From b8b99100c9f485440c9f7fe8873a7431938e06e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20W=C3=A4hlisch?= Date: Wed, 3 Nov 2021 09:45:29 +0100 Subject: [PATCH 01/55] add SECURITY.md (#266) --- SECURITY.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 00000000..28c21fd6 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,34 @@ +# RTRlib Security Policy + +All security bugs reported will be silently fixed in `master` and backported +to the current release. + +## Reporting a Vulnerability + +If a security issue is discovered, please report it to security-rtrlib@googlegroups.com. +A response will be provided within one week. +The issue will be tracked using the [security mailing +list](mailto:security-rtrlib@googlegroups.com). +Only maintainers of the RTRlib are members of this mailing list. +The original reporter of the security vulnerability will be included in the +discussion of the issue, though. + +## Notification of a Vulnerability + +After a fix is provided the security issue will be privately disclosed to the +original reporter, RTRlib security maintainers, and "Trusted RTRlib Users". +A public announcement of the security fix will be made two weeks after the +point release, though this may vary depending on the severity and ability of +trusted RTRlib users to provide the fix. + +## Trusted RTRlib Users + +To access the "Trusted RTRlib Users" notifications on the mailing list +please send information on the RTRlib based service or product as well as +your prefered email address to receive notifications to the [security +mailing list](mailto:security-rtrlib@googlegroups.com). +Early notification of security bugs will be available and should not be +shared publicly. +If done, it will result in access removal from the "Trusted RTRlib Users" +notifications. + From 264a854f39e8d3f5045956ce0af50db89f52c320 Mon Sep 17 00:00:00 2001 From: Colin Sames Date: Thu, 14 Oct 2021 17:55:19 +0200 Subject: [PATCH 02/55] rtrlib/bgpsec: Add BGPsec validation features. - The BGPsec API allows to validate and sign BGPsec paths. - Currently supported BGPsec version is 0. - For cryptographic operations, OpenSSL is used. Both version 1.0 and 1.1 are supported. - The router keys necessary for validation are fetched from the SPKI. --- .gitignore | 1 + CMakeLists.txt | 30 ++ README.md | 15 + rtrlib/bgpsec/bgpsec.c | 585 +++++++++++++++++++++++++++ rtrlib/bgpsec/bgpsec.h | 142 +++++++ rtrlib/bgpsec/bgpsec_private.h | 195 +++++++++ rtrlib/bgpsec/bgpsec_utils.c | 440 ++++++++++++++++++++ rtrlib/bgpsec/bgpsec_utils_private.h | 126 ++++++ rtrlib/config.h.cmake | 24 ++ rtrlib/lib/alloc_utils.c | 1 - rtrlib/rtr/packets.c | 4 + rtrlib/rtr/rtr.c | 1 + rtrlib/rtr_mgr.c | 129 ++++++ rtrlib/rtr_mgr.h | 160 ++++++++ rtrlib/rtrlib.h.cmake | 4 + scripts/check-coding-style.sh | 2 +- 16 files changed, 1857 insertions(+), 2 deletions(-) create mode 100644 rtrlib/bgpsec/bgpsec.c create mode 100644 rtrlib/bgpsec/bgpsec.h create mode 100644 rtrlib/bgpsec/bgpsec_private.h create mode 100644 rtrlib/bgpsec/bgpsec_utils.c create mode 100644 rtrlib/bgpsec/bgpsec_utils_private.h create mode 100644 rtrlib/config.h.cmake diff --git a/.gitignore b/.gitignore index de48f599..f481594a 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ tags *.so* */Makefile rtrlib/rtrlib.h +rtrlib/config.h tools/rpki-rov tools/rtrclient diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e9aa988..856b651a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,6 +78,25 @@ if (NOT DEFINED RTRLIB_TRANSPORT_SSH OR RTRLIB_TRANSPORT_SSH) endif(LIBSSH_FOUND) endif(NOT DEFINED RTRLIB_TRANSPORT_SSH OR RTRLIB_TRANSPORT_SSH) +# bgpsec +if(DEFINED WITH_BGPSEC AND NOT WITH_BGPSEC) + message(STATUS "building librtr without BGPsec support") +else() + find_package(OpenSSL 1.0 QUIET) + + if(OPENSSL_FOUND AND OPENSSL_CRYPTO_LIBRARY) + set(RTRLIB_BGPSEC_ENABLED 1) + include_directories(${OPENSSL_INCLUDE_DIRS}) + set(RTRLIB_SRC ${RTRLIB_SRC} rtrlib/bgpsec/bgpsec.c rtrlib/bgpsec/bgpsec_utils.c) + set(RTRLIB_LINK ${RTRLIB_LINK} ${OPENSSL_LIBRARIES}) + message(STATUS "libcrypto (OpenSSL ${OPENSSL_VERSION}) found, building librtr with BGPsec support") + elseif(WITH_BGPSEC) + message(FATAL_ERROR "libcrypto (OpenSSL) not found, aborting build.") + else() + message(STATUS "libcrypto (OpenSSL) not found, building librtr without BGPsec support.") + endif(OPENSSL_FOUND AND OPENSSL_CRYPTO_LIBRARY) +endif(DEFINED WITH_BGPSEC AND NOT WITH_BGPSEC) + #doxygen target find_package(Doxygen) if(DOXYGEN_FOUND) @@ -120,11 +139,16 @@ ADD_TEST(test_getbits tests/test_getbits) ADD_TEST(test_dynamic_groups tests/test_dynamic_groups) +if(RTRLIB_BGPSEC_ENABLED) + ADD_TEST(test_bgpsec tests/test_bgpsec) +endif(RTRLIB_BGPSEC_ENABLED) + #install lib set (RTRLIB_VERSION_MAJOR 0) set (RTRLIB_VERSION_MINOR 8) set (RTRLIB_VERSION_PATCH 0) CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/rtrlib/rtrlib.h.cmake ${CMAKE_SOURCE_DIR}/rtrlib/rtrlib.h) +CONFIGURE_FILE(${CMAKE_SOURCE_DIR}/rtrlib/config.h.cmake ${CMAKE_SOURCE_DIR}/rtrlib/config.h) set(LIBRARY_VERSION ${RTRLIB_VERSION_MAJOR}.${RTRLIB_VERSION_MINOR}.${RTRLIB_VERSION_PATCH}) set(LIBRARY_SOVERSION ${RTRLIB_VERSION_MAJOR}) set_target_properties(rtrlib PROPERTIES SOVERSION ${LIBRARY_SOVERSION} VERSION ${LIBRARY_VERSION} OUTPUT_NAME rtr) @@ -152,6 +176,12 @@ if(RTRLIB_TRANSPORT_SSH) set (PKG_CONFIG_REQUIRES "libssh >= 0.5.0") endif(RTRLIB_TRANSPORT_SSH) +if(OPENSSL_CRYPTO_LIBRARY) + set (PKG_CONFIG_REQUIRES ${PKG_CONFIG_REQUIRES} "libcrypto >= 1.0") +endif(OPENSSL_CRYPTO_LIBRARY) + +string(REPLACE ";" ", " PKG_CONFIG_REQUIRES "${PKG_CONFIG_REQUIRES}") + # '#include ' includes the "rtrlib/" set (PKG_CONFIG_LIBDIR "\${prefix}/${CMAKE_INSTALL_LIBDIR}") set (PKG_CONFIG_INCLUDEDIR "\${prefix}/include") diff --git a/README.md b/README.md index 28b48955..94862396 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,9 @@ To build the RTRlib, the CMake build system must be installed. To establish an SSH connection between RTR-Client and RTR-Server, the libssh 0.6.x or higher library must also be installed. +To enable BGPsec support for validating and signing AS paths, libssl +1.0 or higher needs to be installed. + cmocka (optional) is required for unit tests Doxygen (optional) is required to create the HTML documentation. @@ -65,6 +68,18 @@ Compilation -D CMAKE_INSTALL_PREFIX= + BGPsec support is enabled by default. If dependencies cannot be + resolved, rtrlib builds without BGPsec. + + To explicitly disable BGPsec: + + -D WITH_BGPSEC=No + + To explicitly enable BGPsec and fail the build if dependencies + cannot be resolved: + + -D WITH_BGPSEC=Yes + * Build library, tests, and tools make diff --git a/rtrlib/bgpsec/bgpsec.c b/rtrlib/bgpsec/bgpsec.c new file mode 100644 index 00000000..55d88b76 --- /dev/null +++ b/rtrlib/bgpsec/bgpsec.c @@ -0,0 +1,585 @@ +/* + * 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 "rtrlib/bgpsec/bgpsec.h" + +#include "rtrlib/bgpsec/bgpsec_private.h" +#include "rtrlib/bgpsec/bgpsec_utils_private.h" +#include "rtrlib/lib/alloc_utils_private.h" +#include "rtrlib/rtrlib_export_private.h" +#include "rtrlib/spki/spkitable_private.h" + +#define SEC_PATH_LEN(seg, idx) \ + struct rtr_secure_path_seg *tmp = seg; \ + while (tmp) { \ + (idx)++; \ + tmp = tmp->next; \ + } + +/** The latest supported BGPsec version. */ +#define BGPSEC_VERSION 0 + +/** + * @brief A static list that contains all supported algorithm suites. + */ +static const uint8_t algorithm_suites[] = {RTR_BGPSEC_ALGORITHM_SUITE_1}; + +/* + * The data for digestion must be ordered exactly like this: + * + * +------------------------------------+ + * | Target AS Number | + * +------------------------------------+----\ + * | Signature Segment : N-1 | \ + * +------------------------------------+ | + * | Secure_Path Segment : N | | + * +------------------------------------+ \ + * ... > Data from + * +------------------------------------+ / N Segments + * | Signature Segment : 1 | | + * +------------------------------------+ | + * | Secure_Path Segment : 2 | | + * +------------------------------------+ / + * | Secure_Path Segment : 1 | / + * +------------------------------------+---/ + * | Algorithm Suite Identifier | + * +------------------------------------+ + * | AFI | + * +------------------------------------+ + * | SAFI | + * +------------------------------------+ + * | NLRI | + * +------------------------------------+ + * + * https://tools.ietf.org/html/rfc8205#section-4.2 + */ + +/* The arrays are passed in "AS path order", meaning the last appended + * Signature Segment / Secure_Path Segment is at the first + * position of the array. + */ + +int rtr_bgpsec_validate_as_path(const struct rtr_bgpsec *data, struct spki_table *table) +{ + /* The AS path validation result. */ + enum rtr_bgpsec_rtvals retval = 0; + + /* The resulting hash. */ + unsigned char *hash_result = NULL; + + /* A temporare spki record */ + struct spki_record *tmp_key = NULL; + + /* A stream that holds the data that is hashed */ + struct stream *s = NULL; + + /* Total size of required space for the stream */ + unsigned int stream_size = 0; + + /* Use a temp variable in the validation loop since we don't want to + * alter data->sigs. + */ + struct rtr_signature_seg *tmp_sig = NULL; + + /* Temp variable that holds the signature length of the of the + * next signature segment. + */ + int tmp_sig_len = 0; + + /* Check, if the parameters are not NULL */ + if (!data || !data->path || !data->sigs || !table) + return RTR_BGPSEC_INVALID_ARGUMENTS; + + /* Check, if there are as many signature segments as there are + * secure path segments + */ + if (data->path_len != data->sigs_len) + return RTR_BGPSEC_WRONG_SEGMENT_COUNT; + + /* Check, if the algorithm suite is supported by RTRlib. */ + if (rtr_bgpsec_has_algorithm_suite(data->alg) == RTR_BGPSEC_ERROR) + return RTR_BGPSEC_UNSUPPORTED_ALGORITHM_SUITE; + + /* Check, if the AFI is usable with BGPsec */ + if ((data->nlri->afi != BGPSEC_IPV4) && (data->nlri->afi != BGPSEC_IPV6)) + return RTR_BGPSEC_UNSUPPORTED_AFI; + + /* Make sure that all router keys are available. */ + retval = check_router_keys(data->sigs, table); + + if (retval != RTR_BGPSEC_SUCCESS) + goto err; + + /* Calculate the required stream size and initialize the stream */ + stream_size = req_stream_size(data, VALIDATION); + s = init_stream(stream_size); + + /* Align the byte sequence and store it in the stream */ + retval = align_byte_sequence(data, s, VALIDATION); + + if (retval != RTR_BGPSEC_SUCCESS) + goto err; + + /* + * The validation is an iterative process. In the first iteration, + * the entire byte sequence is hashed to validate the first signature. + * In the next iteration, the process is repeated. This time, the + * starting position for hashing is moved by an offset to only hash + * the data required to validate the next signature. This procedure + * is repeated until all signatures are validated or a signature is + * determined invalid. + * + * offset: the current position from where on the bytes should + * be hashed. + * + * next_offset: adds to offset, after the bytes on the + * current offset have been processed. next_offset is not + * constant and must be calculated each iteration: + * + * signature length + + * SKI size + + * sizeof(var that holds signature length) + + * sizeof(a secure path segment) + * + * + * offset + * |o++++++++++++++++++++++++++++++++++++++++| bytes + * + * offset+= + * new_offset + * |------------o++++++++++++++++++++++++++++| bytes + * + * offset+= + * new_offset + * |------------------------o++++++++++++++++| bytes + * + * + * A more detailed view can be found at + *https://mailarchive.ietf.org/arch/msg/sidr/8B_e4CNxQCUKeZ_AUzsdnn2f5Mu + **/ + + /* Set retval to RTR_BGPSEC_VALID so the for-condition does not + * fail on the first for loop check. + */ + retval = RTR_BGPSEC_VALID; + tmp_sig = data->sigs; + + for (unsigned int offset = 0, next_offset = 0; offset <= get_stream_size(s) && retval == RTR_BGPSEC_VALID; + offset += next_offset) { + if (tmp_sig->next) + tmp_sig_len = tmp_sig->next->sig_len; + else + tmp_sig_len = tmp_sig->sig_len; + + next_offset = tmp_sig_len + SKI_SIZE + sizeof(tmp_sig->sig_len) + SECURE_PATH_SEG_SIZE; + + /* + * From a certain position on, read until the end of the stream. + * The starting position is determined by the offset. The end + * of the stream is the total length minus the offset. + * The curr pointer points to the resulting "sub-stream". + * Hacky, could be improved. + */ + size_t len = get_stream_size(s) - offset; + + uint8_t *curr = lrtr_malloc(len); + + read_stream_at(curr, s, offset, len); + + retval = hash_byte_sequence(curr, len, data->alg, &hash_result); + + lrtr_free(curr); + + if (retval != RTR_BGPSEC_SUCCESS) + goto err; + + /* Store all router keys for the given SKI in tmp_key. */ + unsigned int router_keys_len = 0; + enum spki_rtvals spki_retval = + spki_table_search_by_ski(table, tmp_sig->ski, &tmp_key, &router_keys_len); + if (spki_retval == SPKI_ERROR) { + retval = RTR_BGPSEC_ERROR; + goto err; + } + + /* Loop in case there are multiple router keys for one SKI. */ + for (unsigned int j = 0; j < router_keys_len; j++) { + /* Validate the siganture depending on the algorithm + * suite. More if-cases are added with new algorithm + * suites. + */ + if (data->alg == RTR_BGPSEC_ALGORITHM_SUITE_1) { + retval = validate_signature(hash_result, tmp_sig, &tmp_key[j]); + } else { + retval = RTR_BGPSEC_UNSUPPORTED_ALGORITHM_SUITE; + goto err; + } + /* As soon as one of the router keys produces a valid + * result, exit the loop. + */ + if (retval == RTR_BGPSEC_VALID) + break; + } + lrtr_free(hash_result); + lrtr_free(tmp_key); + hash_result = NULL; + tmp_key = NULL; + tmp_sig = tmp_sig->next; + } + +err: + if (hash_result) + lrtr_free(hash_result); + if (tmp_key) + lrtr_free(tmp_key); + if (s) + free_stream(s); + + if (retval == RTR_BGPSEC_VALID) + BGPSEC_DBG1("Validation result for the whole BGPsec_PATH: valid"); + else if (retval == RTR_BGPSEC_NOT_VALID) + BGPSEC_DBG1("Validation result for the whole BGPsec_PATH: invalid"); + + return retval; +} + +int rtr_bgpsec_generate_signature(const struct rtr_bgpsec *data, uint8_t *private_key, + struct rtr_signature_seg **new_signature) +{ + /* The signature generation result. */ + enum rtr_bgpsec_rtvals retval = 0; + + /* The resulting hash. */ + unsigned char *hash_result = NULL; + + /* The required size for the signature */ + int req_sig_size = 0; + + /* A stream that holds the data that is hashed */ + struct stream *s = NULL; + + /* Total size of required space for the stream */ + unsigned int stream_size = 0; + + /* OpenSSL private key structure. */ + EC_KEY *priv_key = NULL; + + /* Check, if the parameters are not NULL except for *new_signature, + * which must be NULL. + */ + if (!data || !data->path || !private_key || *new_signature) + return RTR_BGPSEC_INVALID_ARGUMENTS; + + /* Make sure the algorithm suite is supported. */ + if (rtr_bgpsec_has_algorithm_suite(data->alg) == RTR_BGPSEC_ERROR) + return RTR_BGPSEC_UNSUPPORTED_ALGORITHM_SUITE; + + /* Check, if the AFI is usable with BGPsec */ + if ((data->nlri->afi != BGPSEC_IPV4) && (data->nlri->afi != BGPSEC_IPV6)) + return RTR_BGPSEC_UNSUPPORTED_AFI; + + /* Check, if there is one more secure path segment than there are + * signature segments. This is because the next signature is generated + * for a secure path segment, that was not signed yet (the last appended + * secure path segment). + */ + if (data->path_len != (data->sigs_len + 1)) + return RTR_BGPSEC_WRONG_SEGMENT_COUNT; + + /* Load the private key from buffer into OpenSSL structure. */ + retval = load_private_key(&priv_key, private_key); + + if (retval != RTR_BGPSEC_SUCCESS) { + retval = RTR_BGPSEC_LOAD_PRIV_KEY_ERROR; + goto err; + } + + /* Allocate the needed space for the signature */ + req_sig_size = ECDSA_size(priv_key); + if (req_sig_size == 0) { + retval = RTR_BGPSEC_LOAD_PRIV_KEY_ERROR; + goto err; + } + + *new_signature = rtr_bgpsec_new_signature_seg(NULL, req_sig_size, NULL); + if (!(*new_signature)) { + retval = RTR_BGPSEC_ERROR; + goto err; + } + + /* The function call above expects the exact size of the signature. + * Because the signature was not generated yet, we pass the maximum + * possible length to it so enough space is allocated. Then, the + * sig_len field is set to 0, since the signature does not exist yet. + * This is a rather hacky workaround and might get changed in the + * future. + */ + (*new_signature)->sig_len = 0; + + /* Calculate the required stream size and initialize the stream */ + stream_size = req_stream_size(data, SIGNING); + s = init_stream(stream_size); + + /* Align the bytes to prepare them for hashing. */ + retval = align_byte_sequence(data, s, SIGNING); + + if (retval != RTR_BGPSEC_SUCCESS) + goto err; + + /* Hash the aligned bytes. */ + uint8_t *curr = get_stream_start(s); + + retval = hash_byte_sequence(curr, get_stream_size(s), data->alg, &hash_result); + + if (retval != RTR_BGPSEC_SUCCESS) + goto err; + + /* Sign the hash depending on the algorithm suite. */ + retval = sign_byte_sequence(hash_result, priv_key, data->alg, *new_signature); + +err: + if (hash_result) + lrtr_free(hash_result); + if (*new_signature && (retval == RTR_BGPSEC_SIGNING_ERROR)) + rtr_bgpsec_free_signatures(*new_signature); + if (s) + free_stream(s); + if (priv_key) { + EC_KEY_free(priv_key); + priv_key = NULL; + } + + return retval; +} + +/************************************************* + **** Functions for versions and algo suites ***** + ************************************************/ + +int rtr_bgpsec_get_version(void) +{ + return BGPSEC_VERSION; +} + +int rtr_bgpsec_has_algorithm_suite(uint8_t alg_suite) +{ + int alg_suites_len = sizeof(algorithm_suites) / sizeof(uint8_t); + + for (int i = 0; i < alg_suites_len; i++) { + if (alg_suite == algorithm_suites[i]) + return RTR_BGPSEC_SUCCESS; + } + + return RTR_BGPSEC_ERROR; +} + +int rtr_bgpsec_get_algorithm_suites(const uint8_t **algs_arr) +{ + *algs_arr = algorithm_suites; + return sizeof(algorithm_suites) / sizeof(uint8_t); +} + +int rtr_bgpsec_prepend_sig_seg(struct rtr_bgpsec *bgpsec, struct rtr_signature_seg *new_seg) +{ + if (!new_seg || !new_seg->signature || !new_seg->sig_len || ski_is_empty(new_seg->ski)) + return RTR_BGPSEC_ERROR; + + if (bgpsec->sigs) + new_seg->next = bgpsec->sigs; + + bgpsec->sigs = new_seg; + bgpsec->sigs_len++; + + return RTR_BGPSEC_SUCCESS; +} + +struct rtr_signature_seg *rtr_bgpsec_pop_signature_seg(struct rtr_bgpsec *bgpsec) +{ + struct rtr_signature_seg *tmp = NULL; + + if (!bgpsec->sigs) + return NULL; + + tmp = bgpsec->sigs; + bgpsec->sigs = bgpsec->sigs->next; + bgpsec->sigs_len--; + tmp->next = NULL; + return tmp; +} + +int rtr_bgpsec_append_sig_seg(struct rtr_bgpsec *bgpsec, struct rtr_signature_seg *new_seg) +{ + struct rtr_signature_seg *last = bgpsec->sigs; + + if (!new_seg || !new_seg->signature || !new_seg->sig_len || ski_is_empty(new_seg->ski)) + return RTR_BGPSEC_ERROR; + + if (bgpsec->sigs) { + while (last->next) + last = last->next; + last->next = new_seg; + } else { + bgpsec->sigs = new_seg; + } + + bgpsec->sigs_len++; + + return RTR_BGPSEC_SUCCESS; +} + +struct rtr_signature_seg *rtr_bgpsec_new_signature_seg(uint8_t *ski, uint16_t sig_len, uint8_t *signature) +{ + struct rtr_signature_seg *sig_seg = lrtr_malloc(sizeof(struct rtr_signature_seg)); + + if (!sig_seg) + return NULL; + + sig_seg->signature = lrtr_calloc(sig_len, 1); + if (!sig_seg->signature) { + lrtr_free(sig_seg); + return NULL; + } + + if (ski) + memcpy(sig_seg->ski, ski, SKI_SIZE); + else + memset(sig_seg->ski, '\0', SKI_SIZE); + + if (signature) + memcpy(sig_seg->signature, signature, sig_len); + + sig_seg->sig_len = sig_len; + sig_seg->next = NULL; + return sig_seg; +} + +void rtr_bgpsec_free_signatures(struct rtr_signature_seg *seg) +{ + if (!seg) + return; + if (seg->next) + rtr_bgpsec_free_signatures(seg->next); + lrtr_free(seg->signature); + lrtr_free(seg); +} + +void rtr_bgpsec_prepend_sec_path_seg(struct rtr_bgpsec *bgpsec, struct rtr_secure_path_seg *new_seg) +{ + if (bgpsec->path) + new_seg->next = bgpsec->path; + + bgpsec->path = new_seg; + bgpsec->path_len++; +} + +void rtr_bgpsec_append_sec_path_seg(struct rtr_bgpsec *bgpsec, struct rtr_secure_path_seg *new_seg) +{ + struct rtr_secure_path_seg *last = bgpsec->path; + + if (bgpsec->path) { + while (last->next) + last = last->next; + last->next = new_seg; + } else { + bgpsec->path = new_seg; + } + + bgpsec->path_len++; +} + +struct rtr_secure_path_seg *rtr_bgpsec_new_secure_path_seg(uint8_t pcount, uint8_t flags, uint32_t asn) +{ + struct rtr_secure_path_seg *seg = lrtr_malloc(sizeof(struct rtr_secure_path_seg)); + + seg->pcount = pcount; + seg->flags = flags; + seg->asn = asn; + seg->next = NULL; + return seg; +} + +struct rtr_secure_path_seg *rtr_bgpsec_pop_secure_path_seg(struct rtr_bgpsec *bgpsec) +{ + struct rtr_secure_path_seg *tmp = NULL; + + if (!bgpsec->path) + return NULL; + + tmp = bgpsec->path; + bgpsec->path = bgpsec->path->next; + bgpsec->path_len--; + tmp->next = NULL; + return tmp; +} + +struct rtr_bgpsec *rtr_bgpsec_new(uint8_t alg, uint8_t safi, uint16_t afi, uint32_t my_as, uint32_t target_as, + struct rtr_bgpsec_nlri *nlri) +{ + struct rtr_bgpsec *bgpsec = lrtr_malloc(sizeof(struct rtr_bgpsec)); + + if (!bgpsec) + return NULL; + + bgpsec->alg = alg; + bgpsec->safi = safi; + bgpsec->afi = afi; + bgpsec->my_as = my_as; + bgpsec->target_as = target_as; + bgpsec->nlri = nlri; + bgpsec->path_len = 0; + bgpsec->path = NULL; + bgpsec->sigs_len = 0; + bgpsec->sigs = NULL; + + return bgpsec; +} + +void rtr_bgpsec_free(struct rtr_bgpsec *bgpsec) +{ + if (bgpsec->path) + rtr_bgpsec_free_secure_path(bgpsec->path); + if (bgpsec->sigs) + rtr_bgpsec_free_signatures(bgpsec->sigs); + if (bgpsec->nlri) + rtr_bgpsec_nlri_free(bgpsec->nlri); + lrtr_free(bgpsec); +} + +void rtr_bgpsec_free_secure_path(struct rtr_secure_path_seg *seg) +{ + if (!seg) + return; + if (seg->next) + rtr_bgpsec_free_secure_path(seg->next); + lrtr_free(seg); +} + +struct rtr_bgpsec_nlri *rtr_bgpsec_nlri_new(int nlri_len) +{ + struct rtr_bgpsec_nlri *nlri = lrtr_malloc(sizeof(struct rtr_bgpsec_nlri)); + + nlri->nlri = lrtr_malloc(nlri_len); + return nlri; +} + +void rtr_bgpsec_nlri_free(struct rtr_bgpsec_nlri *nlri) +{ + if (!nlri) + return; + + if (nlri->nlri) + lrtr_free(nlri->nlri); + + lrtr_free(nlri); +} + +void rtr_bgpsec_add_spki_record(struct spki_table *table, struct spki_record *record) +{ + spki_table_add_entry(table, record); +} diff --git a/rtrlib/bgpsec/bgpsec.h b/rtrlib/bgpsec/bgpsec.h new file mode 100644 index 00000000..1356ee60 --- /dev/null +++ b/rtrlib/bgpsec/bgpsec.h @@ -0,0 +1,142 @@ +/* + * 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_bgpsec_h BGPsec AS path validation + * @brief BGPsec allows for validation of the BGPsec_PATH attribute of a + * BGPsec update. + * @{ + */ + +#ifndef RTR_BGPSEC_H +#define RTR_BGPSEC_H + +#include "rtrlib/lib/ip.h" +#include "rtrlib/spki/spkitable.h" + +#include + +#define BGPSEC_IPV4 1 +#define BGPSEC_IPV6 2 + +/** + * @brief All supported algorithm suites. + */ +enum rtr_bgpsec_algorithm_suites { + /** Algorithm suite 1 */ + RTR_BGPSEC_ALGORITHM_SUITE_1 = 1, +}; + +/** + * @brief Status codes for various cases. + */ +enum rtr_bgpsec_rtvals { + /** At least one signature is not valid. */ + RTR_BGPSEC_NOT_VALID = 2, + /** All signatures are valid. */ + RTR_BGPSEC_VALID = 1, + /** An operation was successful. */ + RTR_BGPSEC_SUCCESS = 0, + /** An operation was not sucessful. */ + RTR_BGPSEC_ERROR = -1, + /** The public key could not be loaded. */ + RTR_BGPSEC_LOAD_PUB_KEY_ERROR = -2, + /** The private key could not be loaded. */ + RTR_BGPSEC_LOAD_PRIV_KEY_ERROR = -3, + /** The SKI for a router key was not found. */ + RTR_BGPSEC_ROUTER_KEY_NOT_FOUND = -4, + /** An error during signing occurred. */ + RTR_BGPSEC_SIGNING_ERROR = -5, + /** The specified algorithm suite is not supported by RTRlib. */ + RTR_BGPSEC_UNSUPPORTED_ALGORITHM_SUITE = -6, + /** The specified AFI is not supported by BGPsec. */ + RTR_BGPSEC_UNSUPPORTED_AFI = -7, + /** The count of signature and secure path segments are not equal. */ + RTR_BGPSEC_WRONG_SEGMENT_COUNT = -8, + /** There is data missing for validation or signing. */ + RTR_BGPSEC_INVALID_ARGUMENTS = -9, +}; + +/** + * @brief A single Secure Path Segment. + * @param next Reference to the next Secure Path Segment (do not edit manually). + * @param pcount The pCount field of the segment. + * @param flags The flags field of the segment. + * @param asn The ASN of the Segment. + */ +struct rtr_secure_path_seg { + /** Reference to the next Secure Path Segment (do not edit manually). */ + struct rtr_secure_path_seg *next; + uint8_t pcount; + uint8_t flags; + uint32_t asn; +}; + +/** + * @brief A single Signature Segment. + * @param next Reference to the next Signature Segment (do not edit manually). + * @param ski The SKI of the segment. + * @param sig_len The length in octets of the signature field. + * @param signature The signature of the segment. + */ +struct rtr_signature_seg { + struct rtr_signature_seg *next; + uint8_t ski[SKI_SIZE]; + uint16_t sig_len; + /** The signature of the segment. */ + uint8_t *signature; +}; + +/** + * @brief This struct contains the Network Layer Reachability Information + * (NLRI). The NLRI consists of a prefix and its length. + * @param afi The Address Family Identifier. + * @param safi The Subsequent Address Family Identifier. + * @param nlri_len The length of the nlri in bits. + * @param nlri The Network Layer Reachability Information. Trailing bits + * must be set to 0. + */ +struct rtr_bgpsec_nlri { + uint16_t afi; + uint8_t safi; + uint8_t nlri_len; + uint8_t *nlri; +}; + +/** + * @brief The data that is passed to the rtr_mgr_bgpsec_validate_as_path function. + * @param alg The Algorithm Suite Identifier. + * @param safi The Subsequent Address Family Identifier. + * @param afi The Address Family Identifier. + * @param my_as The AS that is currently performing validation (you). + * @param target_as The AS where the update should be sent to. + * @param sigs_len Count of Signature Segments (do not edit manually). + * @param path_len Count of Secure Path Segments (do not edit manually). + * @param nlri Reference to the Network Layer Reachability Information. + * @param sigs Reference to the Signature Segments. + * @param path Reference to the Secure Path Segments. + */ +struct rtr_bgpsec { + uint8_t alg; + uint8_t safi; + uint16_t afi; + uint32_t my_as; + uint32_t target_as; + /** Count of Signature Segments (do not edit manually). */ + uint16_t sigs_len; + /** Count of Secure Path Segments (do not edit manually). */ + uint8_t path_len; + struct rtr_bgpsec_nlri *nlri; + /** Reference to the Signature Segments. */ + struct rtr_signature_seg *sigs; + /** Reference to the Secure Path Segments. */ + struct rtr_secure_path_seg *path; +}; +#endif +/* @} */ diff --git a/rtrlib/bgpsec/bgpsec_private.h b/rtrlib/bgpsec/bgpsec_private.h new file mode 100644 index 00000000..82750057 --- /dev/null +++ b/rtrlib/bgpsec/bgpsec_private.h @@ -0,0 +1,195 @@ +/* + * 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_bgpsec_h BGPsec AS path validation + * @brief BGPsec allows for validation of the BGPsec_PATH attribute of a + * BGPsec update. + * @{ + */ + +#ifndef RTR_BGPSEC_PRIVATE_H +#define RTR_BGPSEC_PRIVATE_H + +#include "rtrlib/bgpsec/bgpsec_utils_private.h" + +/** + * @brief Validation function for AS path validation. + * @param[in] data Data required for AS path validation. See @ref rtr_bgpsec. + * @param[in] table The SPKI table that contains the router keys. + * @return RTR_BGPSEC_VALID If the AS path was valid. + * @return RTR_BGPSEC_NOT_VALID If the AS path was not valid. + * @return RTR_BGPSEC_ERROR If an error occurred. Refer to error codes for + * more details. + */ +int rtr_bgpsec_validate_as_path(const struct rtr_bgpsec *data, struct spki_table *table); + +/** + * @brief Signing function for a BGPsec_PATH. + * @param[in] data Data required for AS path validation. See @ref rtr_bgpsec. + * @param[in] private_key The raw bytes of the private key that is used for signing. + * @param[out] new_signature Contains the generated signature and its length if + * successful. Must not be allocated. + * @return RTR_BGPSEC_SUCCESS If the signature was successfully generated. + * @return RTR_BGPSEC_ERROR If an error occurred. Refer to error codes for + * more details. + */ +int rtr_bgpsec_generate_signature(const struct rtr_bgpsec *data, uint8_t *private_key, + struct rtr_signature_seg **new_signature); + +/** + * @brief Returns the highest supported BGPsec version. + * @return RTR_BGPSEC_VERSION The currently supported BGPsec version. + */ +int rtr_bgpsec_get_version(void); + +/** + * @brief Check, if an algorithm suite is supported by RTRlib. + * @param[in] alg_suite The algorithm suite that is to be checked. + * @return RTR_BGPSEC_SUCCESS If the algorithm suite is supported. + * @return RTR_BGPSEC_ERROR If the algorithm suite is not supported. + */ +int rtr_bgpsec_has_algorithm_suite(uint8_t alg_suite); + +/** + * @brief Returns a pointer to a list that holds all supported algorithm suites. + * @param[out] algs_arr A char pointer that contains all supported suites. + * @return ALGORITHM_SUITES_COUNT The size of algs_arr + */ +int rtr_bgpsec_get_algorithm_suites(const uint8_t **algs_arr); + +/** @brief Free a signature and any signatures that are pointed to. + * @param[in] seg The signature that has been passed to the signing + * function. + */ +void rtr_bgpsec_free_signatures(struct rtr_signature_seg *seg); + +/** + * @brief Return an allocated and initialized Secure Path Segment. + * @param[in] pcount The pcount field. + * @param[in] flags The flags field. + * @param[in] asn The ASN of the segment. + * @return A pointer to an initialized rtr_secure_path_seg struct + */ +struct rtr_secure_path_seg *rtr_bgpsec_new_secure_path_seg(uint8_t pcount, uint8_t flags, uint32_t asn); + +/** + * @brief Prepend a given Secure Path Segment to @ref rtr_bgpsec.path. + * @param[in] bgpsec The rtr_bgpsec struct that holds the path. + * @param[in] new_seg The Secure Path Segment that is appended to the path. + */ +void rtr_bgpsec_prepend_sec_path_seg(struct rtr_bgpsec *bgpsec, struct rtr_secure_path_seg *new_seg); + +/** + * @brief Return an allocated and initialized Signature. + * @param[in] ski The Subject Key Identifier as byte representation. + * @param[in] sig_len The length of the signature. + * @param[in] signature The signature itself. + * @return A pointer to an initialized rtr_secure_path_seg struct. + * @ref rtr_signature_seg.signature is allocated with sig_len + * bytes. + */ +struct rtr_signature_seg *rtr_bgpsec_new_signature_seg(uint8_t *ski, uint16_t sig_len, uint8_t *signature); + +/** + * @brief Prepend a given Signature Segment to @ref rtr_bgpsec.sigs. All fields + * of the new_seg must be filled. + * @param[in] bgpsec The rtr_bgpsec struct that holds the signatures. + * @param[in] new_seg The Signature Segment that is appended to the signatures. + * @return RTR_BGPSEC_SUCCESS If the signature was successfully prepended. + * @return RTR_BGPSEC_ERROR If an error occurred during prepending, e.g. one + * or more fields of new_seg was missing. + */ +int rtr_bgpsec_prepend_sig_seg(struct rtr_bgpsec *bgpsec, struct rtr_signature_seg *new_seg); + +/** + * @brief Initializes and returns a pointer to a rtr_bgpsec struct. + * @param[in] alg The Algorithm Suite Identifier. + * @param[in] safi The Subsequent Address Family Identifier. + * @param[in] afi The Address Family Identifier. + * @param[in] my_as The AS that is currently performing validation (you). + * @param[in] target_as The AS where the update should be sent to. + * @param[in] nlri The Network Layer Reachability Information. + * @return A pointer to an initialized rtr_bgpsec struct. + */ +struct rtr_bgpsec *rtr_bgpsec_new(uint8_t alg, uint8_t safi, uint16_t afi, uint32_t my_as, uint32_t target_as, + struct rtr_bgpsec_nlri *nlri); + +/** + * @brief Allocate memory for a rtr_bgpsec_nlri struct. + * @return A pointer to an allocated rtr_bgpsec_nlri struct. + */ +struct rtr_bgpsec_nlri *rtr_bgpsec_nlri_new(int nlri_len); + +/** + * @brief Free a rtr_bgpsec_nlri struct. + * @param[in] nlri The rtr_bgpsec_nlri struct that is to be freed. + */ +void rtr_bgpsec_nlri_free(struct rtr_bgpsec_nlri *nlri); + +/** + * @brief Free a rtr_bgpsec struct and any Secure Path and Signature + * Segments it holds. + * @param[in] bgpsec The rtr_bgpsec struct that is to be freed. + */ +void rtr_bgpsec_free(struct rtr_bgpsec *bgpsec); + +/** + * @brief Free a Secure Path Segment and any segments that are pointed to + * by @ref rtr_secure_path_seg.next. + * @param[in] seg The Secure Path Segment that is to be freed. + */ +void rtr_bgpsec_free_secure_path(struct rtr_secure_path_seg *seg); + +/** + * @brief Pop off the first Signature Segment from a given rtr_bgpsec struct + * and return this Signature Segment. + * @param[in] bgpsec The rtr_bgpsec struct containing the Signature Segments + * @ref rtr_bgpsec.sigs. + * @return The Signature Segment that was popped off from @ref rtr_bgpsec.sigs. + */ +struct rtr_signature_seg *rtr_bgpsec_pop_signature_seg(struct rtr_bgpsec *bgpsec); + +/** + * @brief Pop off the first Secure Path Segment from a given rtr_bgpsec struct + * and return this Secure Path Segment. + * @param[in] bgpsec The rtr_bgpsec struct containing the Secure Path Segments + * @ref rtr_bgpsec.path. + * @return The Secure Path Segment that was popped off from @ref rtr_bgpsec.path. + */ +struct rtr_secure_path_seg *rtr_bgpsec_pop_secure_path_seg(struct rtr_bgpsec *bgpsec); + +/** + * @brief Append a Signature Segment to the end of the @ref rtr_bgpsec.sigs of a + * given rtr_bgpsec struct. + * @param[in] bgpsec The rtr_bgpsec struct with the @ref rtr_bgpsec.sigs to + * append the Signature Segment to. + * @param[in] new_seg The Signature Segments that will be appended. + * @return RTR_BGPSEC_SUCCESS If the Signature Segment was successfully appended. + * @return RTR_BGPSEC_ERROR If an error occurred in the proccess. + */ +int rtr_bgpsec_append_sig_seg(struct rtr_bgpsec *bgpsec, struct rtr_signature_seg *new_seg); + +/** + * @brief Append a Secure Path Segment to the end of the @ref rtr_bgpsec.path of a + * given rtr_bgpsec struct. + * @param[in] bgpsec The rtr_bgpsec struct with the @ref rtr_bgpsec.path to + * append the Secure Path Segment to. + * @param[in] new_seg The Secure Path Segments that will be appended. + */ +void rtr_bgpsec_append_sec_path_seg(struct rtr_bgpsec *bgpsec, struct rtr_secure_path_seg *new_seg); + +/** + * @brief Manually add a SPKI record into the SPKI table. + * @param[in] table The SPKI table holding the SPKI data. + * @param[in] record The new record that will be added to the SPKI table. + */ +void rtr_bgpsec_add_spki_record(struct spki_table *table, struct spki_record *record); +#endif +/* @} */ diff --git a/rtrlib/bgpsec/bgpsec_utils.c b/rtrlib/bgpsec/bgpsec_utils.c new file mode 100644 index 00000000..b4b41c24 --- /dev/null +++ b/rtrlib/bgpsec/bgpsec_utils.c @@ -0,0 +1,440 @@ +/* + * 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 "rtrlib/bgpsec/bgpsec_utils_private.h" +#include "rtrlib/spki/spkitable_private.h" + +#include + +/** Macro to get the NLRI length in bytes. */ +#define NLRI_BYTE_LEN(data) (((data)->nlri->nlri_len + 7) / 8) + +struct stream { + size_t size; + uint8_t *stream; + const uint8_t *start; + uint16_t w_head; + uint16_t r_head; +}; + +struct stream *init_stream(uint16_t size) +{ + struct stream *s = lrtr_calloc(sizeof(struct stream), 1); + + s->stream = lrtr_calloc(size, 1); + s->start = s->stream; + s->size = size; + s->w_head = 0; + s->r_head = 0; + return s; +} + +/* cppcheck-suppress unusedFunction */ +struct stream *copy_stream(struct stream *s) +{ + struct stream *cpy = init_stream(s->size); + + memcpy(cpy->stream, s->stream, s->size); + cpy->w_head = s->w_head; + cpy->r_head = s->r_head; + return cpy; +} + +void free_stream(struct stream *s) +{ + lrtr_free((uint8_t *)s->start); + lrtr_free(s); +} + +void write_stream(struct stream *s, void *data, uint16_t len) +{ + memcpy(s->stream + s->w_head, data, len); + s->w_head += len; +} + +/* cppcheck-suppress unusedFunction */ +uint8_t read_stream(struct stream *s) +{ + uint8_t c = *(s->start + s->r_head); + + s->r_head++; + return c; +} + +/* cppcheck-suppress unusedFunction */ +void read_n_bytes_stream(uint8_t *buff, struct stream *s, uint16_t len) +{ + if ((s->r_head + len) >= s->size) + len = (s->size - s->r_head) - 1; + memcpy(buff, s->start + s->r_head, len); + s->r_head += len; +} + +void read_stream_at(uint8_t *buff, struct stream *s, uint16_t start, uint16_t len) +{ + if (start + len > s->size) + len = s->size - start; + memcpy(buff, s->start + start, len); +} + +uint8_t *get_stream_start(struct stream *s) +{ + return (uint8_t *)s->start; +} + +size_t get_stream_size(struct stream *s) +{ + return s->size; +} + +size_t req_stream_size(const struct rtr_bgpsec *data, enum align_type type) +{ + unsigned int sig_segs_size = get_sig_seg_size(data->sigs, type); + uint8_t nlri_len_b = (data->nlri->nlri_len + 7) / 8; // bits to bytes + unsigned int bytes_len = 9 // alg(1) + afi(2) + safi(1) + asn(4) + prefix_len(1) + + nlri_len_b + sig_segs_size + (SECURE_PATH_SEG_SIZE * data->path_len); + return bytes_len; +} + +int get_sig_seg_size(const struct rtr_signature_seg *sig_segs, enum align_type type) +{ + unsigned int sig_segs_size = 0; + + if (!sig_segs) + return 0; + + /* Iterate over all signature segments and add the calculated + * length to sig_segs_size. Return the sum at the end. + */ + const struct rtr_signature_seg *curr = sig_segs; + + if (type == VALIDATION) + curr = curr->next; + + while (curr) { + sig_segs_size += curr->sig_len + sizeof(curr->sig_len) + SKI_SIZE; + curr = curr->next; + } + + return sig_segs_size; +} + +int check_router_keys(const struct rtr_signature_seg *sig_segs, struct spki_table *table) +{ + struct spki_record *tmp_key = NULL; + const struct rtr_signature_seg *curr = sig_segs; + + while (curr) { + unsigned int router_keys_len = 0; + enum spki_rtvals spki_retval = + spki_table_search_by_ski(table, (uint8_t *)curr->ski, &tmp_key, &router_keys_len); + if (spki_retval == SPKI_ERROR) + return RTR_BGPSEC_ERROR; + + /* Return an error, if a router key was not found. */ + if (router_keys_len == 0) { + char ski_str[SKI_STR_LEN] = {0}; + + ski_to_char(ski_str, (uint8_t *)curr->ski); + BGPSEC_DBG("ERROR: Could not find router key for SKI: %s", ski_str); + return RTR_BGPSEC_ROUTER_KEY_NOT_FOUND; + } + lrtr_free(tmp_key); + curr = curr->next; + } + + return RTR_BGPSEC_SUCCESS; +} + +int byte_sequence_to_str(char *buffer, uint8_t *bytes, unsigned int bytes_len, unsigned int tabstops) +{ + unsigned int bytes_printed = 1; + + for (unsigned int j = 0; j < tabstops; j++) + buffer += sprintf(buffer, "\t"); + + for (unsigned int i = 0; i < bytes_len; i++, bytes_printed++) { + buffer += sprintf(buffer, "%02X ", bytes[i]); + + /* Only print 16 bytes in a single line. */ + if (bytes_printed % 16 == 0) { + buffer += sprintf(buffer, "\n"); + for (unsigned int j = 0; j < tabstops; j++) + buffer += sprintf(buffer, "\t"); + } + } + + /* If there was no new line printed at the end of the for loop, + * print an extra new line. + */ + if (bytes_len % 16 != 0) + buffer += sprintf(buffer, "\n"); + sprintf(buffer, "\n"); + return RTR_BGPSEC_SUCCESS; +} + +/* cppcheck-suppress unusedFunction */ +int bgpsec_segment_to_str(char *buffer, struct rtr_signature_seg *sig_seg, struct rtr_secure_path_seg *sec_path) +{ + char byte_buffer[256] = {'\0'}; + + buffer += sprintf(buffer, "++++++++++++++++++++++++++++++++++++++++\n"); + buffer += sprintf(buffer, "Signature Segment:\n"); + buffer += sprintf(buffer, "\tSKI:\n"); + + byte_sequence_to_str(byte_buffer, sig_seg->ski, SKI_SIZE, 2); + buffer += sprintf(buffer, "%s\n", byte_buffer); + + buffer += sprintf(buffer, "\tLength: %d\n", sig_seg->sig_len); + buffer += sprintf(buffer, "\tSignature:\n"); + + memset(byte_buffer, 0, sizeof(byte_buffer)); + byte_sequence_to_str(byte_buffer, sig_seg->signature, sig_seg->sig_len, 2); + buffer += sprintf(buffer, "%s\n", byte_buffer); + + buffer += sprintf(buffer, "----------------------------------------\n"); + buffer += sprintf(buffer, + "Secure_Path Segment:\n" + "\tpCount: %d\n" + "\tFlags: %d\n" + "\tAS number: %d\n", + sec_path->pcount, sec_path->flags, sec_path->asn); + buffer += sprintf(buffer, "++++++++++++++++++++++++++++++++++++++++\n"); + buffer += sprintf(buffer, "\n"); + *buffer = '\0'; + + return RTR_BGPSEC_SUCCESS; +} + +void ski_to_char(char *ski_str, uint8_t *ski) +{ + for (unsigned int i = 0; i < SKI_SIZE; i++) + sprintf(&ski_str[i * 3], "%02X ", ski[i]); +} + +/* + * One step in validating a BGPsec signature is hashing some of the + * content of the BGPsec update. These information must be aligned + * in a specific order before they are hashed. The function below + * handles this alignment. Since every byte affects the resulting + * hash, padding or trailing bytes must not exist in the byte + * sequence. + */ +int align_byte_sequence(const struct rtr_bgpsec *data, struct stream *s, enum align_type type) +{ + /* Variables used for network-to-host-order transformation. */ + uint32_t asn = 0; + uint16_t afi = 0; + + /* Temp secure path and signature segments to prevent any + * alteration of the original data. + */ + struct rtr_secure_path_seg *tmp_sec = NULL; + struct rtr_signature_seg *tmp_sig = NULL; + + /* The data alignment begins here, starting with the target ASN. */ + asn = htonl(data->target_as); + write_stream(s, &asn, sizeof(asn)); + + /* Depending on whether we are dealing with alignment for validation + * or signing, the first signature segment is skipped. + */ + if (type == VALIDATION) + tmp_sig = data->sigs->next; + else + tmp_sig = data->sigs; + + tmp_sec = data->path; + + while (tmp_sec) { + if (tmp_sig) { + uint16_t sig_len = htons(tmp_sig->sig_len); + + /* Write the signature segment data to stream. */ + write_stream(s, tmp_sig->ski, SKI_SIZE); + write_stream(s, &sig_len, sizeof(sig_len)); + write_stream(s, tmp_sig->signature, tmp_sig->sig_len); + + tmp_sig = tmp_sig->next; + } + + /* Write the secure path segment data to stream. */ + write_stream(s, (uint8_t *)&tmp_sec->pcount, 1); + write_stream(s, (uint8_t *)&tmp_sec->flags, 1); + + asn = htonl(tmp_sec->asn); + write_stream(s, &asn, sizeof(asn)); + tmp_sec = tmp_sec->next; + } + + /* Write the rest of the data to stream. */ + write_stream(s, (uint8_t *)&data->alg, 1); + + afi = htons(data->afi); + write_stream(s, &afi, sizeof(afi)); + + write_stream(s, (uint8_t *)&data->safi, 1); + write_stream(s, (uint8_t *)&data->nlri->nlri_len, 1); + + /* Write the NLRI to stream */ + write_stream(s, data->nlri->nlri, NLRI_BYTE_LEN(data)); + + return RTR_BGPSEC_SUCCESS; +} + +int validate_signature(const unsigned char *hash, const struct rtr_signature_seg *sig, struct spki_record *record) +{ + int status = 0; + enum rtr_bgpsec_rtvals retval; + + EC_KEY *pub_key = NULL; + + /* Load the contents of the spki buffer into the + * OpenSSL public key. + */ + retval = load_public_key(&pub_key, record->spki); + + if (retval != RTR_BGPSEC_SUCCESS) { + /*The output string looks like this: "XX XX XX XX"*/ + /*where XX is a single byte. Including the spaces,*/ + /*we need to multiply by 3. The plus 1 is for the string*/ + /*terminator.*/ + char ski_str[(SKI_SIZE * 3) + 1] = {'\0'}; + + ski_to_char(ski_str, record->ski); + BGPSEC_DBG("WARNING: Invalid public key for SKI: %s", ski_str); + retval = RTR_BGPSEC_ERROR; + goto err; + } + + /* The OpenSSL validation function to validate the signature. */ + status = ECDSA_verify(0, hash, SHA256_DIGEST_LENGTH, sig->signature, sig->sig_len, pub_key); + + switch (status) { + case -1: + BGPSEC_DBG1("ERROR: Failed to verify EC Signature"); + retval = RTR_BGPSEC_ERROR; + break; + case 0: + BGPSEC_DBG1("Validation result of signature: invalid"); + retval = RTR_BGPSEC_NOT_VALID; + break; + case 1: + BGPSEC_DBG1("Validation result of signature: valid"); + retval = RTR_BGPSEC_VALID; + break; + } + +err: + EC_KEY_free(pub_key); + + return retval; +} + +int load_public_key(EC_KEY **pub_key, uint8_t *spki) +{ + char *p = NULL; + int status = 0; + + p = (char *)spki; + *pub_key = NULL; + + /* This whole procedure is one way to copy the spki into + * an EC_KEY, suggested by OpenSSL. Basically, this function + * returns the public key as a long int, which can later be + * casted to an EC_KEY + */ + *pub_key = d2i_EC_PUBKEY(NULL, (const unsigned char **)&p, (long)SPKI_SIZE); + + if (!*pub_key) + return RTR_BGPSEC_LOAD_PUB_KEY_ERROR; + + status = EC_KEY_check_key(*pub_key); + if (status == 0) { + EC_KEY_free(*pub_key); + *pub_key = NULL; + return RTR_BGPSEC_LOAD_PUB_KEY_ERROR; + } + + return RTR_BGPSEC_SUCCESS; +} + +int load_private_key(EC_KEY **priv_key, uint8_t *bytes_key) +{ + int status = 0; + char *p = (char *)bytes_key; + *priv_key = NULL; + + /* The private key copying is similar to the public key + * copying, except that the private key is returned directly + * as an EC_KEY. + */ + *priv_key = d2i_ECPrivateKey(NULL, (const unsigned char **)&p, (long)PRIVATE_KEY_LENGTH); + + if (!*priv_key) + return RTR_BGPSEC_LOAD_PRIV_KEY_ERROR; + + status = EC_KEY_check_key(*priv_key); + if (status == 0) { + EC_KEY_free(*priv_key); + *priv_key = NULL; + return RTR_BGPSEC_LOAD_PRIV_KEY_ERROR; + } + + return RTR_BGPSEC_SUCCESS; +} + +int hash_byte_sequence(uint8_t *bytes, size_t bytes_len, uint8_t alg_suite_id, unsigned char **hash_result) +{ + if (alg_suite_id == RTR_BGPSEC_ALGORITHM_SUITE_1) { + SHA256_CTX ctx; + + *hash_result = lrtr_malloc(SHA256_DIGEST_LENGTH); + if (!*hash_result) + return RTR_BGPSEC_ERROR; + + SHA256_Init(&ctx); + SHA256_Update(&ctx, (const unsigned char *)bytes, bytes_len); + SHA256_Final(*hash_result, &ctx); + + if (!*hash_result) + return RTR_BGPSEC_ERROR; + } else { + return RTR_BGPSEC_UNSUPPORTED_ALGORITHM_SUITE; + } + + return RTR_BGPSEC_SUCCESS; +} + +int sign_byte_sequence(uint8_t *hash_result, EC_KEY *priv_key, uint8_t alg, struct rtr_signature_seg *new_signature) +{ + enum rtr_bgpsec_rtvals retval = RTR_BGPSEC_SUCCESS; + unsigned int sig_res = 0; + + if (alg == RTR_BGPSEC_ALGORITHM_SUITE_1) { + ECDSA_sign(0, hash_result, SHA256_DIGEST_LENGTH, new_signature->signature, &sig_res, priv_key); + if (sig_res < 1) + retval = RTR_BGPSEC_SIGNING_ERROR; + else + new_signature->sig_len = sig_res; + } else { + retval = RTR_BGPSEC_UNSUPPORTED_ALGORITHM_SUITE; + } + + return retval; +} + +int ski_is_empty(uint8_t *ski) +{ + for (int i = 0; i < SKI_SIZE; i++) { + if (ski[i]) + return 0; + } + return 1; +} diff --git a/rtrlib/bgpsec/bgpsec_utils_private.h b/rtrlib/bgpsec/bgpsec_utils_private.h new file mode 100644 index 00000000..45561c85 --- /dev/null +++ b/rtrlib/bgpsec/bgpsec_utils_private.h @@ -0,0 +1,126 @@ +/* + * 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_BGPSEC_UTILS_PRIVATE_H +#define RTR_BGPSEC_UTILS_PRIVATE_H + +#include "rtrlib/bgpsec/bgpsec.h" +#include "rtrlib/lib/alloc_utils_private.h" +#include "rtrlib/lib/log_private.h" +#include "rtrlib/rtrlib_export_private.h" + +#include +#include +#include + +#define BGPSEC_DBG(fmt, ...) lrtr_dbg("BGPSEC: " fmt, ##__VA_ARGS__) +#define BGPSEC_DBG1(a) lrtr_dbg("BGPSEC: " a) + +/** The length of a rtr_secure_path_seg without the next pointer: + * pcount(1) + flags(1) + asn(4) + */ +#define SECURE_PATH_SEG_SIZE 6 + +/** The string length of a SKI, including spaces. */ +#define SKI_STR_LEN 61 + +/** The total length of a private key in bytes. */ +#define PRIVATE_KEY_LENGTH 121L + +/** Control flag, validation and signing procedures for aligning data differs. + */ +enum align_type { + VALIDATION, + SIGNING, +}; + +/* Forward declaration of stream to make it opaque. */ +struct stream; + +/* Initialize a stream of size bytes */ +struct stream *init_stream(uint16_t size); + +/* Copy a stream s and return the copy */ +struct stream *copy_stream(struct stream *s); + +/* Free stream s */ +void free_stream(struct stream *s); + +/* Write len bytes from data to stream s */ +void write_stream(struct stream *s, void *data, uint16_t len); + +/* Get the start position pointer of stream s */ +uint8_t *get_stream_start(struct stream *s); + +/* Get the size of the storable data of stream s */ +size_t get_stream_size(struct stream *s); + +/* Read one byte from stream s */ +uint8_t read_stream(struct stream *s); + +/* Read len bytes from stream s and write them to buff */ +void read_n_bytes_stream(uint8_t *buff, struct stream *s, uint16_t len); + +/* Read len bytes from stream s, starting from position start and write + * the result to buff. + */ +void read_stream_at(uint8_t *buff, struct stream *s, uint16_t start, uint16_t len); + +/* Calculate the reqired size for a stream, so that all information from data + * fit into it. type controls, if it is for validation or signing purposes. + */ +size_t req_stream_size(const struct rtr_bgpsec *data, enum align_type type); + +/* Get the length in bytes for a all signature segments */ +int get_sig_seg_size(const struct rtr_signature_seg *sig_segs, enum align_type type); + +/* Check, if there is at least one router key for each SKI from sig_segs. */ +int check_router_keys(const struct rtr_signature_seg *sig_segs, struct spki_table *table); + +/* Store the string representation of a BGPsec_PATH segment in buffer. */ +int bgpsec_segment_to_str(char *buffer, struct rtr_signature_seg *sig_seg, struct rtr_secure_path_seg *sec_path); + +/* Store the hex-string representation of a byte sequence in buffer. */ +int byte_sequence_to_str(char *buffer, uint8_t *bytes, unsigned int bytes_len, unsigned int tabstops); + +/* Takes a binary encoded SKI and stores it in ski_str as a human readable + * hex string. + */ +void ski_to_char(char *ski_str, uint8_t *ski); + +/* Align the BGPsec data as a byte sequence and store it in stream s. type + * controls, if the alignment is for validation or signing. + */ +int align_byte_sequence(const struct rtr_bgpsec *data, struct stream *s, enum align_type type); + +/* Hash a byte sequence and store it in result_buffer. */ +int hash_byte_sequence(uint8_t *bytes, size_t bytes_len, uint8_t alg_suite_id, unsigned char **result_buffer); + +/* Validate a signature sig. */ +int validate_signature(const unsigned char *hash, const struct rtr_signature_seg *sig, struct spki_record *record); + +/* Load a binary private key bytes_key and store it in the openssl EC_KEY + * priv_key. + */ +int load_private_key(EC_KEY **priv_key, uint8_t *bytes_key); + +/* Load a binary public key spki and store it in the openssl EC_KEY + * pub_key. + */ +int load_public_key(EC_KEY **pub_key, uint8_t *spki); + +/* Sign a byte sequence, depending on the algorithm suite. The signature and + * its length are stored in new_signature. + */ +int sign_byte_sequence(uint8_t *hash_result, EC_KEY *priv_key, uint8_t alg, struct rtr_signature_seg *new_signature); + +/* Check, if all elements of a SKI are 0. */ +int ski_is_empty(uint8_t *ski); + +#endif diff --git a/rtrlib/config.h.cmake b/rtrlib/config.h.cmake new file mode 100644 index 00000000..617e394e --- /dev/null +++ b/rtrlib/config.h.cmake @@ -0,0 +1,24 @@ +/* + * 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 CONFIG_H +#define CONFIG_H + +#cmakedefine RTRLIB_BGPSEC_ENABLED + +#endif + +#ifdef __cplusplus +} +#endif + diff --git a/rtrlib/lib/alloc_utils.c b/rtrlib/lib/alloc_utils.c index e3d68313..2b8d6b1b 100644 --- a/rtrlib/lib/alloc_utils.c +++ b/rtrlib/lib/alloc_utils.c @@ -35,7 +35,6 @@ 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; diff --git a/rtrlib/rtr/packets.c b/rtrlib/rtr/packets.c index ec007796..f16d6d32 100644 --- a/rtrlib/rtr/packets.c +++ b/rtrlib/rtr/packets.c @@ -9,6 +9,7 @@ #include "packets_private.h" +#include "rtrlib/config.h" #include "rtrlib/lib/alloc_utils_private.h" #include "rtrlib/lib/convert_byte_order_private.h" #include "rtrlib/lib/log_private.h" @@ -17,6 +18,9 @@ #include "rtrlib/rtr/rtr_private.h" #include "rtrlib/spki/hashtable/ht-spkitable_private.h" #include "rtrlib/transport/transport_private.h" +#ifdef RTRLIB_BGPSEC_ENABLED +#include "rtrlib/bgpsec/bgpsec_utils_private.h" +#endif #include #include diff --git a/rtrlib/rtr/rtr.c b/rtrlib/rtr/rtr.c index 086c9539..26452e26 100644 --- a/rtrlib/rtr/rtr.c +++ b/rtrlib/rtr/rtr.c @@ -20,6 +20,7 @@ #include #include #include +#include #include static void rtr_purge_outdated_records(struct rtr_socket *rtr_socket); diff --git a/rtrlib/rtr_mgr.c b/rtrlib/rtr_mgr.c index cfd1de69..f00a0a57 100644 --- a/rtrlib/rtr_mgr.c +++ b/rtrlib/rtr_mgr.c @@ -9,6 +9,7 @@ #include "rtr_mgr_private.h" +#include "rtrlib/config.h" #include "rtrlib/lib/alloc_utils_private.h" #include "rtrlib/lib/log_private.h" #include "rtrlib/pfx/pfx_private.h" @@ -16,6 +17,9 @@ #include "rtrlib/rtrlib_export_private.h" #include "rtrlib/spki/hashtable/ht-spkitable_private.h" #include "rtrlib/transport/transport_private.h" +#ifdef RTRLIB_BGPSEC_ENABLED +#include "rtrlib/bgpsec/bgpsec_private.h" +#endif #include #include @@ -673,3 +677,128 @@ RTRLIB_EXPORT inline void rtr_mgr_for_each_ipv6_record(struct rtr_mgr_config *co { pfx_table_for_each_ipv6_record(config->pfx_table, fp, data); } + +#ifdef RTRLIB_BGPSEC_ENABLED +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT int rtr_mgr_bgpsec_validate_as_path(const struct rtr_bgpsec *data, struct rtr_mgr_config *config) +{ + int retval = rtr_bgpsec_validate_as_path(data, config->spki_table); + + return retval; +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT int rtr_mgr_bgpsec_generate_signature(const struct rtr_bgpsec *data, uint8_t *private_key, + struct rtr_signature_seg **new_signature) +{ + int retval = rtr_bgpsec_generate_signature(data, private_key, new_signature); + + return retval; +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT int rtr_mgr_bgpsec_get_version(void) +{ + return rtr_bgpsec_get_version(); +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT int rtr_mgr_bgpsec_has_algorithm_suite(uint8_t alg_suite) +{ + return rtr_bgpsec_has_algorithm_suite(alg_suite); +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT int rtr_mgr_bgpsec_get_algorithm_suites(const uint8_t **algs_arr) +{ + return rtr_bgpsec_get_algorithm_suites(algs_arr); +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT void rtr_mgr_bgpsec_free_signatures(struct rtr_signature_seg *seg) +{ + rtr_bgpsec_free_signatures(seg); +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT struct rtr_secure_path_seg *rtr_mgr_bgpsec_new_secure_path_seg(uint8_t pcount, uint8_t flags, + uint32_t asn) +{ + return rtr_bgpsec_new_secure_path_seg(pcount, flags, asn); +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT void rtr_mgr_bgpsec_prepend_sec_path_seg(struct rtr_bgpsec *bgpsec, struct rtr_secure_path_seg *new_seg) +{ + rtr_bgpsec_prepend_sec_path_seg(bgpsec, new_seg); +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT struct rtr_signature_seg *rtr_mgr_bgpsec_new_signature_seg(uint8_t *ski, uint16_t sig_len, + uint8_t *signature) +{ + return rtr_bgpsec_new_signature_seg(ski, sig_len, signature); +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT int rtr_mgr_bgpsec_prepend_sig_seg(struct rtr_bgpsec *bgpsec, struct rtr_signature_seg *new_seg) +{ + return rtr_bgpsec_prepend_sig_seg(bgpsec, new_seg); +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT struct rtr_bgpsec *rtr_mgr_bgpsec_new(uint8_t alg, uint8_t safi, uint16_t afi, uint32_t my_as, + uint32_t target_as, struct rtr_bgpsec_nlri *nlri) +{ + return rtr_bgpsec_new(alg, safi, afi, my_as, target_as, nlri); +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT void rtr_mgr_bgpsec_free(struct rtr_bgpsec *bgpsec) +{ + rtr_bgpsec_free(bgpsec); +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT void rtr_mgr_free_secure_path(struct rtr_secure_path_seg *seg) +{ + rtr_bgpsec_free_secure_path(seg); +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT struct rtr_secure_path_seg *rtr_mgr_bgpsec_pop_secure_path_seg(struct rtr_bgpsec *bgpsec) +{ + return rtr_bgpsec_pop_secure_path_seg(bgpsec); +} + +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT struct rtr_signature_seg *rtr_mgr_bgpsec_pop_signature_seg(struct rtr_bgpsec *bgpsec) +{ + return rtr_bgpsec_pop_signature_seg(bgpsec); +} + +RTRLIB_EXPORT void rtr_mgr_bgpsec_append_sec_path_seg(struct rtr_bgpsec *bgpsec, struct rtr_secure_path_seg *new_seg) +{ + rtr_bgpsec_append_sec_path_seg(bgpsec, new_seg); +} + +RTRLIB_EXPORT int rtr_mgr_bgpsec_append_sig_seg(struct rtr_bgpsec *bgpsec, struct rtr_signature_seg *new_seg) +{ + return rtr_bgpsec_append_sig_seg(bgpsec, new_seg); +} + +RTRLIB_EXPORT struct rtr_bgpsec_nlri *rtr_mgr_bgpsec_nlri_new(int nlri_len) +{ + return rtr_bgpsec_nlri_new(nlri_len); +} + +RTRLIB_EXPORT void rtr_mgr_bgpsec_nlri_free(struct rtr_bgpsec_nlri *nlri) +{ + rtr_bgpsec_nlri_free(nlri); +} + +RTRLIB_EXPORT void rtr_mgr_bgpsec_add_spki_record(struct rtr_mgr_config *config, struct spki_record *record) +{ + rtr_bgpsec_add_spki_record(config->spki_table, record); +} +#endif diff --git a/rtrlib/rtr_mgr.h b/rtrlib/rtr_mgr.h index b267723d..512db863 100644 --- a/rtrlib/rtr_mgr.h +++ b/rtrlib/rtr_mgr.h @@ -33,8 +33,13 @@ #ifndef RTR_MGR #define RTR_MGR +#include "config.h" + #include "rtrlib/pfx/pfx.h" #include "rtrlib/spki/spkitable.h" +#ifdef RTRLIB_BGPSEC_ENABLED +#include "rtrlib/bgpsec/bgpsec.h" +#endif #include #include @@ -257,5 +262,160 @@ 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); +/* @} */ + +/** + * @defgroup mod_bgpsec_h BGPsec AS path validation + * @brief BGPsec allows for validation of the BGPsec_PATH attribute of a + * BGPsec update. + * @{ + */ +#ifdef RTRLIB_BGPSEC_ENABLED +/** + * @brief Validation function for AS path validation. + * @param[in] data Data required for AS path validation. See @ref rtr_bgpsec. + * @param[in] config The rtr_mgr_config containing a SPKI table. + * @return RTR_BGPSEC_VALID If the AS path was valid. + * @return RTR_BGPSEC_NOT_VALID If the AS path was not valid. + * @return RTR_BGPSEC_ERROR If an error occurred. Refer to error codes for + * more details. + */ +int rtr_mgr_bgpsec_validate_as_path(const struct rtr_bgpsec *data, struct rtr_mgr_config *config); + +/** + * @brief Signing function for a BGPsec_PATH. + * @param[in] data Data required for AS path validation. See @ref rtr_bgpsec. + * @param[in] private_key The raw bytes of the private key that is used for signing. + * @param[out] new_signature Contains the generated signature and its length if + * successful. Must not be allocated. + * @return RTR_BGPSEC_SUCCESS If the signature was successfully generated. + * @return RTR_BGPSEC_ERROR If an error occurred. Refer to error codes for + * more details. + */ +int rtr_mgr_bgpsec_generate_signature(const struct rtr_bgpsec *data, uint8_t *private_key, + struct rtr_signature_seg **new_signature); + +/** + * @brief Returns the highest supported BGPsec version. + * @return RTR_BGPSEC_VERSION The currently supported BGPsec version. + */ +int rtr_mgr_bgpsec_get_version(void); + +/** + * @brief Check, if an algorithm suite is supported by RTRlib. + * @param[in] alg_suite The algorithm suite that is to be checked. + * @return RTR_BGPSEC_SUCCESS If the algorithm suite is supported. + * @return RTR_BGPSEC_ERROR If the algorithm suite is not supported. + */ +int rtr_mgr_bgpsec_has_algorithm_suite(uint8_t alg_suite); + +/** + * @brief Returns pointer to a list that holds all supported algorithm suites. + * @param[out] algs_arr A char pointer that contains all supported suites. + * @return ALGORITHM_SUITES_COUNT The size of algs_arr + */ +int rtr_mgr_bgpsec_get_algorithm_suites(const uint8_t **algs_arr); + +/** + * @brief Free a signature and any signatures that are pointed to. + * @param[in] seg The signature that has been passed to the signing + * function. + */ +void rtr_mgr_bgpsec_free_signatures(struct rtr_signature_seg *seg); + +/** + * @brief Return an allocated and initialized Secure Path Segment. + * @param[in] pcount The pcount field. + * @param[in] flags The flags field. + * @param[in] asn The ASN of the segment. + * @return A pointer to an initialized rtr_secure_path_seg struct + */ +struct rtr_secure_path_seg *rtr_mgr_bgpsec_new_secure_path_seg(uint8_t pcount, uint8_t flags, uint32_t asn); + +/** + * @brief Prepend a given Secure Path Segment to @ref rtr_bgpsec.path. + * @param[in] bgpsec The rtr_bgpsec struct that holds the path. + * @param[in] new_seg The Secure Path Segment that is appended to the path. + */ +void rtr_mgr_bgpsec_prepend_sec_path_seg(struct rtr_bgpsec *bgpsec, struct rtr_secure_path_seg *new_seg); + +/** + * @brief Return an allocated and initialized Signature. + * @param[in] ski The Subject Key Identifier as byte representation. + * @param[in] sig_len The length of the signature. + * @param[in] signature The signature itself. + * @return A pointer to an initialized rtr_secure_path_seg struct. + * @ref rtr_signature_seg.signature is allocated with sig_len + * bytes. + */ +struct rtr_signature_seg *rtr_mgr_bgpsec_new_signature_seg(uint8_t *ski, uint16_t sig_len, uint8_t *signature); + +/** + * @brief Prepend a given Signature Segment to @ref rtr_bgpsec.sigs. All fields + * of the new_seg must be filled. + * @param[in] bgpsec The rtr_bgpsec struct that holds the signatures. + * @param[in] new_seg The Signature Segment that is appended to the signatures. + * @return RTR_BGPSEC_SUCCESS If the signature was successfully prepended. + * @return RTR_BGPSEC_ERROR If an error occurred during prepending, e.g. one + * or more fields of new_seg was missing. + */ +int rtr_mgr_bgpsec_prepend_sig_seg(struct rtr_bgpsec *bgpsec, struct rtr_signature_seg *new_seg); + +/** + * @brief Initializes and returns a pointer to a rtr_bgpsec struct. + * @param[in] alg The Algorithm Suite Identifier. + * @param[in] safi The Subsequent Address Family Identifier. + * @param[in] afi The Address Family Identifier. + * @param[in] my_as The AS that is currently performing validation (you). + * @param[in] target_as The AS where the update should be sent to. + * @param[in] nlri The Network Layer Reachability Information. + * @return A pointer to an initialized rtr_bgpsec struct. + */ +struct rtr_bgpsec *rtr_mgr_bgpsec_new(uint8_t alg, uint8_t safi, uint16_t afi, uint32_t my_as, uint32_t target_as, + struct rtr_bgpsec_nlri *nlri); + +/** + * @brief Free a rtr_bgpsec struct and any Secure Path and Signature + * Segments it holds. + * @param[in] bgpsec The rtr_bgpsec struct that is to be freed. + */ +void rtr_mgr_bgpsec_free(struct rtr_bgpsec *bgpsec); + +/** + * @brief Free a Secure Path Segment and any segments that are pointed to + * by @ref rtr_secure_path_seg.next. + * @param[in] seg The Secure Path Segment that is to be freed. + */ +void rtr_mgr_free_secure_path(struct rtr_secure_path_seg *seg); + +/** + * @brief Retrieve a pointer to the last appended Secure Path Segment + * from a bgpsec struct. + * @param[in] bgpsec The bgpsec struct that contains the Secure Path. + * @return *rtr_secure_path_seg If @ref rtr_bgpsec.path_len > 0. + * @return NULL If @ref rtr_bgpsec.path_len = 0. + */ +struct rtr_secure_path_seg *rtr_mgr_bgpsec_pop_secure_path_seg(struct rtr_bgpsec *bgpsec); + +/** + * @brief Retrieve a pointer to the last appended Signature Segment from + * a bgpsec struct. + * @param[in] bgpsec The bgpsec struct that contains the Signatures. + * @return *rtr_signature_seg If @ref rtr_bgpsec.sigs_len > 0. + * @return NULL if @ref rtr_bgpsec.sigs_len = 0. + */ +struct rtr_signature_seg *rtr_mgr_bgpsec_pop_signature_seg(struct rtr_bgpsec *bgpsec); + +void rtr_mgr_bgpsec_append_sec_path_seg(struct rtr_bgpsec *bgpsec, struct rtr_secure_path_seg *new_seg); + +int rtr_mgr_bgpsec_append_sig_seg(struct rtr_bgpsec *bgpsec, struct rtr_signature_seg *new_seg); + +struct rtr_bgpsec_nlri *rtr_mgr_bgpsec_nlri_new(int nlri_len); + +void rtr_mgr_bgpsec_nlri_free(struct rtr_bgpsec_nlri *nlri); + +void rtr_mgr_bgpsec_add_spki_record(struct rtr_mgr_config *config, struct spki_record *record); +#endif + #endif /** @} */ diff --git a/rtrlib/rtrlib.h.cmake b/rtrlib/rtrlib.h.cmake index de8f612f..5781050e 100644 --- a/rtrlib/rtrlib.h.cmake +++ b/rtrlib/rtrlib.h.cmake @@ -19,6 +19,7 @@ extern "C" { #define RTRLIB_VERSION_MINOR @RTRLIB_VERSION_MINOR@ #define RTRLIB_VERSION_PATCH @RTRLIB_VERSION_PATCH@ +#include "config.h" #include "lib/alloc_utils.h" #include "lib/ip.h" #include "lib/ipv4.h" @@ -32,6 +33,9 @@ extern "C" { #ifdef RTRLIB_HAVE_LIBSSH #include "rtrlib/transport/ssh/ssh_transport.h" #endif +#ifdef RTRLIB_BGPSEC_ENABLED +#include "rtrlib/bgpsec/bgpsec.h" +#endif #endif diff --git a/scripts/check-coding-style.sh b/scripts/check-coding-style.sh index 5f1e3c3c..9ef0a111 100755 --- a/scripts/check-coding-style.sh +++ b/scripts/check-coding-style.sh @@ -26,7 +26,7 @@ cd $SCRIPT_DIR/.. for i in $CHECKSOURCE; do echo "> check coding style of $i ..." IGNORE="PREFER_KERNEL_TYPES,CONST_STRUCT,OPEN_BRACE,SPDX_LICENSE_TAG,OPEN_ENDED_LINE,UNNECESSARY_PARENTHESES,PREFER_PRINTF,GLOBAL_INITIALISERS,PREFER_PACKED,BOOL_MEMBER,STATIC_CONST_CHAR_ARRAY,LONG_LINE_STRING" - if [[ $i == *"unittest"* ]]; then + if [[ $i == *"unittest"* ]] || [[ $i == *"bgpsec"* ]]; then IGNORE="${IGNORE},CAMELCASE" fi $SCRIPT_DIR/checkpatch.pl -f --strict --no-tree --terse --show-types \ From 8d95a54a8bf3a084efb6e27246192f7debdce1cd Mon Sep 17 00:00:00 2001 From: Colin Sames Date: Thu, 14 Oct 2021 17:56:46 +0200 Subject: [PATCH 03/55] tests: Add integration and unit tests for BGPsec. The integration tests cover: - originating a BGPsec path - signing a BGPsec path - validating a BGPsec path - getting BGPsec version and algorithm suite values The unit tests cover: - allocator and initializer functions - utility functions --- tests/CMakeLists.txt | 5 + tests/test_bgpsec.c | 416 ++++++++++++++++++++++ tests/unittests/CMakeLists.txt | 10 + tests/unittests/test_bgpsec_signing.c | 286 +++++++++++++++ tests/unittests/test_bgpsec_signing.h | 33 ++ tests/unittests/test_bgpsec_utils.c | 435 +++++++++++++++++++++++ tests/unittests/test_bgpsec_utils.h | 20 ++ tests/unittests/test_bgpsec_validation.c | 248 +++++++++++++ tests/unittests/test_bgpsec_validation.h | 33 ++ 9 files changed, 1486 insertions(+) create mode 100644 tests/test_bgpsec.c create mode 100644 tests/unittests/test_bgpsec_signing.c create mode 100644 tests/unittests/test_bgpsec_signing.h create mode 100644 tests/unittests/test_bgpsec_utils.c create mode 100644 tests/unittests/test_bgpsec_utils.h create mode 100644 tests/unittests/test_bgpsec_validation.c create mode 100644 tests/unittests/test_bgpsec_validation.h diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d4757dae..81bd9f55 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -27,6 +27,11 @@ add_coverage(test_getbits) add_executable(test_dynamic_groups test_dynamic_groups.c) target_link_libraries(test_dynamic_groups rtrlib_static) add_coverage(test_dynamic_groups) +if(RTRLIB_BGPSEC_ENABLED) + add_executable(test_bgpsec test_bgpsec.c) + target_link_libraries(test_bgpsec rtrlib_static) + add_coverage(test_bgpsec) +endif(RTRLIB_BGPSEC_ENABLED) if(UNIT_TESTING AND NOT APPLE) diff --git a/tests/test_bgpsec.c b/tests/test_bgpsec.c new file mode 100644 index 00000000..9cd56e73 --- /dev/null +++ b/tests/test_bgpsec.c @@ -0,0 +1,416 @@ +#include "rtrlib/bgpsec/bgpsec.h" +#include "rtrlib/bgpsec/bgpsec_private.h" +#include "rtrlib/bgpsec/bgpsec_utils_private.h" +#include "rtrlib/rtr_mgr.h" +#include "rtrlib/rtrlib.h" +#include "rtrlib/spki/hashtable/ht-spkitable_private.h" + +#include +#include +#include +#include +#include + +/* Below are the SKIs, signatures, public keys and the private key that + * are required to build a valid BGPsec path of length two. They all are + * in binary form. + */ +static uint8_t ski1[] = {0x47, 0xF2, 0x3B, 0xF1, 0xAB, 0x2F, 0x8A, 0x9D, 0x26, 0x86, + 0x4E, 0xBB, 0xD8, 0xDF, 0x27, 0x11, 0xC7, 0x44, 0x06, 0xEC}; + +static uint8_t sig1[] = {0x30, 0x46, 0x02, 0x21, 0x00, 0xEF, 0xD4, 0x8B, 0x2A, 0xAC, 0xB6, 0xA8, 0xFD, 0x11, 0x40, + 0xDD, 0x9C, 0xD4, 0x5E, 0x81, 0xD6, 0x9D, 0x2C, 0x87, 0x7B, 0x56, 0xAA, 0xF9, 0x91, 0xC3, + 0x4D, 0x0E, 0xA8, 0x4E, 0xAF, 0x37, 0x16, 0x02, 0x21, 0x00, 0x90, 0xF2, 0xC1, 0x29, 0xAB, + 0xB2, 0xF3, 0x9B, 0x6A, 0x07, 0x96, 0x3B, 0xD5, 0x55, 0xA8, 0x7A, 0xB2, 0xB7, 0x33, 0x3B, + 0x7B, 0x91, 0xF1, 0x66, 0x8F, 0xD8, 0x61, 0x8C, 0x83, 0xFA, 0xC3, 0xF1}; + +static uint8_t spki1[] = {0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, + 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, + 0x04, 0x28, 0xFC, 0x5F, 0xE9, 0xAF, 0xCF, 0x5F, 0x4C, 0xAB, 0x3F, 0x5F, 0x85, + 0xCB, 0x21, 0x2F, 0xC1, 0xE9, 0xD0, 0xE0, 0xDB, 0xEA, 0xEE, 0x42, 0x5B, 0xD2, + 0xF0, 0xD3, 0x17, 0x5A, 0xA0, 0xE9, 0x89, 0xEA, 0x9B, 0x60, 0x3E, 0x38, 0xF3, + 0x5F, 0xB3, 0x29, 0xDF, 0x49, 0x56, 0x41, 0xF2, 0xBA, 0x04, 0x0F, 0x1C, 0x3A, + 0xC6, 0x13, 0x83, 0x07, 0xF2, 0x57, 0xCB, 0xA6, 0xB8, 0xB5, 0x88, 0xF4, 0x1F}; + +static uint8_t ski2[] = {0xAB, 0x4D, 0x91, 0x0F, 0x55, 0xCA, 0xE7, 0x1A, 0x21, 0x5E, + 0xF3, 0xCA, 0xFE, 0x3A, 0xCC, 0x45, 0xB5, 0xEE, 0xC1, 0x54}; + +static uint8_t sig2[] = {0x30, 0x46, 0x02, 0x21, 0x00, 0xEF, 0xD4, 0x8B, 0x2A, 0xAC, 0xB6, 0xA8, 0xFD, 0x11, 0x40, + 0xDD, 0x9C, 0xD4, 0x5E, 0x81, 0xD6, 0x9D, 0x2C, 0x87, 0x7B, 0x56, 0xAA, 0xF9, 0x91, 0xC3, + 0x4D, 0x0E, 0xA8, 0x4E, 0xAF, 0x37, 0x16, 0x02, 0x21, 0x00, 0x8E, 0x21, 0xF6, 0x0E, 0x44, + 0xC6, 0x06, 0x6C, 0x8B, 0x8A, 0x95, 0xA3, 0xC0, 0x9D, 0x3A, 0xD4, 0x37, 0x95, 0x85, 0xA2, + 0xD7, 0x28, 0xEE, 0xAD, 0x07, 0xA1, 0x7E, 0xD7, 0xAA, 0x05, 0x5E, 0xCA}; + +static uint8_t spki2[] = {0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, + 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, + 0x04, 0x73, 0x91, 0xBA, 0xBB, 0x92, 0xA0, 0xCB, 0x3B, 0xE1, 0x0E, 0x59, 0xB1, + 0x9E, 0xBF, 0xFB, 0x21, 0x4E, 0x04, 0xA9, 0x1E, 0x0C, 0xBA, 0x1B, 0x13, 0x9A, + 0x7D, 0x38, 0xD9, 0x0F, 0x77, 0xE5, 0x5A, 0xA0, 0x5B, 0x8E, 0x69, 0x56, 0x78, + 0xE0, 0xFA, 0x16, 0x90, 0x4B, 0x55, 0xD9, 0xD4, 0xF5, 0xC0, 0xDF, 0xC5, 0x88, + 0x95, 0xEE, 0x50, 0xBC, 0x4F, 0x75, 0xD2, 0x05, 0xA2, 0x5B, 0xD3, 0x6F, 0xF5}; + +static uint8_t private_key[] = {0x30, 0x77, 0x02, 0x01, 0x01, 0x04, 0x20, 0xD8, 0xAA, 0x4D, 0xFB, 0xE2, 0x47, 0x8F, + 0x86, 0xE8, 0x8A, 0x74, 0x51, 0xBF, 0x07, 0x55, 0x65, 0x70, 0x9C, 0x57, 0x5A, 0xC1, + 0xC1, 0x36, 0xD0, 0x81, 0xC5, 0x40, 0x25, 0x4C, 0xA4, 0x40, 0xB9, 0xA0, 0x0A, 0x06, + 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0xA1, 0x44, 0x03, 0x42, 0x00, + 0x04, 0x73, 0x91, 0xBA, 0xBB, 0x92, 0xA0, 0xCB, 0x3B, 0xE1, 0x0E, 0x59, 0xB1, 0x9E, + 0xBF, 0xFB, 0x21, 0x4E, 0x04, 0xA9, 0x1E, 0x0C, 0xBA, 0x1B, 0x13, 0x9A, 0x7D, 0x38, + 0xD9, 0x0F, 0x77, 0xE5, 0x5A, 0xA0, 0x5B, 0x8E, 0x69, 0x56, 0x78, 0xE0, 0xFA, 0x16, + 0x90, 0x4B, 0x55, 0xD9, 0xD4, 0xF5, 0xC0, 0xDF, 0xC5, 0x88, 0x95, 0xEE, 0x50, 0xBC, + 0x4F, 0x75, 0xD2, 0x05, 0xA2, 0x5B, 0xD3, 0x6F, 0xF5}; + +/* To test potential error sources, a wrong signature, public key and + * private key are used. RTRlib should respond with appropriate error codes. + */ +static uint8_t wrong_sig[] = {0x30, 0x46, 0x02, 0x21, 0x00, 0xEF, 0xD4, 0x8B, 0x2A, 0xAC, 0xB6, 0xA8, 0xFD, 0x11, 0x40, + 0xDD, 0x9C, 0xD4, 0x5E, 0x81, 0xD6, 0x9D, 0x2C, 0x87, 0x7B, 0x56, 0xAA, 0xF9, 0x91, 0xC3, + 0x4D, 0x0E, 0xA8, 0x4E, 0xAF, 0x37, 0x16, 0x02, 0x21, 0x00, 0x8E, 0x21, 0xF6, 0x0E, 0x44, + 0xC6, 0x06, 0x6C, 0x8B, 0x8A, 0x95, 0xA3, 0xC0, 0x9D, 0x3A, 0xD4, 0x37, 0x95, 0x85, 0xA2, + 0xD7, 0x28, 0xEE, 0xAD, 0x07, 0xA1, 0x7E, 0xD7, 0xAA, 0x05, 0x5E, 0xCB}; + +static uint8_t wrong_private_key[] = { + 0x30, 0x77, 0x02, 0x01, 0x01, 0x04, 0x20, 0xD8, 0xAA, 0x4D, 0xFB, 0xE2, 0x47, 0x8F, 0x86, 0xE8, 0x8A, 0x74, + 0x51, 0xBF, 0x07, 0x55, 0x65, 0x70, 0x9C, 0x57, 0x5A, 0xC1, 0xC1, 0x36, 0xD0, 0x81, 0xC5, 0x40, 0x25, 0x4C, + 0xA4, 0x40, 0xBA, 0xA0, 0x0A, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0xA1, 0x44, 0x03, + 0x42, 0x00, 0x04, 0x73, 0x91, 0xBA, 0xBB, 0x92, 0xA0, 0xCB, 0x3B, 0xE1, 0x0E, 0x59, 0xB1, 0x9E, 0xBF, 0xFB, + 0x21, 0x4E, 0x04, 0xA9, 0x1E, 0x0C, 0xBA, 0x1B, 0x13, 0x9A, 0x7D, 0x38, 0xD9, 0x0F, 0x77, 0xE5, 0x5A, 0xA0, + 0x5B, 0x8E, 0x69, 0x56, 0x78, 0xE0, 0xFA, 0x16, 0x90, 0x4B, 0x55, 0xD9, 0xD4, 0xF5, 0xC0, 0xDF, 0xC5, 0x88, + 0x95, 0xEE, 0x50, 0xBC, 0x4F, 0x75, 0xD2, 0x05, 0xA2, 0x5B, 0xD3, 0x6F, 0xF5}; + +/* Helper function to create a spki_record */ +static struct spki_record *create_record(int ASN, uint8_t *ski, uint8_t *spki) +{ + struct spki_record *record = malloc(sizeof(struct spki_record)); + + memset(record, 0, sizeof(*record)); + record->asn = ASN; + memcpy(record->ski, ski, SKI_SIZE); + memcpy(record->spki, spki, SPKI_SIZE); + record->socket = NULL; + return record; +} + +/* Test function to validate a BGPsec path. Multiple scenarios are tested + * regarding validation. + */ +static void validate_bgpsec_path_test(void) +{ + struct rtr_bgpsec *bgpsec = NULL; + struct rtr_bgpsec_nlri *pfx = NULL; + int pfx_int = 0; + + struct spki_table table; + struct spki_record *record1; + struct spki_record *record2; + struct spki_record *duplicate_record; + + enum rtr_bgpsec_rtvals result; + + struct rtr_signature_seg *ss = NULL; + struct rtr_secure_path_seg *sps = NULL; + + uint8_t alg = 1; + uint8_t safi = 1; + uint16_t afi = 1; + uint32_t my_as = 65537; + + pfx = rtr_mgr_bgpsec_nlri_new(3); + pfx->nlri_len = 24; + pfx->afi = 1; /* LRTR_IPV4 */ + pfx_int = htonl(3221225984); /* 192.0.2.0 */ + + memcpy(pfx->nlri, &pfx_int, 3); + + bgpsec = rtr_mgr_bgpsec_new(alg, safi, afi, my_as, my_as, pfx); + + /* init the rtr_signature_seg and rtr_secure_path_seg structs. */ + + uint8_t pcount = 1; + uint8_t flags = 0; + uint32_t asn = 64496; + + sps = rtr_mgr_bgpsec_new_secure_path_seg(pcount, flags, asn); + rtr_mgr_bgpsec_prepend_sec_path_seg(bgpsec, sps); + + asn = 65536; + sps = rtr_mgr_bgpsec_new_secure_path_seg(pcount, flags, asn); + rtr_mgr_bgpsec_prepend_sec_path_seg(bgpsec, sps); + + uint16_t sig_len = 72; + + ss = rtr_mgr_bgpsec_new_signature_seg(ski2, sig_len, sig2); + result = rtr_mgr_bgpsec_prepend_sig_seg(bgpsec, ss); + assert(result == RTR_BGPSEC_SUCCESS); + + ss = rtr_mgr_bgpsec_new_signature_seg(ski1, sig_len, sig1); + result = rtr_mgr_bgpsec_prepend_sig_seg(bgpsec, ss); + assert(result == RTR_BGPSEC_SUCCESS); + + /* init the SPKI table and store two valid router keys and one duplicate + * key in it. + */ + spki_table_init(&table, NULL); + record1 = create_record(65536, ski1, spki1); + record2 = create_record(64496, ski2, spki2); + duplicate_record = create_record(64497, ski2, spki1); + + /* Pass all data to the validation function. The result is either + * RTR_BGPSEC_VALID or RTR_BGPSEC_NOT_VALID. + * Test with 2 AS hops. + * (table = record1, record2) + */ + spki_table_add_entry(&table, record1); + spki_table_add_entry(&table, record2); + result = rtr_bgpsec_validate_as_path(bgpsec, &table); + + assert(result == RTR_BGPSEC_VALID); + + /* Pass a wrong signature. + * (table = record1, record2) + */ + memcpy(bgpsec->sigs->next->signature, wrong_sig, sizeof(wrong_sig)); + + result = rtr_bgpsec_validate_as_path(bgpsec, &table); + + assert(result == RTR_BGPSEC_NOT_VALID); + + memcpy(bgpsec->sigs->next->signature, sig2, sizeof(sig2)); + + /* Public key not in SPKI table + * (table = record2) + */ + spki_table_remove_entry(&table, record1); + + result = rtr_bgpsec_validate_as_path(bgpsec, &table); + + assert(result == RTR_BGPSEC_ROUTER_KEY_NOT_FOUND); + + /* What if there are mulitple SPKI entries for a SKI in the SPKI table. + * (table = record1, record2, duplicate_record) + */ + spki_table_add_entry(&table, duplicate_record); + spki_table_add_entry(&table, record1); + + result = rtr_bgpsec_validate_as_path(bgpsec, &table); + + assert(result == RTR_BGPSEC_VALID); + + /* Pass an unsupported algorithm suite. */ + bgpsec->alg = 2; + + result = rtr_bgpsec_validate_as_path(bgpsec, &table); + + assert(result == RTR_BGPSEC_UNSUPPORTED_ALGORITHM_SUITE); + + /* Free all allocated memory. */ + spki_table_free(&table); + free(record1); + free(record2); + free(duplicate_record); + rtr_mgr_bgpsec_free(bgpsec); +} + +/* Test function for generating signatures. Since signing does not depend + * on the SPKI table, all error sources regarding public keys can be + * disregarded. + */ +static void generate_signature_test(void) +{ + /* AS(64496)--->AS(65536)--->AS(65537) */ + struct rtr_bgpsec *bgpsec = NULL; + struct rtr_bgpsec_nlri *pfx = NULL; + int pfx_int = 0; + + struct spki_table table; + struct spki_record *record1; + struct spki_record *record2; + + struct rtr_signature_seg *new_sig = NULL; + struct rtr_secure_path_seg *new_sec = NULL; + + enum rtr_bgpsec_rtvals result; + + struct rtr_signature_seg *ss = NULL; + struct rtr_secure_path_seg *sps = NULL; + + uint8_t alg = 1; + uint8_t safi = 1; + uint16_t afi = 1; + uint32_t my_as = 65537; + uint32_t target_as = 65538; + + pfx = rtr_mgr_bgpsec_nlri_new(3); + pfx->nlri_len = 24; + pfx->afi = 1; /* LRTR_IPV4 */ + pfx_int = htonl(3221225984); /* 192.0.2.0 */ + + memcpy(pfx->nlri, &pfx_int, 3); + + bgpsec = rtr_mgr_bgpsec_new(alg, safi, afi, my_as, target_as, pfx); + + /* init the rtr_signature_seg and rtr_secure_path_seg structs. */ + + uint8_t pcount = 1; + uint8_t flags = 0; + uint32_t asn = 64496; + + sps = rtr_mgr_bgpsec_new_secure_path_seg(pcount, flags, asn); + rtr_mgr_bgpsec_prepend_sec_path_seg(bgpsec, sps); + + asn = 65536; + sps = rtr_mgr_bgpsec_new_secure_path_seg(pcount, flags, asn); + rtr_mgr_bgpsec_prepend_sec_path_seg(bgpsec, sps); + + uint16_t sig_len = 72; + + ss = rtr_mgr_bgpsec_new_signature_seg(ski2, sig_len, sig2); + result = rtr_mgr_bgpsec_prepend_sig_seg(bgpsec, ss); + assert(result == RTR_BGPSEC_SUCCESS); + + ss = rtr_mgr_bgpsec_new_signature_seg(ski1, sig_len, sig1); + result = rtr_mgr_bgpsec_prepend_sig_seg(bgpsec, ss); + assert(result == RTR_BGPSEC_SUCCESS); + + new_sec = rtr_mgr_bgpsec_new_secure_path_seg(pcount, flags, my_as); + rtr_mgr_bgpsec_prepend_sec_path_seg(bgpsec, new_sec); + + /* init the SPKI table and store two router keys in it. */ + spki_table_init(&table, NULL); + record2 = create_record(65536, ski1, spki1); + record1 = create_record(64496, ski2, spki2); + + spki_table_add_entry(&table, record1); + spki_table_add_entry(&table, record2); + + /* Pass all data to the validation function. The result is either + * RTR_BGPSEC_VALID or RTR_BGPSEC_NOT_VALID. + * Test with 1 AS hop. + */ + + result = rtr_bgpsec_generate_signature(bgpsec, private_key, &new_sig); + + assert(new_sig->sig_len > 0); + + rtr_mgr_bgpsec_free_signatures(new_sig); + + new_sig = NULL; + + result = rtr_bgpsec_generate_signature(bgpsec, wrong_private_key, &new_sig); + + assert(result == RTR_BGPSEC_LOAD_PRIV_KEY_ERROR); + + assert(!new_sig); + + /* Free all allocated memory. */ + spki_table_free(&table); + free(record1); + free(record2); + rtr_mgr_bgpsec_free(bgpsec); + rtr_mgr_bgpsec_free_signatures(new_sig); +} + +/* Another test for creating a signature. This time, there are no prior + * BGPsec path elements that need to be signed. + */ +static void originate_and_validate_test(void) +{ + /* AS(64496)--->AS(65536)--->AS(65537) */ + struct rtr_bgpsec *bgpsec = NULL; + struct rtr_bgpsec_nlri *pfx = NULL; + int pfx_int = 0; + + struct spki_table table; + struct spki_record *record1; + struct spki_record *record2; + + struct rtr_signature_seg *new_sig = NULL; + struct rtr_secure_path_seg *new_sec = NULL; + + enum rtr_bgpsec_rtvals result; + + uint8_t alg = 1; + uint8_t safi = 1; + uint16_t afi = 1; + uint32_t my_as = 64496; + uint32_t target_as = 65536; + + pfx = rtr_mgr_bgpsec_nlri_new(3); + pfx->nlri_len = 24; + pfx->afi = 1; /* LRTR_IPV4 */ + pfx_int = htonl(3221225984); /* 192.0.2.0 */ + + memcpy(pfx->nlri, &pfx_int, 3); + + bgpsec = rtr_mgr_bgpsec_new(alg, safi, afi, my_as, target_as, pfx); + + /* init the rtr_signature_seg and rtr_secure_path_seg structs. */ + + uint8_t pcount = 1; + uint8_t flags = 0; + + new_sec = rtr_mgr_bgpsec_new_secure_path_seg(pcount, flags, my_as); + rtr_mgr_bgpsec_prepend_sec_path_seg(bgpsec, new_sec); + + /* init the SPKI table and store two router keys in it. */ + spki_table_init(&table, NULL); + record2 = create_record(65536, ski1, spki1); + record1 = create_record(64496, ski2, spki2); + + spki_table_add_entry(&table, record1); + spki_table_add_entry(&table, record2); + + /* Generate a signature... */ + result = rtr_bgpsec_generate_signature(bgpsec, private_key, &new_sig); + assert(new_sig->sig_len > 0); + + /* ... copy the SKI to the new_sig struct... */ + memcpy(new_sig->ski, ski2, SKI_SIZE); + + /* ... prepend the new signature to bgpsec... */ + result = rtr_mgr_bgpsec_prepend_sig_seg(bgpsec, new_sig); + assert(result == RTR_BGPSEC_SUCCESS); + + /* ... and validate the freshly generated signature. */ + result = rtr_bgpsec_validate_as_path(bgpsec, &table); + assert(result == RTR_BGPSEC_VALID); + + /* Free all allocated memory. */ + spki_table_free(&table); + free(record1); + free(record2); + rtr_mgr_bgpsec_free(bgpsec); +} + +/* Test function for version and algorithm suites. Basic tests to + * cover the rest of the public API. + */ +static void bgpsec_version_and_algorithms_test(void) +{ + /* BGPsec version tests */ + assert(rtr_bgpsec_get_version() == 0); + + assert(rtr_bgpsec_get_version() != 1); + + /* BGPsec algorithm suite tests */ + assert(rtr_bgpsec_has_algorithm_suite(1) == RTR_BGPSEC_SUCCESS); + + assert(rtr_bgpsec_has_algorithm_suite(2) == RTR_BGPSEC_ERROR); + + /* BGPsec algorithm suites array test */ + const uint8_t *suites = NULL; + unsigned int suites_len = rtr_bgpsec_get_algorithm_suites(&suites); + + assert(suites_len == 1); + for (unsigned int i = 0; i < suites_len; i++) + assert(suites[i] == 1); +} + +int main(void) +{ + validate_bgpsec_path_test(); + generate_signature_test(); + originate_and_validate_test(); + bgpsec_version_and_algorithms_test(); + printf("Test Sucessful!\n"); + return EXIT_SUCCESS; +} diff --git a/tests/unittests/CMakeLists.txt b/tests/unittests/CMakeLists.txt index 29151dd7..827901e0 100644 --- a/tests/unittests/CMakeLists.txt +++ b/tests/unittests/CMakeLists.txt @@ -5,3 +5,13 @@ wrap_functions(test_packets_static lrtr_get_monotonic_time tr_send_all) add_rtr_unit_test(test_packets test_packets.c rtrlib_static cmocka) wrap_functions(test_packets rtr_change_socket_state tr_send_all) + +if(RTRLIB_BGPSEC_ENABLED) + add_rtr_unit_test(test_bgpsec_utils test_bgpsec_utils.c rtrlib_static cmocka) + + add_rtr_unit_test(test_bgpsec_validation test_bgpsec_validation.c rtrlib_static cmocka) + wrap_functions(test_bgpsec_validation check_router_keys req_stream_size align_byte_sequence hash_byte_sequence validate_signature spki_table_search_by_ski) + + add_rtr_unit_test(test_bgpsec_signing test_bgpsec_signing.c rtrlib_static cmocka) + wrap_functions(test_bgpsec_signing load_private_key req_stream_size align_byte_sequence hash_byte_sequence ECDSA_size sign_byte_sequence) +endif(RTRLIB_BGPSEC_ENABLED) diff --git a/tests/unittests/test_bgpsec_signing.c b/tests/unittests/test_bgpsec_signing.c new file mode 100644 index 00000000..664d7010 --- /dev/null +++ b/tests/unittests/test_bgpsec_signing.c @@ -0,0 +1,286 @@ +/* + * 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 "rtrlib_unittests.h" +#include "test_bgpsec_signing.h" + +#include "rtrlib/bgpsec/bgpsec.c" + +#include + +struct rtr_bgpsec *setup_bgpsec(void) +{ + struct rtr_bgpsec *bgpsec = NULL; + uint8_t alg = 1; + uint8_t safi = 1; + uint16_t afi = 1; + uint32_t my_as = 65537; + uint32_t target_as = 65538; + struct rtr_bgpsec_nlri *pfx = NULL; + int pfx_int = 0; + + pfx = rtr_bgpsec_nlri_new(3); + pfx->nlri_len = 24; + pfx->afi = 1; /* LRTR_IPV4 */ + pfx_int = htonl(3221225984); /* 192.0.2.0 */ + + memcpy(pfx->nlri, &pfx_int, 3); + + bgpsec = rtr_bgpsec_new(alg, safi, afi, my_as, target_as, pfx); + bgpsec->path = lrtr_malloc(sizeof(struct rtr_secure_path_seg)); + bgpsec->sigs = lrtr_malloc(sizeof(struct rtr_signature_seg)); + bgpsec->path->next = NULL; + bgpsec->sigs->next = NULL; + bgpsec->sigs->sig_len = 0; + + return bgpsec; +} + +int __wrap_load_private_key(EC_KEY **priv_key, uint8_t *bytes_key) +{ + UNUSED(priv_key); + UNUSED(bytes_key); + return (int)mock(); +} + +int __wrap_ECDSA_size(const EC_KEY *key) +{ + UNUSED(key); + return (int)mock(); +} + +int __wrap_align_byte_sequence(const struct rtr_bgpsec *data, struct stream *s, enum align_type type) +{ + UNUSED(data); + UNUSED(s); + UNUSED(type); + return (int)mock(); +} + +unsigned int __wrap_req_stream_size(const struct rtr_bgpsec *data, enum align_type type) +{ + UNUSED(data); + UNUSED(type); + return (int)mock(); +} + +int __wrap_hash_byte_sequence(uint8_t *bytes, unsigned int bytes_len, uint8_t alg_suite_id, unsigned char **hash_result) +{ + UNUSED(bytes); + UNUSED(bytes_len); + UNUSED(alg_suite_id); + UNUSED(hash_result); + return (int)mock(); +} + +int __wrap_sign_byte_sequence(uint8_t *hash_result, EC_KEY *priv_key, uint8_t alg, + struct rtr_signature_seg *new_signature) +{ + UNUSED(hash_result); + UNUSED(priv_key); + UNUSED(alg); + UNUSED(new_signature); + return (int)mock(); +} + +static void test_sanity_checks(void **state) +{ + struct rtr_bgpsec *bgpsec = setup_bgpsec(); + uint8_t *private_key = lrtr_malloc(20); + struct rtr_signature_seg *new_signature = NULL; + enum rtr_bgpsec_rtvals result = RTR_BGPSEC_SUCCESS; + struct rtr_signature_seg *not_empty = lrtr_malloc(sizeof(struct rtr_signature_seg)); + + UNUSED(state); + + bgpsec->path_len = 2; + bgpsec->sigs_len = 1; + + result = rtr_bgpsec_generate_signature(NULL, private_key, &new_signature); + assert_int_equal(RTR_BGPSEC_INVALID_ARGUMENTS, result); + + result = rtr_bgpsec_generate_signature(bgpsec, NULL, &new_signature); + assert_int_equal(RTR_BGPSEC_INVALID_ARGUMENTS, result); + + result = rtr_bgpsec_generate_signature(NULL, NULL, &new_signature); + assert_int_equal(RTR_BGPSEC_INVALID_ARGUMENTS, result); + + bgpsec->path_len = 1; + bgpsec->sigs_len = 1; + + result = rtr_bgpsec_generate_signature(bgpsec, private_key, &new_signature); + assert_int_equal(RTR_BGPSEC_WRONG_SEGMENT_COUNT, result); + + bgpsec->path_len = 2; + bgpsec->sigs_len = 1; + + result = rtr_bgpsec_generate_signature(bgpsec, private_key, ¬_empty); + assert_int_equal(RTR_BGPSEC_INVALID_ARGUMENTS, result); + + lrtr_free(bgpsec->path); + lrtr_free(bgpsec->sigs); + bgpsec->path = NULL; + + result = rtr_bgpsec_generate_signature(bgpsec, private_key, &new_signature); + assert_int_equal(RTR_BGPSEC_INVALID_ARGUMENTS, result); + + lrtr_free(private_key); + lrtr_free(bgpsec->nlri->nlri); + lrtr_free(bgpsec->nlri); + lrtr_free(bgpsec); + lrtr_free(not_empty); + rtr_bgpsec_free_signatures(new_signature); +} + +static void test_load_private_key(void **state) +{ + struct rtr_bgpsec *bgpsec = setup_bgpsec(); + uint8_t *private_key = lrtr_malloc(20); + struct rtr_signature_seg *new_signature = NULL; + enum rtr_bgpsec_rtvals result = RTR_BGPSEC_SUCCESS; + + UNUSED(state); + + bgpsec->path_len = 2; + bgpsec->sigs_len = 1; + + will_return(__wrap_load_private_key, RTR_BGPSEC_LOAD_PRIV_KEY_ERROR); + result = rtr_bgpsec_generate_signature(bgpsec, private_key, &new_signature); + assert_int_equal(RTR_BGPSEC_LOAD_PRIV_KEY_ERROR, result); + + lrtr_free(private_key); + lrtr_free(bgpsec->path); + lrtr_free(bgpsec->sigs); + lrtr_free(bgpsec->nlri->nlri); + lrtr_free(bgpsec->nlri); + lrtr_free(bgpsec); + rtr_bgpsec_free_signatures(new_signature); +} + +static void test_ecdsa_size(void **state) +{ + struct rtr_bgpsec *bgpsec = setup_bgpsec(); + uint8_t *private_key = lrtr_malloc(20); + struct rtr_signature_seg *new_signature = NULL; + enum rtr_bgpsec_rtvals result = RTR_BGPSEC_SUCCESS; + + UNUSED(state); + + bgpsec->path_len = 2; + bgpsec->sigs_len = 1; + + will_return(__wrap_ECDSA_size, 0); + will_return(__wrap_load_private_key, RTR_BGPSEC_SUCCESS); + result = rtr_bgpsec_generate_signature(bgpsec, private_key, &new_signature); + assert_int_equal(RTR_BGPSEC_LOAD_PRIV_KEY_ERROR, result); + + lrtr_free(private_key); + lrtr_free(bgpsec->path); + lrtr_free(bgpsec->sigs); + lrtr_free(bgpsec->nlri->nlri); + lrtr_free(bgpsec->nlri); + lrtr_free(bgpsec); + rtr_bgpsec_free_signatures(new_signature); +} + +static void test_align_byte_sequence(void **state) +{ + struct rtr_bgpsec *bgpsec = setup_bgpsec(); + uint8_t *private_key = lrtr_malloc(20); + struct rtr_signature_seg *new_signature = NULL; + enum rtr_bgpsec_rtvals result = RTR_BGPSEC_SUCCESS; + + UNUSED(state); + + bgpsec->path_len = 2; + bgpsec->sigs_len = 1; + + will_return(__wrap_align_byte_sequence, RTR_BGPSEC_ERROR); + will_return(__wrap_req_stream_size, 20); + will_return(__wrap_ECDSA_size, 10); + will_return(__wrap_load_private_key, RTR_BGPSEC_SUCCESS); + result = rtr_bgpsec_generate_signature(bgpsec, private_key, &new_signature); + assert_int_equal(RTR_BGPSEC_ERROR, result); + + lrtr_free(private_key); + lrtr_free(bgpsec->path); + lrtr_free(bgpsec->sigs); + lrtr_free(bgpsec->nlri->nlri); + lrtr_free(bgpsec->nlri); + lrtr_free(bgpsec); + rtr_bgpsec_free_signatures(new_signature); +} + +static void test_hash_byte_sequence(void **state) +{ + struct rtr_bgpsec *bgpsec = setup_bgpsec(); + uint8_t *private_key = lrtr_malloc(20); + struct rtr_signature_seg *new_signature = NULL; + enum rtr_bgpsec_rtvals result = RTR_BGPSEC_SUCCESS; + + UNUSED(state); + + bgpsec->path_len = 2; + bgpsec->sigs_len = 1; + + will_return(__wrap_hash_byte_sequence, RTR_BGPSEC_ERROR); + will_return(__wrap_align_byte_sequence, RTR_BGPSEC_SUCCESS); + will_return(__wrap_req_stream_size, 20); + will_return(__wrap_ECDSA_size, 10); + will_return(__wrap_load_private_key, RTR_BGPSEC_SUCCESS); + result = rtr_bgpsec_generate_signature(bgpsec, private_key, &new_signature); + assert_int_equal(RTR_BGPSEC_ERROR, result); + + lrtr_free(private_key); + lrtr_free(bgpsec->path); + lrtr_free(bgpsec->sigs); + lrtr_free(bgpsec->nlri->nlri); + lrtr_free(bgpsec->nlri); + lrtr_free(bgpsec); + rtr_bgpsec_free_signatures(new_signature); +} + +static void test_sign_byte_sequence(void **state) +{ + struct rtr_bgpsec *bgpsec = setup_bgpsec(); + uint8_t *private_key = lrtr_malloc(20); + struct rtr_signature_seg *new_signature = NULL; + enum rtr_bgpsec_rtvals result = RTR_BGPSEC_SUCCESS; + + UNUSED(state); + + bgpsec->path_len = 2; + bgpsec->sigs_len = 1; + + will_return(__wrap_sign_byte_sequence, RTR_BGPSEC_SIGNING_ERROR); + will_return(__wrap_hash_byte_sequence, RTR_BGPSEC_SUCCESS); + will_return(__wrap_align_byte_sequence, RTR_BGPSEC_SUCCESS); + will_return(__wrap_req_stream_size, 20); + will_return(__wrap_ECDSA_size, 10); + will_return(__wrap_load_private_key, RTR_BGPSEC_SUCCESS); + result = rtr_bgpsec_generate_signature(bgpsec, private_key, &new_signature); + assert_int_equal(RTR_BGPSEC_SIGNING_ERROR, result); + + lrtr_free(private_key); + lrtr_free(bgpsec->path); + lrtr_free(bgpsec->sigs); + lrtr_free(bgpsec->nlri->nlri); + lrtr_free(bgpsec->nlri); + lrtr_free(bgpsec); +} + +int main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_sanity_checks), cmocka_unit_test(test_load_private_key), + cmocka_unit_test(test_ecdsa_size), cmocka_unit_test(test_align_byte_sequence), + cmocka_unit_test(test_hash_byte_sequence), cmocka_unit_test(test_sign_byte_sequence), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/tests/unittests/test_bgpsec_signing.h b/tests/unittests/test_bgpsec_signing.h new file mode 100644 index 00000000..0ae329d9 --- /dev/null +++ b/tests/unittests/test_bgpsec_signing.h @@ -0,0 +1,33 @@ +/* + * 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 TEST_BGPSEC_SIGNING_H +#define TEST_BGPSEC_SIGNING_H + +#include "rtrlib/bgpsec/bgpsec_utils_private.h" + +#include +#include + +struct rtr_bgpsec *setup_bgpsec(void); + +int __wrap_load_private_key(EC_KEY **priv_key, uint8_t *bytes_key); + +int __wrap_ECDSA_size(const EC_KEY *key); + +int __wrap_align_byte_sequence(const struct rtr_bgpsec *data, struct stream *s, enum align_type type); + +unsigned int __wrap_req_stream_size(const struct rtr_bgpsec *data, enum align_type type); + +int __wrap_hash_byte_sequence(uint8_t *bytes, unsigned int bytes_len, uint8_t alg_suite_id, + unsigned char **hash_result); + +int __wrap_sign_byte_sequence(uint8_t *hash_result, EC_KEY *priv_key, uint8_t alg, + struct rtr_signature_seg *new_signature); +#endif diff --git a/tests/unittests/test_bgpsec_utils.c b/tests/unittests/test_bgpsec_utils.c new file mode 100644 index 00000000..43b67952 --- /dev/null +++ b/tests/unittests/test_bgpsec_utils.c @@ -0,0 +1,435 @@ +/* + * 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 "rtrlib_unittests.h" +#include "test_bgpsec_utils.h" + +#include "rtrlib/bgpsec/bgpsec_private.h" +#include "rtrlib/bgpsec/bgpsec_utils.c" + +#include + +/* Setup and return a bgpsec struct */ +struct rtr_bgpsec *setup_bgpsec(void) +{ + struct rtr_bgpsec *bgpsec = NULL; + uint8_t alg = 1; + uint8_t safi = 1; + uint16_t afi = 1; + uint32_t my_as = 65537; + uint32_t target_as = 65538; + struct rtr_bgpsec_nlri *pfx = NULL; + long pfx_int = 0; + + pfx = rtr_bgpsec_nlri_new(3); + pfx->nlri_len = 24; + pfx->afi = BGPSEC_IPV4; + pfx_int = 3221225984; /* 192.0.2.0 */ + + memcpy(pfx->nlri, &pfx_int, 3); + + bgpsec = rtr_bgpsec_new(alg, safi, afi, my_as, target_as, pfx); + return bgpsec; +} + +/* Setup and return a signature segment */ +struct rtr_signature_seg *setup_sig_seg(void) +{ + struct rtr_signature_seg *sig_seg = NULL; + + const char *ski = "aaaaaaaaaaaaaaaaaaaa"; // 20 times 'a' + uint16_t sig_len = 10; + const char *sig = "bbbbbbbbbb"; // 10 times 'b' + + sig_seg = rtr_bgpsec_new_signature_seg((uint8_t *)ski, sig_len, (uint8_t *)sig); + return sig_seg; +} + +/* Setup and return a secure path segment */ +struct rtr_secure_path_seg *setup_sec_seg(void) +{ + struct rtr_secure_path_seg *sec_path = NULL; + uint8_t pcount = 1; + uint8_t flags = 0; + uint32_t my_as = 65537; + + sec_path = rtr_bgpsec_new_secure_path_seg(pcount, flags, my_as); + return sec_path; +} + +/* Test all stream functions */ +static void test_bgpsec_streams(void **state) +{ + UNUSED(state); + + uint16_t size = 8; + struct stream *s = init_stream(size); + + uint16_t exp_size = get_stream_size(s); + uint8_t *stream_start = get_stream_start(s); + + /* Test, if stream is initialized correctly */ + assert_int_equal(size, exp_size); + assert_int_equal(0, s->w_head); + assert_int_equal(0, s->r_head); + assert(stream_start == s->stream); + + /* Write 8 bytes to the stream */ + const char *data = "abcdefgh"; + + write_stream(s, (uint8_t *)data, 8); + + /* Check, if the write head moved to position 8 */ + assert_int_equal(s->w_head, 8); + + /* Read 8 bytes from stream. Must be identical to data. */ + for (size_t i = 0; i < s->size; i++) + assert_int_equal(read_stream(s), data[i]); + + /* Check, if the read head moved to position 8 */ + assert_int_equal(s->r_head, 8); + + struct stream *s_cpy = copy_stream(s); + + assert_int_equal(s->size, s_cpy->size); + assert_int_equal(s->r_head, s_cpy->r_head); + assert_int_equal(s->w_head, s_cpy->w_head); + + // Reset read/write heads. + s_cpy->w_head = 0; + s_cpy->r_head = 0; + + /* Repeat the read test, this time on the stream copy */ + for (size_t i = 0; i < s_cpy->size; i++) + assert_int_equal(read_stream(s_cpy), data[i]); + + s->w_head = 0; // Reset write heads. + + /* Read 3 bytes from the stream at current read position 2 */ + s->r_head = 2; + uint8_t buffer_a[3] = {'\0'}; + + read_n_bytes_stream(buffer_a, s, 3); + + for (int i = 0; i < 3; i++) + assert_int_equal(buffer_a[i], data[i + 2]); + + /* Read 6 bytes from the stream starting at position 3. + * If you want to read more bytes from the stream than it holds, + * bytes are only read till the end of the stream. + */ + uint8_t buffer_b[6] = {'\0'}; + + read_stream_at(buffer_b, s, 3, 6); + + for (int i = 0; i < 6; i++) + assert_int_equal(buffer_b[i], data[i + 3]); + + s->r_head = 0; + + uint8_t buffer_c[10] = {'\0'}; + + buffer_c[8] = 8; + buffer_c[9] = 9; + + /* Reading more bytes than are held by the stream should not + * exceed the maximum size of the stream. Thus, the last values of + * buffer_c should not get overwritten. + */ + read_n_bytes_stream(buffer_c, s, 10); + + assert_int_equal(buffer_c[8], 8); + assert_int_equal(buffer_c[9], 9); + + /* Free the stream and its copy */ + free_stream(s_cpy); + free_stream(s); +} + +/* Test the size calculator functions */ +static void test_bgpsec_constructors(void **state) +{ + UNUSED(state); + + enum rtr_bgpsec_rtvals retval = RTR_BGPSEC_SUCCESS; + + struct rtr_bgpsec *bgpsec = NULL; + struct rtr_secure_path_seg *sec_path = NULL; + struct rtr_signature_seg *sig_seg = NULL; + + struct rtr_signature_seg *mal_sig_seg = NULL; + + struct rtr_bgpsec_nlri *pfx = NULL; + long pfx_int = 0; + + pfx = rtr_bgpsec_nlri_new(3); + pfx->nlri_len = 24; + pfx->afi = BGPSEC_IPV4; + pfx_int = 3221225984; /* 192.0.2.0 */ + + memcpy(pfx->nlri, &pfx_int, 3); + + /* The signature is not valid, but this is not relevant for the + * test. We only check if the information are copied correctly. + */ + const char *ski = "aaaaaaaaaaaaaaaaaaaa"; // 20 times 'a' + uint16_t sig_len = 10; + const char *sig = "bbbbbbbbbb"; // 10 times 'b' + const char *mal_ski = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; + + bgpsec = setup_bgpsec(); + + /* Check every single field in bgpsec that contains a numeric value. + * Do the same for the secure path and signature segment. + */ + assert_int_equal(1, bgpsec->alg); + assert_int_equal(1, bgpsec->safi); + assert_int_equal(1, bgpsec->afi); + assert_int_equal(65537, bgpsec->my_as); + assert_int_equal(65538, bgpsec->target_as); + assert_int_equal(0, bgpsec->path_len); + assert_int_equal(0, bgpsec->sigs_len); + assert_int_equal(24, bgpsec->nlri->nlri_len); + assert_int_equal(1, bgpsec->nlri->afi); + assert(memcmp(pfx->nlri, bgpsec->nlri->nlri, 3) == 0); + + sec_path = setup_sec_seg(); + + assert_int_equal(1, sec_path->pcount); + assert_int_equal(0, sec_path->flags); + assert_int_equal(65537, sec_path->asn); + + rtr_bgpsec_prepend_sec_path_seg(bgpsec, sec_path); + + /* Check, if the path was appeded to the bgpsec struct. If so, + * the information of the first bgpsec path element must be + * identical to the information of sec_path. + */ + assert_int_equal(1, bgpsec->path_len); + assert_int_equal(sec_path->pcount, bgpsec->path->pcount); + assert_int_equal(sec_path->flags, bgpsec->path->flags); + assert_int_equal(sec_path->asn, bgpsec->path->asn); + + sig_seg = setup_sig_seg(); + + assert_int_equal(sig_len, sig_seg->sig_len); + + /* Check, if each byte of ski and signature was copied correctly */ + for (int i = 0; i < SKI_SIZE; i++) + assert_int_equal('a', sig_seg->ski[i]); + + for (int i = 0; i < sig_len; i++) + assert_int_equal('b', sig_seg->signature[i]); + + retval = rtr_bgpsec_prepend_sig_seg(bgpsec, sig_seg); + assert_int_equal(RTR_BGPSEC_SUCCESS, retval); + + /* Same append check as for the secure path segment, but this time + * for the signature. + */ + assert_int_equal(1, bgpsec->sigs_len); + assert_int_equal(sig_seg->sig_len, bgpsec->sigs->sig_len); + for (int i = 0; i < SKI_SIZE; i++) + assert_int_equal(ski[i], bgpsec->sigs->ski[i]); + for (int i = 0; i < sig_len; i++) + assert_int_equal(sig[i], bgpsec->sigs->signature[i]); + + mal_sig_seg = setup_sig_seg(); + memcpy(mal_sig_seg->ski, mal_ski, SKI_SIZE); + + assert_int_equal(1, ski_is_empty((uint8_t *)mal_ski)); + + retval = rtr_bgpsec_prepend_sig_seg(bgpsec, mal_sig_seg); + assert_int_equal(RTR_BGPSEC_ERROR, retval); + + /* Append a segment to the last position of the path. */ + sec_path = setup_sec_seg(); + sec_path->asn = 12345; + rtr_bgpsec_append_sec_path_seg(bgpsec, sec_path); + assert_int_equal(2, bgpsec->path_len); + + /* Check, if the element was appended at the last position */ + assert_int_equal(12345, bgpsec->path->next->asn); + + /* Do the same appending process with a signature segment */ + sig_seg = setup_sig_seg(); + sig_seg->sig_len = 42; + retval = rtr_bgpsec_append_sig_seg(bgpsec, sig_seg); + assert_int_equal(RTR_BGPSEC_SUCCESS, retval); + + assert_int_equal(42, bgpsec->sigs->next->sig_len); + + /* Do the same appending process with an invalid signature segment */ + retval = rtr_bgpsec_append_sig_seg(bgpsec, mal_sig_seg); + assert_int_equal(RTR_BGPSEC_ERROR, retval); + + /* Free the bgpsec struct and all signatures and secure paths */ + rtr_bgpsec_nlri_free(pfx); + rtr_bgpsec_free(bgpsec); + rtr_bgpsec_free_signatures(mal_sig_seg); +} + +/* Test size calculator functions */ +static void test_bgpsec_sizes(void **state) +{ + UNUSED(state); + + struct rtr_bgpsec *bgpsec = NULL; + struct rtr_secure_path_seg *sec_path = NULL; + struct rtr_signature_seg *sig_seg = NULL; + + bgpsec = setup_bgpsec(); + + /* Generate five secure path and signature segments and append + * them to bgpsec. + */ + for (int i = 0; i < 5; i++) { + sec_path = setup_sec_seg(); + rtr_bgpsec_prepend_sec_path_seg(bgpsec, sec_path); + + sig_seg = setup_sig_seg(); + rtr_bgpsec_prepend_sig_seg(bgpsec, sig_seg); + } + + /* When calculating the required stream size for validation, the + * last appended signature segment is skipped. The calculation goes: + * five secure path segments + + * four signature segments + + * the size of rest (afi, nlri, ...) + */ + unsigned int exp_stream_size = (5 * 6) + (4 * 32) + 12; + size_t result = req_stream_size(bgpsec, VALIDATION); + + assert_int_equal(exp_stream_size, result); + + /* For singing, the length of all secure path and signature segments + * matters. The calculation goes: + * five secure path segments + + * five signature segments + + * the size of rest (afi, nlri, ...) + */ + exp_stream_size = (5 * 6) + (5 * 32) + 12; + result = req_stream_size(bgpsec, SIGNING); + assert_int_equal(exp_stream_size, result); + + /* Calculate the signature size only (validation). Again, the first + * segment is skipped. + */ + int size = get_sig_seg_size(bgpsec->sigs, VALIDATION); + + assert_int_equal((4 * 32), size); + + /* Calculate the signature size only (signing) */ + size = get_sig_seg_size(bgpsec->sigs, SIGNING); + assert_int_equal((5 * 32), size); + + /* Free the bgpsec struct and all signatures and secure paths */ + rtr_bgpsec_free(bgpsec); +} + +/* Test size calculator functions */ +static void test_bgpsec_prepend_pop(void **state) +{ + UNUSED(state); + + enum rtr_bgpsec_rtvals retval; + + struct rtr_bgpsec *bgpsec = NULL; + struct rtr_secure_path_seg *sec_path = NULL; + struct rtr_signature_seg *sig_seg = NULL; + + bgpsec = setup_bgpsec(); + sec_path = setup_sec_seg(); + sig_seg = setup_sig_seg(); + + /* Path length = signature length = 1 */ + rtr_bgpsec_prepend_sec_path_seg(bgpsec, sec_path); + retval = rtr_bgpsec_prepend_sig_seg(bgpsec, sig_seg); + + assert_int_equal(RTR_BGPSEC_SUCCESS, retval); + assert_int_equal(1, bgpsec->path_len); + assert_int_equal(1, bgpsec->sigs_len); + + sec_path = setup_sec_seg(); + sig_seg = setup_sig_seg(); + + /* Change some values so we can later validate that these + * specific segments were returned. + */ + sec_path->asn = 12345; + sig_seg->sig_len = 80; + + /* Path length = signature length = 2 */ + rtr_bgpsec_prepend_sec_path_seg(bgpsec, sec_path); + retval = rtr_bgpsec_prepend_sig_seg(bgpsec, sig_seg); + + assert_int_equal(RTR_BGPSEC_SUCCESS, retval); + assert_int_equal(2, bgpsec->path_len); + assert_int_equal(2, bgpsec->sigs_len); + + sec_path = NULL; + sig_seg = NULL; + + /* Path length = signature length = 1 */ + sec_path = rtr_bgpsec_pop_secure_path_seg(bgpsec); + sig_seg = rtr_bgpsec_pop_signature_seg(bgpsec); + + assert_int_equal(1, bgpsec->path_len); + assert_int_equal(1, bgpsec->sigs_len); + assert_int_equal(12345, sec_path->asn); + assert_int_equal(80, sig_seg->sig_len); + assert(sec_path); + assert(sig_seg); + assert(!sec_path->next); + assert(!sig_seg->next); + + rtr_bgpsec_free_secure_path(sec_path); + rtr_bgpsec_free_signatures(sig_seg); + + sec_path = NULL; + sig_seg = NULL; + + /* Path length = signature length = 0 */ + sec_path = rtr_bgpsec_pop_secure_path_seg(bgpsec); + sig_seg = rtr_bgpsec_pop_signature_seg(bgpsec); + + assert_int_equal(0, bgpsec->path_len); + assert_int_equal(0, bgpsec->sigs_len); + assert_int_equal(65537, sec_path->asn); + assert_int_equal(10, sig_seg->sig_len); + assert(sec_path); + assert(sig_seg); + assert(!sec_path->next); + assert(!sig_seg->next); + + rtr_bgpsec_free_secure_path(sec_path); + rtr_bgpsec_free_signatures(sig_seg); + + sec_path = NULL; + sig_seg = NULL; + + /* Path length = signature length = 0 */ + sec_path = rtr_bgpsec_pop_secure_path_seg(bgpsec); + sig_seg = rtr_bgpsec_pop_signature_seg(bgpsec); + assert(!sec_path); + assert(!sig_seg); + + rtr_bgpsec_free(bgpsec); +} + +int main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_bgpsec_streams), + cmocka_unit_test(test_bgpsec_constructors), + cmocka_unit_test(test_bgpsec_sizes), + cmocka_unit_test(test_bgpsec_prepend_pop), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/tests/unittests/test_bgpsec_utils.h b/tests/unittests/test_bgpsec_utils.h new file mode 100644 index 00000000..1a12b953 --- /dev/null +++ b/tests/unittests/test_bgpsec_utils.h @@ -0,0 +1,20 @@ +/* + * 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 TEST_BGPSEC_UTILS_H +#define TEST_BGPSEC_UTILS_H + +#include "rtrlib/bgpsec/bgpsec.h" + +struct rtr_bgpsec *setup_bgpsec(void); + +struct rtr_signature_seg *setup_sig_seg(void); + +struct rtr_secure_path_seg *setup_sec_seg(void); +#endif diff --git a/tests/unittests/test_bgpsec_validation.c b/tests/unittests/test_bgpsec_validation.c new file mode 100644 index 00000000..0b4a67b7 --- /dev/null +++ b/tests/unittests/test_bgpsec_validation.c @@ -0,0 +1,248 @@ +/* + * 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 "rtrlib_unittests.h" +#include "test_bgpsec_validation.h" + +#include "rtrlib/bgpsec/bgpsec_private.h" +#include "rtrlib/bgpsec/bgpsec_utils.c" + +#include + +struct rtr_bgpsec *setup_bgpsec(void) +{ + struct rtr_bgpsec *bgpsec = NULL; + uint8_t alg = 1; + uint8_t safi = 1; + uint16_t afi = 1; + uint32_t my_as = 65537; + uint32_t target_as = 65538; + struct rtr_bgpsec_nlri *pfx = NULL; + int pfx_int = 0; + + pfx = rtr_bgpsec_nlri_new(3); + pfx->nlri_len = 24; + pfx->afi = 1; /* LRTR_IPV4 */ + pfx_int = htonl(3221225984); /* 192.0.2.0 */ + + memcpy(pfx->nlri, &pfx_int, 3); + + bgpsec = rtr_bgpsec_new(alg, safi, afi, my_as, target_as, pfx); + bgpsec->path = lrtr_malloc(sizeof(struct rtr_secure_path_seg)); + bgpsec->sigs = lrtr_malloc(sizeof(struct rtr_signature_seg)); + bgpsec->path->next = NULL; + bgpsec->sigs->next = NULL; + bgpsec->sigs->sig_len = 0; + return bgpsec; +} + +int __wrap_check_router_keys(const struct rtr_signature_seg *sig_segs, struct spki_table *table) +{ + UNUSED(sig_segs); + UNUSED(table); + return (int)mock(); +} + +int __wrap_align_byte_sequence(const struct rtr_bgpsec *data, struct stream *s, enum align_type type) +{ + UNUSED(data); + UNUSED(s); + UNUSED(type); + return (int)mock(); +} + +unsigned int __wrap_req_stream_size(const struct rtr_bgpsec *data, enum align_type type) +{ + UNUSED(data); + UNUSED(type); + return (int)mock(); +} + +int __wrap_hash_byte_sequence(uint8_t *bytes, unsigned int bytes_len, uint8_t alg_suite_id, unsigned char **hash_result) +{ + UNUSED(bytes); + UNUSED(bytes_len); + UNUSED(alg_suite_id); + UNUSED(hash_result); + return (int)mock(); +} + +int __wrap_validate_signature(const unsigned char *hash, const struct rtr_signature_seg *sig, + struct spki_record *record) +{ + UNUSED(hash); + UNUSED(sig); + UNUSED(record); + return (int)mock(); +} + +int __wrap_spki_table_search_by_ski(struct spki_table *spki_table, uint8_t *ski, struct spki_record **result, + unsigned int *result_size) +{ + UNUSED(spki_table); + UNUSED(ski); + UNUSED(result); + *result_size = 1; + return (int)mock(); +} + +static void test_sanity_checks(void **state) +{ + struct rtr_bgpsec *bgpsec = setup_bgpsec(); + struct spki_table *table = lrtr_malloc(16); + enum rtr_bgpsec_rtvals result = RTR_BGPSEC_SUCCESS; + + UNUSED(state); + + result = rtr_bgpsec_validate_as_path(NULL, table); + assert_int_equal(RTR_BGPSEC_INVALID_ARGUMENTS, result); + + result = rtr_bgpsec_validate_as_path(bgpsec, NULL); + assert_int_equal(RTR_BGPSEC_INVALID_ARGUMENTS, result); + + result = rtr_bgpsec_validate_as_path(NULL, NULL); + assert_int_equal(RTR_BGPSEC_INVALID_ARGUMENTS, result); + + bgpsec->path_len = 1; + bgpsec->sigs_len = 2; + + result = rtr_bgpsec_validate_as_path(bgpsec, table); + assert_int_equal(RTR_BGPSEC_WRONG_SEGMENT_COUNT, result); + + bgpsec->path_len = 0; + bgpsec->sigs_len = 0; + bgpsec->alg = 3; + + result = rtr_bgpsec_validate_as_path(bgpsec, table); + assert_int_equal(RTR_BGPSEC_UNSUPPORTED_ALGORITHM_SUITE, result); + + bgpsec->alg = 1; + bgpsec->nlri->afi = 8; + + result = rtr_bgpsec_validate_as_path(bgpsec, table); + assert_int_equal(RTR_BGPSEC_UNSUPPORTED_AFI, result); + + lrtr_free(bgpsec->path); + lrtr_free(bgpsec->sigs); + + bgpsec->path = NULL; + bgpsec->sigs = NULL; + + result = rtr_bgpsec_validate_as_path(bgpsec, table); + assert_int_equal(RTR_BGPSEC_INVALID_ARGUMENTS, result); + + lrtr_free(table); + lrtr_free(bgpsec->nlri->nlri); + lrtr_free(bgpsec->nlri); + lrtr_free(bgpsec); +} + +static void test_check_router_keys(void **state) +{ + struct rtr_bgpsec *bgpsec = setup_bgpsec(); + struct spki_table *table = lrtr_malloc(16); + enum rtr_bgpsec_rtvals result = RTR_BGPSEC_SUCCESS; + + UNUSED(state); + + will_return(__wrap_check_router_keys, RTR_BGPSEC_ROUTER_KEY_NOT_FOUND); + result = rtr_bgpsec_validate_as_path(bgpsec, table); + + assert_int_equal(RTR_BGPSEC_ROUTER_KEY_NOT_FOUND, result); + + lrtr_free(table); + lrtr_free(bgpsec->path); + lrtr_free(bgpsec->sigs); + lrtr_free(bgpsec->nlri->nlri); + lrtr_free(bgpsec->nlri); + lrtr_free(bgpsec); +} + +static void test_align_byte_sequence(void **state) +{ + struct rtr_bgpsec *bgpsec = setup_bgpsec(); + struct spki_table *table = lrtr_malloc(16); + enum rtr_bgpsec_rtvals result = RTR_BGPSEC_SUCCESS; + + UNUSED(state); + + will_return(__wrap_align_byte_sequence, RTR_BGPSEC_ERROR); + will_return(__wrap_req_stream_size, 12); + will_return(__wrap_check_router_keys, RTR_BGPSEC_SUCCESS); + result = rtr_bgpsec_validate_as_path(bgpsec, table); + + assert_int_equal(RTR_BGPSEC_ERROR, result); + + lrtr_free(table); + lrtr_free(bgpsec->path); + lrtr_free(bgpsec->sigs); + lrtr_free(bgpsec->nlri->nlri); + lrtr_free(bgpsec->nlri); + lrtr_free(bgpsec); +} + +static void test_hash_byte_sequence(void **state) +{ + struct rtr_bgpsec *bgpsec = setup_bgpsec(); + struct spki_table *table = lrtr_malloc(16); + enum rtr_bgpsec_rtvals result = RTR_BGPSEC_SUCCESS; + + UNUSED(state); + + will_return(__wrap_hash_byte_sequence, RTR_BGPSEC_ERROR); + will_return(__wrap_align_byte_sequence, RTR_BGPSEC_SUCCESS); + will_return(__wrap_req_stream_size, 12); + will_return(__wrap_check_router_keys, RTR_BGPSEC_SUCCESS); + result = rtr_bgpsec_validate_as_path(bgpsec, table); + + assert_int_equal(RTR_BGPSEC_ERROR, result); + + lrtr_free(table); + lrtr_free(bgpsec->path); + lrtr_free(bgpsec->sigs); + lrtr_free(bgpsec->nlri->nlri); + lrtr_free(bgpsec->nlri); + lrtr_free(bgpsec); +} + +static void test_validate_signature(void **state) +{ + struct rtr_bgpsec *bgpsec = setup_bgpsec(); + struct spki_table *table = lrtr_malloc(16); + enum rtr_bgpsec_rtvals result = RTR_BGPSEC_SUCCESS; + + UNUSED(state); + + will_return(__wrap_validate_signature, RTR_BGPSEC_ERROR); + will_return(__wrap_spki_table_search_by_ski, SPKI_SUCCESS); + will_return(__wrap_hash_byte_sequence, RTR_BGPSEC_SUCCESS); + will_return(__wrap_align_byte_sequence, RTR_BGPSEC_SUCCESS); + will_return(__wrap_req_stream_size, 12); + will_return(__wrap_check_router_keys, RTR_BGPSEC_SUCCESS); + result = rtr_bgpsec_validate_as_path(bgpsec, table); + + assert_int_equal(RTR_BGPSEC_ERROR, result); + + lrtr_free(table); + lrtr_free(bgpsec->path); + lrtr_free(bgpsec->sigs); + lrtr_free(bgpsec->nlri->nlri); + lrtr_free(bgpsec->nlri); + lrtr_free(bgpsec); +} + +int main(void) +{ + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_sanity_checks), cmocka_unit_test(test_check_router_keys), + cmocka_unit_test(test_align_byte_sequence), cmocka_unit_test(test_hash_byte_sequence), + cmocka_unit_test(test_validate_signature), + }; + return cmocka_run_group_tests(tests, NULL, NULL); +} diff --git a/tests/unittests/test_bgpsec_validation.h b/tests/unittests/test_bgpsec_validation.h new file mode 100644 index 00000000..40fc945f --- /dev/null +++ b/tests/unittests/test_bgpsec_validation.h @@ -0,0 +1,33 @@ +/* + * 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 TEST_BGPSEC_VALIDATION_H +#define TEST_BGPSEC_VALIDATION_H + +#include "rtrlib/bgpsec/bgpsec_utils_private.h" + +#include + +struct rtr_bgpsec *setup_bgpsec(void); + +int __wrap_check_router_keys(const struct rtr_signature_seg *sig_segs, struct spki_table *table); + +int __wrap_align_byte_sequence(const struct rtr_bgpsec *data, struct stream *s, enum align_type type); + +unsigned int __wrap_req_stream_size(const struct rtr_bgpsec *data, enum align_type type); + +int __wrap_hash_byte_sequence(uint8_t *bytes, unsigned int bytes_len, uint8_t alg_suite_id, + unsigned char **hash_result); + +int __wrap_validate_signature(const unsigned char *hash, const struct rtr_signature_seg *sig, + struct spki_record *record); + +int __wrap_spki_table_search_by_ski(struct spki_table *spki_table, uint8_t *ski, struct spki_record **result, + unsigned int *result_size); +#endif From ebee20f24a0774960b6c0efb3136555ab2f7dcac Mon Sep 17 00:00:00 2001 From: Colin Sames Date: Thu, 14 Oct 2021 17:57:15 +0200 Subject: [PATCH 04/55] debian: add libssl dependencies. BGPsec requires OpenSSL. --- debian/control | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/debian/control b/debian/control index 4be8da31..f147bb19 100644 --- a/debian/control +++ b/debian/control @@ -2,7 +2,7 @@ Source: librtr0 Section: libs Priority: optional Maintainer: Fabian Holler -Build-Depends: cmake, dpkg-dev (>= 1.16.1~), debhelper (>= 9), libssh-dev (>= 0.5.0), doxygen +Build-Depends: cmake, dpkg-dev (>= 1.16.1~), debhelper (>= 9), libssh-dev (>= 0.5.0), libssl1.0-dev (>= 1.0) | libssl-dev (>= 1.0), doxygen Standards-Version: 3.9.6 Vcs-Git: git://github.com/rtrlib/rtrlib.git Vcs-Browser: https://github.com/rtrlib/rtrlib @@ -16,9 +16,9 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, libssh-4 (>= 0.5.0) Description: Small extensible RPKI-RTR-Client C library. RTRlib is an open-source C implementation of the RPKI/Router Protocol client. The library allows one to fetch and store validated prefix origin data - from a RTR-cache and performs origin verification of prefixes. It supports - different types of transport sessions (e.g., SSH, unprotected TCP) and is - easily extendable. + from a RTR-cache and performs origin verification of prefixes. It also allows + validating and signing BGPsec AS paths. RTRlib supports different types of + transport sessions (e.g., SSH, unprotected TCP) and is easily extendable. Package: librtr-dev Section: libdevel @@ -30,9 +30,9 @@ Suggests: librtr-doc Description: Small extensible RPKI-RTR-Client C library. Development files RTRlib is an open-source C implementation of the RPKI/Router Protocol client. The library allows one to fetch and store validated prefix origin data - from a RTR-cache and performs origin verification of prefixes. It supports - different types of transport sessions (e.g., SSH, unprotected TCP) and is - easily extendable. + from a RTR-cache and performs origin verification of prefixes. It also allows + validating and signing BGPsec AS paths. RTRlib supports different types of + transport sessions (e.g., SSH, unprotected TCP) and is easily extendable. . This package contains development files. @@ -45,9 +45,9 @@ Depends: librtr0 (= ${binary:Version}), ${misc:Depends} Description: Small extensible RPKI-RTR-Client C library. Debug Symbols RTRlib is an open-source C implementation of the RPKI/Router Protocol client. The library allows one to fetch and store validated prefix origin data - from a RTR-cache and performs origin verification of prefixes. It supports - different types of transport sessions (e.g., SSH, unprotected TCP) and is - easily extendable. + from a RTR-cache and performs origin verification of prefixes. It also allows + validating and signing BGPsec AS paths. RTRlib supports different types of + transport sessions (e.g., SSH, unprotected TCP) and is easily extendable. . This package contains debug symbols. @@ -60,9 +60,9 @@ Suggests: doc-base Description: Small extensible RPKI-RTR-Client C library. Documentation files RTRlib is an open-source C implementation of the RPKI/Router Protocol client. The library allows one to fetch and store validated prefix origin data - from a RTR-cache and performs origin verification of prefixes. It supports - different types of transport sessions (e.g., SSH, unprotected TCP) and is - easily extendable. + from a RTR-cache and performs origin verification of prefixes. It also allows + validating and signing BGPsec AS paths. RTRlib supports different types of + transport sessions (e.g., SSH, unprotected TCP) and is easily extendable. . This package contains documentation files. From f822fd98ac3409bd00c6d428786f26db19b13531 Mon Sep 17 00:00:00 2001 From: Colin Sames Date: Thu, 14 Oct 2021 17:57:39 +0200 Subject: [PATCH 05/55] redhat: add libssl as a dependency. BGPsec requires libssl as a dependency. --- redhat/SPECS/librtr.spec | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/redhat/SPECS/librtr.spec b/redhat/SPECS/librtr.spec index 6a3441b2..d8b84ccc 100644 --- a/redhat/SPECS/librtr.spec +++ b/redhat/SPECS/librtr.spec @@ -6,27 +6,29 @@ Group: Development/Libraries License: MIT URL: http://rpki.realmv6.org/ Source0: %{name}-%{version}.tar.gz -BuildRequires: binutils gcc tar cmake libssh-devel >= 0.5.0 doxygen -Requires: libssh >= 0.5.0 +BuildRequires: binutils gcc tar cmake libssh-devel >= 0.5.0 openssl-devel >= 1.0 doxygen +Requires: libssh >= 0.5.0 openssl >= 1.0 %description -RTRlib is an open-source C implementation of the RPKI/Router Protocol +RTRlib is an open-source C implementation of the RPKI/Router Protocol client. The library allows one to fetch and store validated prefix origin -data from a RTR-cache and performs origin verification of prefixes. It -supports different types of transport sessions (e.g., SSH, unprotected TCP) -and is easily extendable. +data from a RTR-cache and performs origin verification of prefixes. It also +allows validating and signing BGPsec AS paths. It supports different +types of transport sessions (e.g., SSH, unprotected TCP) and is easily +extendable. %package devel Summary: Small extensible RPKI-RTR-Client C library. Development files Group: Development/Libraries -Requires: %{name} = %{version}-%{release} libssh-devel >= 0.5.0 +Requires: %{name} = %{version}-%{release} libssh-devel >= 0.5.0 openssl-devel >= 1.0 %description devel -RTRlib is an open-source C implementation of the RPKI/Router Protocol +RTRlib is an open-source C implementation of the RPKI/Router Protocol client. The library allows one to fetch and store validated prefix origin -data from a RTR-cache and performs origin verification of prefixes. It -supports different types of transport sessions (e.g., SSH, unprotected TCP) -and is easily extendable. +data from a RTR-cache and performs origin verification of prefixes. It also +allows validating and signing BGPsec AS paths. It supports different +types of transport sessions (e.g., SSH, unprotected TCP) and is easily +extendable. . This package contains development files. @@ -37,11 +39,12 @@ Requires: %{name} = %{version}-%{release} BuildArch: noarch %description doc -RTRlib is an open-source C implementation of the RPKI/Router Protocol +RTRlib is an open-source C implementation of the RPKI/Router Protocol client. The library allows one to fetch and store validated prefix origin -data from a RTR-cache and performs origin verification of prefixes. It -supports different types of transport sessions (e.g., SSH, unprotected TCP) -and is easily extendable. +data from a RTR-cache and performs origin verification of prefixes. It also +allows validating and signing BGPsec AS paths. It supports different +types of transport sessions (e.g., SSH, unprotected TCP) and is easily +extendable. . This package contains documentation files. From 474ea3814af9b4064c6fa9d30bcaac6e16b6092f Mon Sep 17 00:00:00 2001 From: Colin Sames Date: Wed, 15 Dec 2021 16:51:18 +0100 Subject: [PATCH 06/55] bgpsec: CONFIG_H -> RTR_CONFIG_H --- rtrlib/config.h.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rtrlib/config.h.cmake b/rtrlib/config.h.cmake index 617e394e..943d611e 100644 --- a/rtrlib/config.h.cmake +++ b/rtrlib/config.h.cmake @@ -11,8 +11,8 @@ extern "C" { #endif -#ifndef CONFIG_H -#define CONFIG_H +#ifndef RTR_CONFIG_H +#define RTR_CONFIG_H #cmakedefine RTRLIB_BGPSEC_ENABLED From 179e7efb59529008eed77b3cf783667435dfba9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20R=C3=B6thke?= Date: Sun, 26 Dec 2021 14:00:30 +0100 Subject: [PATCH 07/55] rtrlib/rtr_mgr: properly cleanup rtr_sockets on stop (#268) Previously rtr_sockets could not be restarted because their state remained on SHUTDOWN, which they can, by design, not recover from themselves. --- rtrlib/rtr/rtr.c | 1 + 1 file changed, 1 insertion(+) diff --git a/rtrlib/rtr/rtr.c b/rtrlib/rtr/rtr.c index 26452e26..57bfbc3a 100644 --- a/rtrlib/rtr/rtr.c +++ b/rtrlib/rtr/rtr.c @@ -242,6 +242,7 @@ void rtr_stop(struct rtr_socket *rtr_socket) 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_socket->state = RTR_CLOSED; } RTR_DBG1("Socket shut down"); } From d80baaf0cffe42bb4414e4a06bb5e54058059945 Mon Sep 17 00:00:00 2001 From: Martin Winter Date: Mon, 17 Jan 2022 16:40:29 +0100 Subject: [PATCH 08/55] redhat: Fix RPM file to work on Fedora 33+ and RedHat 9+ - Newer Fedora/RedHat changes cmake rpm build to build out of tree and requires the use of macros for correct locations See https://fedoraproject.org/wiki/Changes/CMake_to_do_out-of-source_builds - On newer Fedora, the SOURCES subdirectory isn't created automatically and needs to be created in the prep phase before the tar is created Signed-off-by: Martin Winter --- redhat/SPECS/librtr.spec | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/redhat/SPECS/librtr.spec b/redhat/SPECS/librtr.spec index d8b84ccc..e02d81e2 100644 --- a/redhat/SPECS/librtr.spec +++ b/redhat/SPECS/librtr.spec @@ -9,6 +9,14 @@ Source0: %{name}-%{version}.tar.gz BuildRequires: binutils gcc tar cmake libssh-devel >= 0.5.0 openssl-devel >= 1.0 doxygen Requires: libssh >= 0.5.0 openssl >= 1.0 +# Fedora 33 and up changed cmake in rpm's +# See https://fedoraproject.org/wiki/Changes/CMake_to_do_out-of-source_builds +%if 0%{?fedora} >= 33 || 0%{?rhel} >= 9 + %global use_cmake_macros 1 +%else + %global use_cmake_macros 0 +%endif + %description RTRlib is an open-source C implementation of the RPKI/Router Protocol client. The library allows one to fetch and store validated prefix origin @@ -63,8 +71,10 @@ allows to validate given IP prefixes and origin ASes. %prep if [ ! -f %{SOURCE0} ]; then # Build Source Tarball first + mkdir -p %{_topdir}/SOURCES pushd `dirname %_topdir`; tar czf %{SOURCE0} --exclude-vcs --exclude=redhat . ; popd fi +mkdir -p %{_topdir}/BUILD cd %{_topdir}/BUILD rm -rf %{name}-%{version} tar xzf %{SOURCE0} @@ -72,10 +82,18 @@ tar xzf %{SOURCE0} %build %cmake -D CMAKE_BUILD_TYPE=Release . -make %{?_smp_mflags} +%if %{use_cmake_macros} + %cmake_build %{?_smp_mflags} +%else + make %{?_smp_mflags} +%endif %install -%make_install +%if %{use_cmake_macros} + %cmake_install +%else + %make_install +%endif strip $RPM_BUILD_ROOT/usr/lib64/librtr.so.%{version} strip $RPM_BUILD_ROOT/usr/bin/rpki-rov strip $RPM_BUILD_ROOT/usr/bin/rtrclient @@ -83,7 +101,11 @@ cp %{_topdir}/BUILD/CHANGELOG %{buildroot}/%{_docdir}/rtrlib/ cp %{_topdir}/BUILD/LICENSE %{buildroot}/%{_docdir}/rtrlib/ %check -export LD_LIBRARY_PATH=.; make test +%if %{use_cmake_macros} + export LD_LIBRARY_PATH=.; %cmake_build --target test +%else + export LD_LIBRARY_PATH=.; make test +%endif %post -p /sbin/ldconfig @@ -114,7 +136,11 @@ export LD_LIBRARY_PATH=.; make test %doc LICENSE %changelog -* Sun Mar 15 2020 Martin Winter - %{version}-%{release} +* Mon Jan 17 2022 Martin Winter - %{version}-%{release} +- Use cmake macros for builds on Fedora 33 and up and RedHat 9 and up +- Fix missing SOURCES directory during rpmbuild on newer systems + +* Sun Mar 15 2020 Martin Winter - 0.7.0 - Update RPM spec changelog to fix changelog error * Thu Dec 14 2017 Martin Winter - 0.5.0 From 41f5f057d4762267fbd6224c672bc439570f944d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20R=C3=B6thke?= Date: Wed, 5 Jan 2022 11:29:25 +0100 Subject: [PATCH 09/55] rtr_mgr: replace mutex with rwlock rtr_mgr could enter a deadlocked state with multiple socket groups when a group with at least one group of lower preference comes back online. This happens because the thread of the group coming back online blocks on trying to shut down all threads with lower preference while holding the rtr_mgrs mutex, but a thread that tries to acquire that same mutex cannot be shut down while doing so. Since socket threads do not change rtr_mgrs protected structures an rwlock can be used to allow multiple socket threads to read them while preventing external calls from changing then concurrently. This allows the thread that is supposed to shut down to enter code sections where it can be shut down. Fix #269 --- rtrlib/rtr_mgr.c | 52 ++++++++++++++++++++++++------------------------ rtrlib/rtr_mgr.h | 2 +- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/rtrlib/rtr_mgr.c b/rtrlib/rtr_mgr.c index f00a0a57..aedec013 100644 --- a/rtrlib/rtr_mgr.c +++ b/rtrlib/rtr_mgr.c @@ -94,7 +94,7 @@ bool rtr_mgr_config_status_is_synced(const struct rtr_mgr_group *group) 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); + pthread_rwlock_rdlock(&config->mutex); tommy_node *node = tommy_list_head(&config->groups->list); while (node) { @@ -109,12 +109,12 @@ static void rtr_mgr_close_less_preferable_groups(const struct rtr_socket *sock, } node = node->next; } - pthread_mutex_unlock(&config->mutex); + pthread_rwlock_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); + pthread_rwlock_rdlock(&config->mutex); tommy_node *node = tommy_list_head(&config->groups->list); while (node) { @@ -122,30 +122,30 @@ static struct rtr_mgr_group *get_best_inactive_rtr_mgr_group(struct rtr_mgr_conf struct rtr_mgr_group *current_group = group_node->group; if ((current_group != group) && (current_group->status == RTR_MGR_CLOSED)) { - pthread_mutex_unlock(&config->mutex); + pthread_rwlock_unlock(&config->mutex); return current_group; } node = node->next; } - pthread_mutex_unlock(&config->mutex); + pthread_rwlock_unlock(&config->mutex); return NULL; } static bool is_some_rtr_mgr_group_established(struct rtr_mgr_config *config) { - pthread_mutex_lock(&config->mutex); + pthread_rwlock_rdlock(&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); + pthread_rwlock_unlock(&config->mutex); return true; } node = node->next; } - pthread_mutex_unlock(&config->mutex); + pthread_rwlock_unlock(&config->mutex); return false; } @@ -189,7 +189,7 @@ static inline void _rtr_mgr_cb_state_established(const struct rtr_socket *sock, */ bool all_error = true; - pthread_mutex_lock(&config->mutex); + pthread_rwlock_rdlock(&config->mutex); tommy_node *node = tommy_list_head(&config->groups->list); while (node) { @@ -203,7 +203,7 @@ static inline void _rtr_mgr_cb_state_established(const struct rtr_socket *sock, } node = node->next; } - pthread_mutex_unlock(&config->mutex); + pthread_rwlock_unlock(&config->mutex); if (all_error && rtr_mgr_config_status_is_synced(group)) { set_status(config, group, RTR_MGR_ESTABLISHED, sock); @@ -320,7 +320,7 @@ RTRLIB_EXPORT int rtr_mgr_init(struct rtr_mgr_config **config_out, struct rtr_mg config->len = groups_len; - if (pthread_mutex_init(&config->mutex, NULL) != 0) { + if (pthread_rwlock_init(&config->mutex, NULL) != 0) { MGR_DBG1("Mutex initialization failed"); goto err; } @@ -426,7 +426,7 @@ RTRLIB_EXPORT int rtr_mgr_start(struct rtr_mgr_config *config) RTRLIB_EXPORT bool rtr_mgr_conf_in_sync(struct rtr_mgr_config *config) { - pthread_mutex_lock(&config->mutex); + pthread_rwlock_rdlock(&config->mutex); tommy_node *node = tommy_list_head(&config->groups->list); while (node) { @@ -438,19 +438,19 @@ RTRLIB_EXPORT bool rtr_mgr_conf_in_sync(struct rtr_mgr_config *config) all_sync = false; } if (all_sync) { - pthread_mutex_unlock(&config->mutex); + pthread_rwlock_unlock(&config->mutex); return true; } node = node->next; } - pthread_mutex_unlock(&config->mutex); + pthread_rwlock_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); + pthread_rwlock_wrlock(&config->mutex); pfx_table_free(config->pfx_table); spki_table_free(config->spki_table); @@ -474,8 +474,8 @@ RTRLIB_EXPORT void rtr_mgr_free(struct rtr_mgr_config *config) lrtr_free(config->groups); - pthread_mutex_unlock(&config->mutex); - pthread_mutex_destroy(&config->mutex); + pthread_rwlock_unlock(&config->mutex); + pthread_rwlock_destroy(&config->mutex); lrtr_free(config); } @@ -496,7 +496,7 @@ RTRLIB_EXPORT inline int rtr_mgr_get_spki(struct rtr_mgr_config *config, const u RTRLIB_EXPORT void rtr_mgr_stop(struct rtr_mgr_config *config) { - pthread_mutex_lock(&config->mutex); + pthread_rwlock_rdlock(&config->mutex); tommy_node *node = tommy_list_head(&config->groups->list); MGR_DBG("%s()", __func__); @@ -507,7 +507,7 @@ RTRLIB_EXPORT void rtr_mgr_stop(struct rtr_mgr_config *config) rtr_stop(group_node->group->sockets[j]); node = node->next; } - pthread_mutex_unlock(&config->mutex); + pthread_rwlock_unlock(&config->mutex); } /* cppcheck-suppress unusedFunction */ @@ -522,7 +522,7 @@ RTRLIB_EXPORT int rtr_mgr_add_group(struct rtr_mgr_config *config, const struct struct rtr_mgr_group *new_group = NULL; struct rtr_mgr_group_node *gnode; - pthread_mutex_lock(&config->mutex); + pthread_rwlock_wrlock(&config->mutex); tommy_node *node = tommy_list_head(&config->groups->list); @@ -574,11 +574,11 @@ RTRLIB_EXPORT int rtr_mgr_add_group(struct rtr_mgr_config *config, const struct if (best_group->status == RTR_MGR_CLOSED) rtr_mgr_start_sockets(best_group); - pthread_mutex_unlock(&config->mutex); + pthread_rwlock_unlock(&config->mutex); return RTR_SUCCESS; err: - pthread_mutex_unlock(&config->mutex); + pthread_rwlock_unlock(&config->mutex); if (new_group) lrtr_free(new_group); @@ -589,7 +589,7 @@ RTRLIB_EXPORT int rtr_mgr_add_group(struct rtr_mgr_config *config, const struct /* cppcheck-suppress unusedFunction */ RTRLIB_EXPORT int rtr_mgr_remove_group(struct rtr_mgr_config *config, unsigned int preference) { - pthread_mutex_lock(&config->mutex); + pthread_rwlock_wrlock(&config->mutex); tommy_node *remove_node = NULL; tommy_node *node = tommy_list_head(&config->groups->list); struct rtr_mgr_group_node *group_node; @@ -597,7 +597,7 @@ RTRLIB_EXPORT int rtr_mgr_remove_group(struct rtr_mgr_config *config, unsigned i if (config->len == 1) { MGR_DBG1("Cannot remove last remaining group!"); - pthread_mutex_unlock(&config->mutex); + pthread_rwlock_unlock(&config->mutex); return RTR_ERROR; } @@ -611,7 +611,7 @@ RTRLIB_EXPORT int rtr_mgr_remove_group(struct rtr_mgr_config *config, unsigned i if (!remove_node) { MGR_DBG1("The group that should be removed does not exist!"); - pthread_mutex_unlock(&config->mutex); + pthread_rwlock_unlock(&config->mutex); return RTR_ERROR; } @@ -621,7 +621,7 @@ RTRLIB_EXPORT int rtr_mgr_remove_group(struct rtr_mgr_config *config, unsigned i 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); + pthread_rwlock_unlock(&config->mutex); // If group isn't closed, make it so! if (remove_group->status != RTR_MGR_CLOSED) { diff --git a/rtrlib/rtr_mgr.h b/rtrlib/rtr_mgr.h index 512db863..aa33d94e 100644 --- a/rtrlib/rtr_mgr.h +++ b/rtrlib/rtr_mgr.h @@ -83,7 +83,7 @@ struct tommy_list_wrapper; struct rtr_mgr_config { struct tommy_list_wrapper *groups; unsigned int len; - pthread_mutex_t mutex; + pthread_rwlock_t mutex; rtr_mgr_status_fp status_fp; void *status_fp_data; struct pfx_table *pfx_table; From 1a2a042ca659f3d9601a278bbdd6ababe54368f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Cochard-Labb=C3=A9?= Date: Tue, 6 Sep 2022 00:47:56 +0200 Subject: [PATCH 10/55] Fix LIBSSH_ variable names --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 94862396..d516eb3a 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,8 @@ Compilation If the libssh isn't installed within the systems include and library directories you can run cmake with the following parameters: - -D LIBSSH_LIBRARY= - -D LIBSSH_INCLUDE= + -D LIBSSH_LIBRARIES= + -D LIBSSH_INCLUDE_DIRS= If libssh is installed but you do not want to build rtrlib with ssh support, you can disable it with the following parameter: From e3f6bf625bf7249f0cd5858fe1b14b7aecbaef65 Mon Sep 17 00:00:00 2001 From: Zopolis4 Date: Thu, 22 Sep 2022 10:25:09 +1000 Subject: [PATCH 11/55] lib: Restrict overmatching MACH ifdef to only trigger on OSX and Mach --- rtrlib/lib/utils.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rtrlib/lib/utils.c b/rtrlib/lib/utils.c index ca708e97..cf347103 100644 --- a/rtrlib/lib/utils.c +++ b/rtrlib/lib/utils.c @@ -13,14 +13,14 @@ #include #include -#ifdef __MACH__ +#if defined(__MACH__) && defined(__APPLE__) #include static double timeconvert = 0.0; #endif int lrtr_get_monotonic_time(time_t *seconds) { -#ifdef __MACH__ +#if defined(__MACH__) && defined(__APPLE__) if (timeconvert == 0.0) { mach_timebase_info_data_t time_base; (void)mach_timebase_info(&time_base); From 52b2d66049664046afd80deaf2bc8eee8486173e Mon Sep 17 00:00:00 2001 From: maurim Date: Mon, 20 Nov 2023 16:03:04 +0100 Subject: [PATCH 12/55] Updates the used public rpki-caches for testing environment Motivation - get pipeline checks running How: - find valid rpki-cache which runs also rtr - edit url in live_tests --- tests/test_dynamic_groups.c | 4 ++-- tests/test_live_validation.c | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_dynamic_groups.c b/tests/test_dynamic_groups.c index 51d042cb..c67bb35d 100644 --- a/tests/test_dynamic_groups.c +++ b/tests/test_dynamic_groups.c @@ -24,8 +24,8 @@ int main(void) //create a TCP transport socket int retval = 0; struct tr_socket tr_tcp; - char tcp_host[] = "rpki-validator.realmv6.org"; - char tcp_port[] = "8283"; + char tcp_host[] = "rpki-cache.netd.cs.tu-dresden.de"; + char tcp_port[] = "3323"; struct tr_tcp_config tcp_config = { tcp_host, //IP diff --git a/tests/test_live_validation.c b/tests/test_live_validation.c index 73c85757..c3cdbcaf 100644 --- a/tests/test_live_validation.c +++ b/tests/test_live_validation.c @@ -57,12 +57,12 @@ int main(void) /* These variables are not in the global scope * because it would cause warnings about discarding constness */ - char RPKI_CACHE_HOST[] = "rpki-validator.realmv6.org"; - char RPKI_CACHE_POST[] = "8283"; + char RPKI_CACHE_HOST[] = "rpki-cache.netd.cs.tu-dresden.de"; + char RPKI_CACHE_PORT[] = "3323"; /* create a TCP transport socket */ struct tr_socket tr_tcp; - struct tr_tcp_config tcp_config = {RPKI_CACHE_HOST, RPKI_CACHE_POST, NULL, NULL, NULL, 0}; + struct tr_tcp_config tcp_config = {RPKI_CACHE_HOST, RPKI_CACHE_PORT, NULL, NULL, NULL, 0}; struct rtr_socket rtr_tcp; struct rtr_mgr_group groups[1]; From c7d58ab518167bcd613c09a40e6be911973b0a9e Mon Sep 17 00:00:00 2001 From: Nils Bars Date: Tue, 23 Apr 2024 14:58:10 +0200 Subject: [PATCH 13/55] Bugfix: Use the actual struct size and not the pointer size (#288) --- rtrlib/pfx/trie/trie.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtrlib/pfx/trie/trie.c b/rtrlib/pfx/trie/trie.c index b3598929..90242c77 100644 --- a/rtrlib/pfx/trie/trie.c +++ b/rtrlib/pfx/trie/trie.c @@ -193,7 +193,7 @@ static int append_node_to_array(struct trie_node ***ary, unsigned int *len, stru { struct trie_node **new; - new = lrtr_realloc(*ary, *len * sizeof(n)); + new = lrtr_realloc(*ary, *len * sizeof(*n)); if (!new) return -1; From 5911d1507e49be50388e08246b47c7e74c2c0367 Mon Sep 17 00:00:00 2001 From: maurim Date: Mon, 20 Nov 2023 16:03:04 +0100 Subject: [PATCH 14/55] Updates public rpki-cache and fixes pipline issues Motivation - get pipeline checks running How: - insert valid rpki-cache which runs also rtr - edit url in live_tests, README - modify changelog date in librtr.spec file => no changelog results in error, because all changes older than 2years are dismissed - add more output on failure for tests --- CMakeLists.txt | 1 + README.md | 4 ++-- redhat/SPECS/librtr.spec | 4 +++- tests/test_dynamic_groups.c | 17 ++++++++--------- tests/test_live_validation.c | 13 ++++++++++--- 5 files changed, 24 insertions(+), 15 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 856b651a..7e36ccfd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,6 +124,7 @@ ADD_SUBDIRECTORY(doxygen/examples) include(AddTest) ADD_SUBDIRECTORY(tests) ENABLE_TESTING() +list(APPEND CMAKE_CTEST_ARGUMENTS "--output-on-failure") ADD_TEST(test_pfx tests/test_pfx) ADD_TEST(test_trie tests/test_trie) #ADD_TEST(test_pfx_locks tests/test_pfx_locks) diff --git a/README.md b/README.md index d516eb3a..03d410da 100644 --- a/README.md +++ b/README.md @@ -129,8 +129,8 @@ RTR-Server using the rtrclient command line tool: bin/rtrclient tcp rpki-validator.realmv6.org 8282 -`rpki-validator.realmv6.org` is an open RTR-Server instance for testing -purposes, which runs the RIPE Validator. It listens on port 8282 and +`rpki-cache.netd.cs.tu-dresden.de` is an open RTR-Server instance for testing +purposes, which runs the RIPE Validator. It listens on port 3323 and validates ROAs from the following trust anchors: AfriNIC, APNIC, ARIN, LACNIC, RIPE. diff --git a/redhat/SPECS/librtr.spec b/redhat/SPECS/librtr.spec index e02d81e2..7c71ba76 100644 --- a/redhat/SPECS/librtr.spec +++ b/redhat/SPECS/librtr.spec @@ -136,7 +136,9 @@ cp %{_topdir}/BUILD/LICENSE %{buildroot}/%{_docdir}/rtrlib/ %doc LICENSE %changelog -* Mon Jan 17 2022 Martin Winter - %{version}-%{release} +# artificially modified date due to trimming all entries older than 2 years +# correct date: Mon Jan 17 2022 +* Mon Jan 17 2024 Martin Winter - %{version}-%{release} - Use cmake macros for builds on Fedora 33 and up and RedHat 9 and up - Fix missing SOURCES directory during rpmbuild on newer systems diff --git a/tests/test_dynamic_groups.c b/tests/test_dynamic_groups.c index c67bb35d..af67d619 100644 --- a/tests/test_dynamic_groups.c +++ b/tests/test_dynamic_groups.c @@ -8,7 +8,7 @@ #include #include -const int connection_timeout = 20; +const int connection_timeout = 60; enum rtr_mgr_status connection_status = -1; static void connection_status_callback(const struct rtr_mgr_group *group __attribute__((unused)), @@ -21,12 +21,12 @@ static void connection_status_callback(const struct rtr_mgr_group *group __attri int main(void) { - //create a TCP transport socket int retval = 0; - struct tr_socket tr_tcp; char tcp_host[] = "rpki-cache.netd.cs.tu-dresden.de"; char tcp_port[] = "3323"; + /* create a TCP transport socket */ + struct tr_socket tr_tcp; struct tr_tcp_config tcp_config = { tcp_host, //IP tcp_port, //Port @@ -35,14 +35,14 @@ int main(void) NULL, //new_socket() 0, // connect timeout }; - tr_tcp_init(&tcp_config, &tr_tcp); - struct rtr_socket rtr_tcp; - - rtr_tcp.tr_socket = &tr_tcp; - struct rtr_mgr_group groups[1]; + + /* init a TCP transport and create rtr socket */ + tr_tcp_init(&tcp_config, &tr_tcp); + rtr_tcp.tr_socket = &tr_tcp; + /* create a rtr_mr_group array with 1 element */ groups[0].sockets = malloc(sizeof(struct rtr_socket *)); groups[0].sockets_len = 1; groups[0].sockets[0] = &rtr_tcp; @@ -170,7 +170,6 @@ int main(void) //try to remove last remainig group. retval = rtr_mgr_remove_group(conf, 3); assert(retval == RTR_ERROR); - rtr_mgr_stop(conf); rtr_mgr_free(conf); free(groups[0].sockets); diff --git a/tests/test_live_validation.c b/tests/test_live_validation.c index c3cdbcaf..c44fbf5b 100644 --- a/tests/test_live_validation.c +++ b/tests/test_live_validation.c @@ -34,7 +34,7 @@ const struct test_validity_query queries[] = {{"93.175.146.0", 24, 12654, BGP_PF {"2001:7fb:ff03::", 48, 12654, BGP_PFXV_STATE_NOT_FOUND}, {NULL, 0, 0, 0} }; -const int connection_timeout = 20; +const int connection_timeout = 60; enum rtr_mgr_status connection_status = -1; static void connection_status_callback(const struct rtr_mgr_group *group __attribute__((unused)), @@ -58,11 +58,18 @@ int main(void) * because it would cause warnings about discarding constness */ char RPKI_CACHE_HOST[] = "rpki-cache.netd.cs.tu-dresden.de"; - char RPKI_CACHE_PORT[] = "3323"; + char RPKI_CACHE_POST[] = "3323"; /* create a TCP transport socket */ struct tr_socket tr_tcp; - struct tr_tcp_config tcp_config = {RPKI_CACHE_HOST, RPKI_CACHE_PORT, NULL, NULL, NULL, 0}; + struct tr_tcp_config tcp_config = { + RPKI_CACHE_HOST, //IP + RPKI_CACHE_POST, //Port + NULL, //source address + NULL, //data + NULL, //new_socket() + 0, //connection timeout + }; struct rtr_socket rtr_tcp; struct rtr_mgr_group groups[1]; From 3e4f635a2f25df8b5da00f5788b4ae15e6234dd0 Mon Sep 17 00:00:00 2001 From: maurim Date: Tue, 2 Apr 2024 20:32:51 +0200 Subject: [PATCH 15/55] [FIX] Bit selection error for trie building Motivation: - test cases for arm7 Ubuntu18.04 and ppc64le Ubuntu18.04 failed - pipeline results in fail after commit - minor improvements for uniform build up live_tests How: - trie building is based on address (binary-trie) - getting a single bit for IPv6 addresses has been error prone for bit_postion > 95 - unsure why other distributions did not fail => may be due to different endianess --- CMakeLists.txt | 2 +- redhat/SPECS/librtr.spec | 2 +- rtrlib/lib/ipv6.c | 2 +- tests/test_dynamic_groups.c | 4 ++-- tests/test_live_validation.c | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e36ccfd..6a4de639 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,7 +124,6 @@ ADD_SUBDIRECTORY(doxygen/examples) include(AddTest) ADD_SUBDIRECTORY(tests) ENABLE_TESTING() -list(APPEND CMAKE_CTEST_ARGUMENTS "--output-on-failure") ADD_TEST(test_pfx tests/test_pfx) ADD_TEST(test_trie tests/test_trie) #ADD_TEST(test_pfx_locks tests/test_pfx_locks) @@ -139,6 +138,7 @@ ADD_TEST(test_ipaddr tests/test_ipaddr) ADD_TEST(test_getbits tests/test_getbits) ADD_TEST(test_dynamic_groups tests/test_dynamic_groups) +list(APPEND CMAKE_CTEST_ARGUMENTS "--output-on-failure") if(RTRLIB_BGPSEC_ENABLED) ADD_TEST(test_bgpsec tests/test_bgpsec) diff --git a/redhat/SPECS/librtr.spec b/redhat/SPECS/librtr.spec index 7c71ba76..9dea77f2 100644 --- a/redhat/SPECS/librtr.spec +++ b/redhat/SPECS/librtr.spec @@ -138,7 +138,7 @@ cp %{_topdir}/BUILD/LICENSE %{buildroot}/%{_docdir}/rtrlib/ %changelog # artificially modified date due to trimming all entries older than 2 years # correct date: Mon Jan 17 2022 -* Mon Jan 17 2024 Martin Winter - %{version}-%{release} +* Mon Jan 17 2024 Martin Winter - %%{version}-%%{release} - Use cmake macros for builds on Fedora 33 and up and RedHat 9 and up - Fix missing SOURCES directory during rpmbuild on newer systems diff --git a/rtrlib/lib/ipv6.c b/rtrlib/lib/ipv6.c index 5034c3c3..34bd8caa 100644 --- a/rtrlib/lib/ipv6.c +++ b/rtrlib/lib/ipv6.c @@ -67,7 +67,7 @@ struct lrtr_ipv6_addr lrtr_ipv6_get_bits(const struct lrtr_ipv6_addr *val, const } if ((first_bit <= 127) && ((first_bit + quantity) > 96)) { - const uint8_t fr = first_bit < 96 ? 0 : first_bit - 127; + const uint8_t fr = first_bit < 96 ? 0 : first_bit - 96; const uint8_t q = bits_left > 32 ? 32 : bits_left; assert(bits_left >= q); diff --git a/tests/test_dynamic_groups.c b/tests/test_dynamic_groups.c index af67d619..ae511ca6 100644 --- a/tests/test_dynamic_groups.c +++ b/tests/test_dynamic_groups.c @@ -8,7 +8,7 @@ #include #include -const int connection_timeout = 60; +const int connection_timeout = 80; enum rtr_mgr_status connection_status = -1; static void connection_status_callback(const struct rtr_mgr_group *group __attribute__((unused)), @@ -37,7 +37,7 @@ int main(void) }; struct rtr_socket rtr_tcp; struct rtr_mgr_group groups[1]; - + /* init a TCP transport and create rtr socket */ tr_tcp_init(&tcp_config, &tr_tcp); rtr_tcp.tr_socket = &tr_tcp; diff --git a/tests/test_live_validation.c b/tests/test_live_validation.c index c44fbf5b..b90754c4 100644 --- a/tests/test_live_validation.c +++ b/tests/test_live_validation.c @@ -34,7 +34,7 @@ const struct test_validity_query queries[] = {{"93.175.146.0", 24, 12654, BGP_PF {"2001:7fb:ff03::", 48, 12654, BGP_PFXV_STATE_NOT_FOUND}, {NULL, 0, 0, 0} }; -const int connection_timeout = 60; +const int connection_timeout = 80; enum rtr_mgr_status connection_status = -1; static void connection_status_callback(const struct rtr_mgr_group *group __attribute__((unused)), From e5aaf073d63ddfcdc1827cd8e03776439ec1df25 Mon Sep 17 00:00:00 2001 From: maurim Date: Mon, 6 May 2024 15:30:15 +0200 Subject: [PATCH 16/55] [FIX] Building with strict aliasing Motivation - building with strict aliasing flags fails - used flags shown below ``` set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -flto=4 -Werror=odr -Werror=lto-type-mismatch -Werror=strict-aliasing") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_FORTIFY_SOURCE=3 -fdiagnostics-color=always -frecord-gcc-switches") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fstack-clash-protection -march=native -O2 -pipe -U_FORTIFY_SOURCE") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Werror=format-security -Werror=implicit-int -Werror=int-conversion -Wformat") ``` How: - modify casted variable type in test case to initialized type Related: - fixes #287 --- tests/test_pfx.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/test_pfx.c b/tests/test_pfx.c index e7f26512..cb5a2873 100644 --- a/tests/test_pfx.c +++ b/tests/test_pfx.c @@ -128,8 +128,8 @@ static void mass_test(void) pfx.min_len = 128; pfx.max_len = 128; pfx.prefix.ver = LRTR_IPV6; - ((uint64_t *)pfx.prefix.u.addr6.addr)[1] = max_i; - ((uint64_t *)pfx.prefix.u.addr6.addr)[0] = min_i + i; + ((uint32_t *)pfx.prefix.u.addr6.addr)[2] = max_i; + ((uint32_t *)pfx.prefix.u.addr6.addr)[0] = min_i + i; assert(pfx_table_add(&pfxt, &pfx) == PFX_SUCCESS); } @@ -148,8 +148,8 @@ static void mass_test(void) pfx.min_len = 128; pfx.max_len = 128; pfx.prefix.ver = LRTR_IPV6; - ((uint64_t *)pfx.prefix.u.addr6.addr)[1] = max_i; - ((uint64_t *)pfx.prefix.u.addr6.addr)[0] = min_i + i; + ((uint32_t *)pfx.prefix.u.addr6.addr)[2] = max_i; + ((uint32_t *)pfx.prefix.u.addr6.addr)[0] = min_i + i; assert(pfx_table_validate(&pfxt, i + 1, &pfx.prefix, pfx.min_len, &res) == PFX_SUCCESS); assert(res == BGP_PFXV_STATE_VALID); @@ -172,8 +172,8 @@ static void mass_test(void) pfx.prefix.ver = LRTR_IPV6; pfx.min_len = 128; pfx.max_len = 128; - ((uint64_t *)pfx.prefix.u.addr6.addr)[1] = max_i; - ((uint64_t *)pfx.prefix.u.addr6.addr)[0] = min_i + i; + ((uint32_t *)pfx.prefix.u.addr6.addr)[2] = max_i; + ((uint32_t *)pfx.prefix.u.addr6.addr)[0] = min_i + i; assert(pfx_table_remove(&pfxt, &pfx) == PFX_SUCCESS); } From fb3f8e440a1c3080b90920ebfcaba9f3a937eef2 Mon Sep 17 00:00:00 2001 From: revol-xut Date: Wed, 31 Jan 2024 09:28:41 +0100 Subject: [PATCH 17/55] rtr: add support for aspa pdus - add support for rtrv2 including aspa pdus - move rtr pdus to separate header - refactor undo-update logic - add aspa in-place and swap-in update mechanism Co-authored-by: mrzslz Co-authored-by: carl <115627588+carl-tud@users.noreply.github.com> --- rtrlib/rtr/packets.c | 1131 ++++++++++++++++++++++------------ rtrlib/rtr/packets_private.h | 4 +- rtrlib/rtr/rtr.c | 14 +- rtrlib/rtr/rtr.h | 2 + rtrlib/rtr/rtr_pdus.h | 157 +++++ rtrlib/rtr/rtr_private.h | 12 +- 6 files changed, 914 insertions(+), 406 deletions(-) create mode 100644 rtrlib/rtr/rtr_pdus.h diff --git a/rtrlib/rtr/packets.c b/rtrlib/rtr/packets.c index f16d6d32..f2fa7261 100644 --- a/rtrlib/rtr/packets.c +++ b/rtrlib/rtr/packets.c @@ -9,14 +9,17 @@ #include "packets_private.h" +#include "rtrlib/aspa/aspa_private.h" #include "rtrlib/config.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_pdus.h" #include "rtrlib/rtr/rtr_private.h" #include "rtrlib/spki/hashtable/ht-spkitable_private.h" +#include "rtrlib/spki/spkitable.h" #include "rtrlib/transport/transport_private.h" #ifdef RTRLIB_BGPSEC_ENABLED #include "rtrlib/bgpsec/bgpsec_utils_private.h" @@ -32,153 +35,28 @@ #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 | - * | | - * `-------------------------------------------' +/** + * @brief @c ASPA_UPDATE_MECHANISM macro sets the mechanism used in this file to update the ASPA table. + * @see aspa/aspa_private.h */ -struct pdu_reset_query { - uint8_t ver; - uint8_t type; - uint16_t flags; - uint32_t len; -}; +#define ASPA_IN_PLACE 'P' +#define ASPA_SWAP_IN 'S' +#define ASPA_UPDATE_MECHANISM ASPA_SWAP_IN -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 aspa_pdu_list_node { + struct pdu_aspa *pdu; + struct aspa_pdu_list_node *next; }; struct recv_loop_cleanup_args { - struct pdu_ipv4 *ipv4_pdus; - struct pdu_ipv6 *ipv6_pdus; - struct pdu_router_key *router_key_pdus; + struct pdu_ipv4 **ipv4_pdus; + struct pdu_ipv6 **ipv6_pdus; + struct pdu_router_key **router_key_pdus; + struct pdu_aspa ***aspa_pdus; + size_t *aspa_pdu_count; }; +//static void recv_loop_cleanup(struct recv_loop_cleanup_args *args); 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, @@ -192,6 +70,11 @@ static int rtr_send_error_pdu_from_host(const struct rtr_socket *rtr_socket, con static int interval_send_error_pdu(struct rtr_socket *rtr_socket, void *pdu, uint32_t interval, uint16_t minimum, uint32_t maximum); +static bool rtr_version_supported(uint8_t version) +{ + return RTR_PROTOCOL_MIN_SUPPORTED_VERSION <= version && RTR_PROTOCOL_MAX_SUPPORTED_VERSION >= version; +} + static inline enum pdu_type rtr_get_pdu_type(const void *pdu) { return *((char *)pdu + 1); @@ -264,8 +147,7 @@ int rtr_check_interval_option(struct rtr_socket *rtr_socket, int interval_mode, else apply_interval_value(rtr_socket, maximum, type); } else { - RTR_DBG("Received expiration value out of range. Was %u. It will be ignored.", - interval); + RTR_DBG("Received expiration value out of range. Was %u. It will be ignored.", interval); } return RTR_SUCCESS; @@ -332,18 +214,18 @@ static void rtr_pdu_convert_footer_byte_order(void *pdu, const enum target_byte_ 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); + if (header->ver == RTR_PROTOCOL_VERSION_1 || header->ver == RTR_PROTOCOL_VERSION_2) { + ((struct pdu_end_of_data_v1_v2 *)pdu)->expire_interval = lrtr_convert_long( + target_byte_order, ((struct pdu_end_of_data_v1_v2 *)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_v2 *)pdu)->refresh_interval = lrtr_convert_long( + target_byte_order, ((struct pdu_end_of_data_v1_v2 *)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_v2 *)pdu)->retry_interval = lrtr_convert_long( + target_byte_order, ((struct pdu_end_of_data_v1_v2 *)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); + ((struct pdu_end_of_data_v1_v2 *)pdu)->sn = + lrtr_convert_long(target_byte_order, ((struct pdu_end_of_data_v1_v2 *)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); @@ -367,6 +249,24 @@ static void rtr_pdu_convert_footer_byte_order(void *pdu, const enum target_byte_ lrtr_convert_long(target_byte_order, ((struct pdu_router_key *)pdu)->asn); break; + case ASPA: + ((struct pdu_aspa *)pdu)->provider_count = + lrtr_convert_short(target_byte_order, ((struct pdu_aspa *)pdu)->provider_count); + ((struct pdu_aspa *)pdu)->customer_asn = + lrtr_convert_long(target_byte_order, ((struct pdu_aspa *)pdu)->customer_asn); + + uint16_t asn_count = ((struct pdu_aspa *)pdu)->provider_count; + + // prevent converting twice + if (target_byte_order != TO_HOST_HOST_BYTE_ORDER) + asn_count = lrtr_convert_short(TO_HOST_HOST_BYTE_ORDER, asn_count); + + for (size_t i = 0; i < asn_count; i++) { + ((struct pdu_aspa *)pdu)->provider_asns[i] = + lrtr_convert_long(target_byte_order, ((struct pdu_aspa *)pdu)->provider_asns[i]); + } + break; + default: break; } @@ -398,6 +298,11 @@ static void rtr_pdu_header_to_host_byte_order(void *pdu) rtr_pdu_convert_header_byte_order(pdu, TO_HOST_HOST_BYTE_ORDER); } +static size_t rtr_size_of_aspa_pdu(const struct pdu_aspa *pdu) +{ + return sizeof(struct pdu_aspa) + sizeof(*pdu->provider_asns) * pdu->provider_count; +} + /* * Check if the PDU is big enough for the PDU type it * pretend to be. @@ -408,8 +313,9 @@ 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; + const struct pdu_aspa *aspa_pdu = NULL; bool retval = false; - uint64_t min_size = 0; + size_t expected_size = 0; switch (type) { case SERIAL_NOTIFY: @@ -430,7 +336,8 @@ static bool rtr_pdu_check_size(const struct pdu_header *pdu) 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))) { + ((pdu->ver == RTR_PROTOCOL_VERSION_1 || pdu->ver == RTR_PROTOCOL_VERSION_2) && + (sizeof(struct pdu_end_of_data_v1_v2) == pdu->len))) { retval = true; } break; @@ -445,8 +352,8 @@ static bool rtr_pdu_check_size(const struct pdu_header *pdu) 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) { + expected_size = 4 + sizeof(struct pdu_error); + if (err_pdu->len < expected_size) { RTR_DBG1("PDU is too small to contain \"Length of Error Text\" field!"); break; } @@ -455,8 +362,8 @@ static bool rtr_pdu_check_size(const struct pdu_header *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) { + expected_size += enc_pdu_len; + if (err_pdu->len < expected_size) { RTR_DBG1("PDU is too small to contain erroneous PDU!"); break; } @@ -465,8 +372,8 @@ static bool rtr_pdu_check_size(const struct pdu_header *pdu) 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) { + expected_size += err_msg_len; + if (err_pdu->len != expected_size) { RTR_DBG1("PDU is too small to contain error_msg!"); break; } @@ -481,6 +388,26 @@ static bool rtr_pdu_check_size(const struct pdu_header *pdu) if (sizeof(struct pdu_reset_query) == pdu->len) retval = true; break; + case ASPA: + aspa_pdu = (const struct pdu_aspa *)pdu; + expected_size = sizeof(struct pdu_aspa); + if (aspa_pdu->len < expected_size) { + RTR_DBG1("PDU is too small to contain ASPA PDU!"); + break; + } + + // Check if the PDU really contains the ASPA PDU + uint16_t asn_count = ntohs(aspa_pdu->provider_count); + + // ASN is 4 bytes each + expected_size += asn_count * sizeof(aspa_pdu->provider_asns[0]); + if (aspa_pdu->len != expected_size) { + RTR_DBG1("PDU is too small to contain valid ASPA PDU!"); + break; + } + + retval = true; + break; case RESERVED: default: RTR_DBG1("PDU type is unknown or reserved!"); @@ -561,10 +488,10 @@ static int rtr_receive_pdu(struct rtr_socket *rtr_socket, void *pdu, const size_ // 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; + if (header.type != ERROR && rtr_socket->version > header.ver && rtr_version_supported(header.ver)) { + RTR_DBG("First received PDU is a version %u PDU, downgrading from version %u to %u", header.ver, + rtr_socket->version, header.ver); + rtr_socket->version = header.ver; } rtr_socket->has_received_pdus = true; } @@ -609,6 +536,18 @@ static int rtr_receive_pdu(struct rtr_socket *rtr_socket, void *pdu, const size_ if (((struct pdu_ipv4 *)pdu)->zero != 0) RTR_DBG1("Warning: Zero field of received Prefix PDU doesn't contain 0"); } + if (header.type == ASPA) { + if (((struct pdu_aspa *)pdu)->ver != RTR_PROTOCOL_VERSION_2) { + error = UNSUPPORTED_PROTOCOL_VER; + goto error; + } + + if (((struct pdu_aspa *)pdu)->zero != 0) + RTR_DBG1("Warning: Zero field of received ASPA PDU doesn't contain 0"); + + if (((struct pdu_aspa *)pdu)->afi_flags != 0b11) + RTR_DBG1("Warning: AFI flags of received ASPA PDU not set to 0x03"); + } if (header.type == ROUTER_KEY && ((struct pdu_router_key *)pdu)->zero != 0) RTR_DBG1("Warning: ROUTER_KEY_PDU zero field is != 0"); @@ -632,9 +571,9 @@ static int rtr_receive_pdu(struct rtr_socket *rtr_socket, void *pdu, const size_ 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]; + char txt[43]; - snprintf(txt, sizeof(txt), "PDU too big, max. PDU size is: %u bytes", RTR_MAX_PDU_LEN); + snprintf(txt, sizeof(txt), "PDU too big, max. PDU size is: %lu 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) { @@ -775,9 +714,19 @@ static void rtr_prefix_pdu_2_pfx_record(const struct rtr_socket *rtr_socket, con } } -/* - * @brief Removes all Prefix from the pfx_table with flag field == ADD, ADDs all Prefix PDU to the pfx_table with flag - * field == REMOVE. +__attribute__((always_inline)) inline void rtr_aspa_pdu_2_aspa_operation(struct pdu_aspa *pdu, + struct aspa_update_operation *op) +{ + op->type = (pdu->flags & 1) == 1 ? ASPA_ADD : ASPA_REMOVE; + op->is_no_op = false; + op->record.customer_asn = pdu->customer_asn; + op->record.provider_count = (op->type == ASPA_ADD) ? pdu->provider_count : 0; + op->record.provider_asns = (op->type == ASPA_ADD) ? pdu->provider_asns : NULL; +} + +/** + * @brief Removes all prefixes from the @p pfx_table with flag field == ADD, adds all prefixes + * to the @p pfx_table with if flag field == REMOVE. */ static int rtr_undo_update_pfx_table(struct rtr_socket *rtr_socket, struct pfx_table *pfx_table, void *pdu) { @@ -798,9 +747,43 @@ static int rtr_undo_update_pfx_table(struct rtr_socket *rtr_socket, struct pfx_t 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. +/** + * @brief Removes all prefixes from multiple PDUs from the @p pfx_table with flag field == ADD, adds all prefixes + * to the @p pfx_table if flag field == REMOVE. + */ +static int rtr_undo_update_pfx_table_batch(struct rtr_socket *rtr_socket, struct pfx_table *pfx_table, + struct pdu_ipv4 *ipv4_pdus, size_t ipv4_pdu_count, + struct pdu_ipv6 *ipv6_pdus, size_t ipv6_pdu_count) +{ + for (size_t i = 0; i < ipv4_pdu_count; i++) { + int res = rtr_undo_update_pfx_table(rtr_socket, pfx_table, &(ipv4_pdus[i])); + + if (res == RTR_ERROR || res == PFX_ERROR) { + // Undo failed, cannot recover, remove all records associated with the socket instead + RTR_DBG1( + "Couldn't undo all update operations from failed data synchronisation: Purging all prefix records"); + pfx_table_src_remove(pfx_table, rtr_socket); + return RTR_ERROR; + } + } + + for (size_t i = 0; i < ipv6_pdu_count; i++) { + int res = rtr_undo_update_pfx_table(rtr_socket, pfx_table, &(ipv6_pdus[i])); + + if (res == RTR_ERROR || res == PFX_ERROR) { + // Undo failed, cannot recover, remove all records associated with the socket instead + RTR_DBG1( + "Couldn't undo all update operations from failed data synchronisation: Purging all prefix records"); + pfx_table_src_remove(pfx_table, rtr_socket); + return RTR_ERROR; + } + } + return RTR_SUCCESS; +} + +/** + * @brief Removes router key from the @p spki_table with flag field == ADD, adds router keys + * to the @p spki_table if flag field == REMOVE. */ static int rtr_undo_update_spki_table(struct rtr_socket *rtr_socket, struct spki_table *spki_table, void *pdu) { @@ -821,11 +804,32 @@ static int rtr_undo_update_spki_table(struct rtr_socket *rtr_socket, struct spki return rtval; } -/* +/** + * @brief Removes router key from multiple PDUs from the @p spki_table with flag field == ADD, adds router keys + * to the @p spki_table if flag field == REMOVE. + */ +static int rtr_undo_update_spki_table_batch(struct rtr_socket *rtr_socket, struct spki_table *spki_table, + struct pdu_router_key *pdus, size_t pdu_count) +{ + for (size_t i = 0; i < pdu_count; i++) { + int res = rtr_undo_update_spki_table(rtr_socket, spki_table, &(pdus[i])); + + if (res == RTR_ERROR || res == SPKI_ERROR) { + // Undo failed, cannot recover, remove all records associated with the socket instead + RTR_DBG1( + "Couldn't undo all update operations from failed data synchronisation: Purging all SPKI records"); + spki_table_src_remove(spki_table, rtr_socket); + return RTR_ERROR; + } + } + return RTR_SUCCESS; +} + +/** * @brief Appends the Prefix PDU pdu to ary. * - * @return RTR_SUCCESS On success - * @return RTR_ERROR On realloc failure + * @return @c RTR_SUCCESS On success + * @return @c 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, @@ -834,7 +838,7 @@ static int rtr_store_prefix_pdu(struct rtr_socket *rtr_socket, const void *pdu, const enum pdu_type type = rtr_get_pdu_type(pdu); assert(type == IPV4_PREFIX || type == IPV6_PREFIX); - if ((*ind) >= *size) { + if (*ind >= *size) { *size += TEMPORARY_PDU_STORE_INCREMENT_VALUE; void *tmp = lrtr_realloc(*ary, *size * pdu_size); @@ -861,11 +865,11 @@ static int rtr_store_prefix_pdu(struct rtr_socket *rtr_socket, const void *pdu, return RTR_SUCCESS; } -/* - * @brief Appends the router key to ary. +/** + * @brief Appends the router key to @p ary. * - * @return RTR_SUCCESS On success - * @return RTR_ERROR On realloc failure + * @return @c RTR_SUCCESS On success + * @return @c 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, @@ -873,7 +877,7 @@ static int rtr_store_router_key_pdu(struct rtr_socket *rtr_socket, const void *p { assert(rtr_get_pdu_type(pdu) == ROUTER_KEY); - if ((*ind) >= *size) { + if (*ind >= *size) { *size += TEMPORARY_PDU_STORE_INCREMENT_VALUE; void *tmp = lrtr_realloc(*ary, *size * pdu_size); @@ -893,6 +897,48 @@ static int rtr_store_router_key_pdu(struct rtr_socket *rtr_socket, const void *p return RTR_SUCCESS; } +/** + * @brief Stores the ASPA pdu's contents in an ASPA operation in the given operations array. + * + * @return @c RTR_SUCCESS On success + * @return @c RTR_ERROR On realloc failure + * @attention @c pdu_array not freed in this case, because it might contain data that is still needed + */ +static int rtr_store_aspa_pdu(struct rtr_socket *rtr_socket, const struct pdu_aspa *pdu, struct pdu_aspa ***array, + size_t *count, size_t *capacity) +{ + if (*count >= *capacity) { + *capacity += TEMPORARY_PDU_STORE_INCREMENT_VALUE; + void *tmp = lrtr_realloc(*array, *capacity * sizeof(struct pdu_aspa *)); + + 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; + } + *array = tmp; + } + size_t pdu_size = rtr_size_of_aspa_pdu(pdu); + struct pdu_aspa *copy = lrtr_malloc(pdu_size); + + if (!copy) { + const char txt[] = "Malloc 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; + } + + memcpy(copy, pdu, pdu_size); + *(*array + *count) = copy; + (*count)++; + 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); @@ -906,9 +952,9 @@ static int rtr_update_pfx_table(struct rtr_socket *rtr_socket, struct pfx_table int rtval; - if (((struct pdu_ipv4 *)pdu)->flags == 1) { + if (((struct pdu_ipv4 *)pdu)->flags == 1) { // add record rtval = pfx_table_add(pfx_table, &pfxr); - } else if (((struct pdu_ipv4 *)pdu)->flags == 0) { + } else if (((struct pdu_ipv4 *)pdu)->flags == 0) { // remove record rtval = pfx_table_remove(pfx_table, &pfxr); } else { const char txt[] = "Prefix PDU with invalid flags value received"; @@ -995,19 +1041,397 @@ static int rtr_update_spki_table(struct rtr_socket *rtr_socket, struct spki_tabl return RTR_SUCCESS; } +#if ASPA_UPDATE_MECHANISM == ASPA_SWAP_IN +static int rtr_compute_update_aspa_table(struct rtr_socket *rtr_socket, struct aspa_table *aspa_table, + struct pdu_aspa **aspa_pdus, size_t pdu_count, struct aspa_update **update) +{ + // Fail hard in debug builds. + assert(rtr_socket); + assert(aspa_table); + assert(update); + + if (!update || (pdu_count > 0 && !aspa_pdus)) + return RTR_ERROR; + + if (pdu_count == 0) + return RTR_SUCCESS; + + struct aspa_update_operation *operations = lrtr_malloc(sizeof(struct aspa_update_operation) * pdu_count); + + if (!operations) { + const char txt[] = "Malloc 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; + } + + for (size_t i = 0; i < pdu_count; i++) { + rtr_aspa_pdu_2_aspa_operation(aspa_pdus[i], &operations[i]); + operations[i].index = i; + } + + enum aspa_status res = aspa_table_update_swap_in_compute(aspa_table, rtr_socket, operations, pdu_count, update); + + if (*update && (*update)->failed_operation) { + struct pdu_aspa *pdu = aspa_pdus[(*update)->failed_operation->index]; + size_t pdu_size = rtr_size_of_aspa_pdu(pdu); + + if (res == ASPA_DUPLICATE_RECORD) { + RTR_DBG("Duplicate Announcement for ASPA customer ASN: %u received", pdu->customer_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 (res == ASPA_RECORD_NOT_FOUND) { + RTR_DBG("Withdrawal of unknown ASPA customer ASN: %u", pdu->customer_asn); + 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 (res != ASPA_SUCCESS) { + const char txt[] = "aspa_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; + } + + RTR_DBG1("ASPA update computed"); + return RTR_SUCCESS; +} +#endif + +#if ASPA_UPDATE_MECHANISM == ASPA_IN_PLACE +/** + * @brief Undoes changes made to the given @p aspa_table based on an array of @c aspa_update_operation s. + */ +static int rtr_undo_update_aspa_table(struct rtr_socket *rtr_socket, struct aspa_table *aspa_table, + struct aspa_update_operation *operations, size_t count, + struct aspa_update_operation *failed_operation) +{ + enum aspa_status res = + aspa_table_update_in_place_undo(aspa_table, rtr_socket, operations, count, failed_operation); + + if (res == ASPA_SUCCESS) { + return RTR_SUCCESS; + } else { + // Undo failed, cannot recover, remove all records associated with the socket instead + RTR_DBG1( + "Couldn't undo all update operations from failed data synchronisation: Purging all ASPA records"); + aspa_table_src_remove(aspa_table, rtr_socket, true); + return RTR_ERROR; + } +} + +static int rtr_update_aspa_table(struct rtr_socket *rtr_socket, struct aspa_table *aspa_table, + struct pdu_aspa **aspa_pdus, size_t pdu_count, struct aspa_update_operation **ops, + struct aspa_update_operation **failed_operation) +{ + // Fail hard in debug builds. + assert(rtr_socket); + assert(aspa_table); + assert(failed_operation); + assert(ops); + + if (!ops || !failed_operation || (pdu_count > 0 && !aspa_pdus)) + return RTR_ERROR; + + if (!aspa_pdus && pdu_count == 0) + return RTR_SUCCESS; + + struct aspa_update_operation *operations = lrtr_malloc(sizeof(struct aspa_update_operation) * pdu_count); + + if (!operations) { + const char txt[] = "Malloc 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; + } + + *ops = operations; + + for (size_t i = 0; i < pdu_count; i++) { + rtr_aspa_pdu_2_aspa_operation(aspa_pdus[i], &operations[i]); + operations[i].index = i; + } + + enum aspa_status res = + aspa_table_update_in_place(aspa_table, rtr_socket, operations, pdu_count, failed_operation); + + if (*failed_operation) { + struct pdu_aspa *pdu = aspa_pdus[(*failed_operation)->index]; + size_t pdu_size = rtr_size_of_aspa_pdu(pdu); + + if (res == ASPA_DUPLICATE_RECORD) { + RTR_DBG("Duplicate Announcement for ASPA customer ASN: %u received", pdu->customer_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 (res == ASPA_RECORD_NOT_FOUND) { + RTR_DBG("Withdrawal of unknown ASPA customer ASN: %u", pdu->customer_asn); + 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 (res != ASPA_SUCCESS) { + const char txt[] = "aspa_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; +} +#endif + +static int rtr_sync_update_tables(struct rtr_socket *rtr_socket, struct pfx_table *pfx_table, + struct spki_table *spki_table, struct aspa_table *aspa_table, + struct pdu_ipv4 *ipv4_pdus, const unsigned int ipv4_pdu_count, + struct pdu_ipv6 *ipv6_pdus, const unsigned int ipv6_pdu_count, + struct pdu_router_key *router_key_pdus, const unsigned int router_key_pdu_count, + struct pdu_aspa **aspa_pdus, const size_t aspa_pdu_count, + struct pdu_end_of_data_v0 *eod_pdu) +{ + bool proceed = true; + bool undo_failed = false; +#if ASPA_UPDATE_MECHANISM == ASPA_SWAP_IN + struct aspa_update *aspa_update = NULL; +#elif ASPA_UPDATE_MECHANISM == ASPA_IN_PLACE + struct aspa_update_operation *aspa_failed_operation = NULL; + struct aspa_update_operation *aspa_operations = NULL; +#else +#error "Invalid ASPA_UPDATE_MECHANISM value." +#endif /* ASPA_UPDATE_MECHANISM */ + + // add all IPv4 prefix pdu to the pfx_table + for (size_t i = 0; i < ipv4_pdu_count; i++) { + if (rtr_update_pfx_table(rtr_socket, pfx_table, &(ipv4_pdus[i])) == RTR_ERROR) { + RTR_DBG1("error while updating v4 prefixes"); + proceed = false; + + if (rtr_undo_update_pfx_table_batch(rtr_socket, pfx_table, ipv4_pdus, i, ipv6_pdus, 0) == + RTR_ERROR) + undo_failed = true; + + break; + } + } + + if (proceed) { + RTR_DBG1("v4 prefixes added"); + + // add all IPv6 prefix pdu to the pfx_table + for (size_t i = 0; i < ipv6_pdu_count; i++) { + if (rtr_update_pfx_table(rtr_socket, pfx_table, &(ipv6_pdus[i])) == RTR_ERROR) { + RTR_DBG1("error while updating v6 prefixes"); + proceed = false; + + if (rtr_undo_update_pfx_table_batch(rtr_socket, pfx_table, ipv4_pdus, ipv4_pdu_count, + ipv6_pdus, i) == RTR_ERROR) + undo_failed = true; + + break; + } + } + } + + if (proceed) { + RTR_DBG1("v6 prefixes added"); + + // add all router key pdu to the spki_table + for (size_t i = 0; i < router_key_pdu_count; i++) { + if (rtr_update_spki_table(rtr_socket, spki_table, &(router_key_pdus[i])) == RTR_ERROR) { + RTR_DBG1("error while updating spki data"); + proceed = false; + + if (rtr_undo_update_spki_table_batch(rtr_socket, spki_table, router_key_pdus, i) == + RTR_ERROR) + undo_failed = true; + + if (rtr_undo_update_pfx_table_batch(rtr_socket, pfx_table, ipv4_pdus, ipv4_pdu_count, + ipv6_pdus, ipv6_pdu_count) == RTR_ERROR) + undo_failed = true; + + break; + } + } + } + +#if ASPA_UPDATE_MECHANISM == ASPA_SWAP_IN + if (proceed) { + RTR_DBG1("spki data added"); + + if (rtr_compute_update_aspa_table(rtr_socket, aspa_table, aspa_pdus, aspa_pdu_count, &aspa_update) == + RTR_ERROR) { + RTR_DBG1("error while computing ASPA update"); + proceed = false; + + if (rtr_undo_update_spki_table_batch(rtr_socket, spki_table, router_key_pdus, + router_key_pdu_count) == RTR_ERROR) + undo_failed = true; + + if (rtr_undo_update_pfx_table_batch(rtr_socket, pfx_table, ipv4_pdus, ipv4_pdu_count, ipv6_pdus, + ipv6_pdu_count) == RTR_ERROR) + undo_failed = true; + } + } + + if (proceed) { + aspa_table_update_swap_in_apply(&aspa_update); + RTR_DBG1("ASPA data added"); + } else { + aspa_table_update_swap_in_discard(&aspa_update); + } + +#elif ASPA_UPDATE_MECHANISM == ASPA_IN_PLACE + if (proceed) { + RTR_DBG1("spki data added"); + + if (rtr_update_aspa_table(rtr_socket, aspa_table, aspa_pdus, aspa_pdu_count, &aspa_operations, + &aspa_failed_operation) == RTR_ERROR) { + RTR_DBG1("error while updating ASPA data"); + proceed = false; + + if (rtr_undo_update_aspa_table(rtr_socket, aspa_table, aspa_operations, aspa_pdu_count, + aspa_failed_operation) == RTR_ERROR) + undo_failed = true; + + if (rtr_undo_update_spki_table_batch(rtr_socket, spki_table, router_key_pdus, + router_key_pdu_count) == RTR_ERROR) + undo_failed = true; + + if (rtr_undo_update_pfx_table_batch(rtr_socket, pfx_table, ipv4_pdus, ipv4_pdu_count, ipv6_pdus, + ipv6_pdu_count) == RTR_ERROR) + undo_failed = true; + } + } + + if (proceed) + RTR_DBG1("ASPA data added"); + + aspa_table_update_in_place_cleanup(&aspa_operations, aspa_pdu_count); + aspa_failed_operation = NULL; +#else +#error "Invalid ASPA_UPDATE_MECHANISM value." +#endif /* ASPA_UPDATE_MECHANISM */ + + // An update attempted above failed + if (!proceed) { + if (undo_failed) + // undo failed, so request new session + rtr_socket->request_session_id = true; + + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + return RTR_ERROR; + } + + rtr_socket->serial_number = eod_pdu->sn; + RTR_DBG("Sync successful, received %u Prefix PDUs, %u Router Key PDUs, %lu ASPA PDUs, session_id: %u, SN: %u", + (ipv4_pdu_count + ipv6_pdu_count), router_key_pdu_count, aspa_pdu_count, rtr_socket->session_id, + rtr_socket->serial_number); + + return RTR_SUCCESS; +} + +static inline int rtr_handle_eod_pdu(struct rtr_socket *rtr_socket, struct pdu_end_of_data_v0 *eod_pdu, char pdu_data[]) +{ + 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_data, RTR_MAX_PDU_LEN, CORRUPT_DATA, txt, strlen(txt) + 1); + rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); + return RTR_ERROR; + } + + if ((eod_pdu->ver == RTR_PROTOCOL_VERSION_1 || eod_pdu->ver == RTR_PROTOCOL_VERSION_2) && + 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_v2 *)pdu_data)->expire_interval, + RTR_INTERVAL_TYPE_EXPIRATION); + + if (interv_retval == RTR_ERROR) { + interval_send_error_pdu(rtr_socket, pdu_data, + ((struct pdu_end_of_data_v1_v2 *)pdu_data)->expire_interval, + RTR_EXPIRATION_MIN, RTR_EXPIRATION_MAX); + return RTR_ERROR; + } + + interv_retval = rtr_check_interval_option(rtr_socket, rtr_socket->iv_mode, + ((struct pdu_end_of_data_v1_v2 *)pdu_data)->refresh_interval, + RTR_INTERVAL_TYPE_REFRESH); + + if (interv_retval == RTR_ERROR) { + interval_send_error_pdu(rtr_socket, pdu_data, + ((struct pdu_end_of_data_v1_v2 *)pdu_data)->refresh_interval, + RTR_REFRESH_MIN, RTR_REFRESH_MAX); + return RTR_ERROR; + } + + interv_retval = rtr_check_interval_option(rtr_socket, rtr_socket->iv_mode, + ((struct pdu_end_of_data_v1_v2 *)pdu_data)->retry_interval, + RTR_INTERVAL_TYPE_RETRY); + + if (interv_retval == RTR_ERROR) { + interval_send_error_pdu(rtr_socket, pdu_data, + ((struct pdu_end_of_data_v1_v2 *)pdu_data)->retry_interval, + RTR_RETRY_MIN, RTR_RETRY_MAX); + return RTR_ERROR; + } + + 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); + } + 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); + if (!args) + return; + + if (*args->ipv4_pdus) { + lrtr_free(*args->ipv4_pdus); + *args->ipv4_pdus = NULL; + } + + if (*args->ipv6_pdus) { + lrtr_free(*args->ipv6_pdus); + *args->ipv6_pdus = NULL; + } + + if (*args->router_key_pdus) { + lrtr_free(*args->router_key_pdus); + *args->router_key_pdus = NULL; + } + + if (*args->aspa_pdus) { + for (size_t i = 0; i < *args->aspa_pdu_count; i++) { + if ((*args->aspa_pdus)[i]) + lrtr_free((*args->aspa_pdus)[i]); + } + + lrtr_free(*args->aspa_pdus); + *args->aspa_pdus = NULL; + *args->aspa_pdu_count = 0; + } } /* 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]; + char pdu_data[RTR_MAX_PDU_LEN]; enum pdu_type type; int retval = RTR_SUCCESS; @@ -1023,146 +1447,122 @@ static int rtr_sync_receive_and_store_pdus(struct rtr_socket *rtr_socket) 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; + struct pdu_aspa **aspa_pdus = NULL; + size_t aspa_pdus_capacity = 0; + size_t aspa_pdus_count = 0; int oldcancelstate; - struct recv_loop_cleanup_args cleanup_args = { - .ipv4_pdus = ipv4_pdus, .ipv6_pdus = ipv6_pdus, .router_key_pdus = router_key_pdus}; + struct recv_loop_cleanup_args cleanup_args = {.ipv4_pdus = &ipv4_pdus, + .ipv6_pdus = &ipv6_pdus, + .router_key_pdus = &router_key_pdus, + .aspa_pdus = &aspa_pdus, + .aspa_pdu_count = &aspa_pdus_count}; - // receive LRTR_IPV4/IPV6 PDUs till EOD + // receive LRTR_IPV4/IPV6/ASPA 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); + + // Receive PDUs, cancellable + retval = rtr_receive_pdu(rtr_socket, pdu_data, 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; + break; } else if (retval < 0) { retval = RTR_ERROR; - goto cleanup; + break; } - type = rtr_get_pdu_type(pdu); - if (type == IPV4_PREFIX) { - if (rtr_store_prefix_pdu(rtr_socket, pdu, sizeof(*ipv4_pdus), (void **)&ipv4_pdus, + type = rtr_get_pdu_type(pdu_data); + + switch (type) { + case SERIAL_NOTIFY: + RTR_DBG1("Ignoring Serial Notify"); + break; + + case IPV4_PREFIX: + // Temporarily store prefix PDU, handle later after EOD PDU has been received + if (rtr_store_prefix_pdu(rtr_socket, pdu_data, 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, + break; + + case IPV6_PREFIX: + // Temporarily store prefix PDU, handle later after EOD PDU has been received + if (rtr_store_prefix_pdu(rtr_socket, pdu_data, 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, + break; + + case ROUTER_KEY: + // Temporarily store router key PDU, handle later after EOD PDU has been received + if (rtr_store_router_key_pdu(rtr_socket, pdu_data, 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]; + break; - 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); + case ASPA: + // Temporarily store ASPA PDU, handle later after EOD PDU has been received + if (rtr_store_aspa_pdu(rtr_socket, (struct pdu_aspa *)pdu_data, &aspa_pdus, &aspa_pdus_count, + &aspa_pdus_capacity) == RTR_ERROR) { rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); retval = RTR_ERROR; - goto cleanup; } + break; - 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; - } + case ERROR: + rtr_handle_error_pdu(rtr_socket, pdu_data); + retval = RTR_ERROR; + break; - 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); - } + case EOD: + RTR_DBG1("EOD PDU received."); + struct pdu_end_of_data_v0 *eod_pdu = (struct pdu_end_of_data_v0 *)pdu_data; - struct pfx_table *pfx_update_table; - struct spki_table *spki_update_table; + retval = rtr_handle_eod_pdu(rtr_socket, eod_pdu, pdu_data); + if (retval != RTR_SUCCESS) + break; if (rtr_socket->is_resetting) { + // Use table copies instead in order to perform an atomic update RTR_DBG1("Reset in progress creating shadow table for atomic reset"); - pfx_shadow_table = lrtr_malloc(sizeof(struct pfx_table)); + + struct pfx_table *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)) { + if (pfx_table_copy_except_socket(rtr_socket->pfx_table, pfx_shadow_table, rtr_socket) != + PFX_SUCCESS) { 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)); + struct spki_table *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, + if (spki_table_copy_except_socket(rtr_socket->spki_table, spki_shadow_table, rtr_socket) != SPKI_SUCCESS) { RTR_DBG1("Creation of spki shadow table failed"); rtr_change_socket_state(rtr_socket, RTR_ERROR_FATAL); @@ -1170,150 +1570,91 @@ static int rtr_sync_receive_and_store_pdus(struct rtr_socket *rtr_socket) goto cleanup; } - RTR_DBG1("Shadow table created"); - } else { - pfx_update_table = rtr_socket->pfx_table; - spki_update_table = rtr_socket->spki_table; - } + struct aspa_table *aspa_shadow_table = lrtr_malloc(sizeof(struct aspa_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); + if (!aspa_shadow_table) { + RTR_DBG1("Memory allocation for aspa shadow table failed"); 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; + aspa_table_init(aspa_shadow_table, NULL); + + RTR_DBG1("Shadow tables created"); + + retval = rtr_sync_update_tables(rtr_socket, pfx_shadow_table, spki_shadow_table, + aspa_shadow_table, ipv4_pdus, ipv4_pdus_nindex, + ipv6_pdus, ipv6_pdus_nindex, router_key_pdus, + router_key_pdus_nindex, aspa_pdus, aspa_pdus_count, + eod_pdu); + + if (retval == RTR_SUCCESS) { + 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); + + // No need to notify shadow table's clients as it hasn't any. + RTR_DBG1("Replacing ASPA records"); + aspa_table_src_replace(rtr_socket->aspa_table, aspa_shadow_table, rtr_socket, + !!rtr_socket->aspa_table->update_fp, false); + + 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"); } - 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; + 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_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"); + + cleanup: + 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); } - 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"); + if (aspa_shadow_table) { + aspa_table_free(aspa_shadow_table, false); + lrtr_free(aspa_shadow_table); } + + rtr_socket->is_resetting = false; + } else { + retval = rtr_sync_update_tables(rtr_socket, rtr_socket->pfx_table, + rtr_socket->spki_table, rtr_socket->aspa_table, + ipv4_pdus, ipv4_pdus_nindex, ipv6_pdus, + ipv6_pdus_nindex, router_key_pdus, + router_key_pdus_nindex, aspa_pdus, aspa_pdus_count, + eod_pdu); } - 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); + break; + + default: + RTR_DBG("Received unexpected PDU (Type: %u)", ((struct pdu_header *)pdu_data)->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, + rtr_send_error_pdu_from_host(rtr_socket, pdu_data, 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); + break; } - rtr_socket->is_resetting = false; - } + } while (type != EOD && retval != RTR_ERROR); - lrtr_free(router_key_pdus); - lrtr_free(ipv6_pdus); - lrtr_free(ipv4_pdus); + recv_loop_cleanup(&cleanup_args); return retval; } diff --git a/rtrlib/rtr/packets_private.h b/rtrlib/rtr/packets_private.h index c8d5093a..8a00eade 100644 --- a/rtrlib/rtr/packets_private.h +++ b/rtrlib/rtr/packets_private.h @@ -14,8 +14,8 @@ #include -// 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; +// 16380 aspa providers (current max is ca. 8k) +static const size_t RTR_MAX_PDU_LEN = 65535; static const unsigned int RTR_RECV_TIMEOUT = 60; static const unsigned int RTR_SEND_TIMEOUT = 60; diff --git a/rtrlib/rtr/rtr.c b/rtrlib/rtr/rtr.c index 57bfbc3a..1c9acb8e 100644 --- a/rtrlib/rtr/rtr.c +++ b/rtrlib/rtr/rtr.c @@ -9,6 +9,7 @@ #include "rtr_private.h" +#include "rtrlib/aspa/aspa_private.h" #include "rtrlib/lib/log_private.h" #include "rtrlib/lib/utils_private.h" #include "rtrlib/pfx/pfx_private.h" @@ -38,9 +39,9 @@ static const char *socket_str_states[] = {[RTR_CONNECTING] = "RTR_CONNECTING", [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) + struct spki_table *spki_table, struct aspa_table *aspa_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; @@ -64,6 +65,7 @@ int rtr_init(struct rtr_socket *rtr_socket, struct tr_socket *tr, struct pfx_tab rtr_socket->last_update = 0; rtr_socket->pfx_table = pfx_table; rtr_socket->spki_table = spki_table; + rtr_socket->aspa_table = aspa_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; @@ -79,7 +81,7 @@ 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); + int rtval = pthread_create(&(rtr_socket->thread_id), NULL, (void *(*)(void *)) & rtr_fsm_start, rtr_socket); if (rtval == 0) return RTR_SUCCESS; @@ -100,6 +102,8 @@ void rtr_purge_outdated_records(struct rtr_socket *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"); + aspa_table_src_remove(rtr_socket->aspa_table, rtr_socket, true); + RTR_DBG1("Removed outdated records from aspa_table"); rtr_socket->request_session_id = true; rtr_socket->serial_number = 0; rtr_socket->last_update = 0; @@ -126,6 +130,7 @@ void *rtr_fsm_start(struct rtr_socket *rtr_socket) // 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 + // old aspa_Record could exists in the aspa_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) { @@ -241,6 +246,7 @@ void rtr_stop(struct rtr_socket *rtr_socket) 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); + aspa_table_src_remove(rtr_socket->aspa_table, rtr_socket, true); rtr_socket->thread_id = 0; rtr_socket->state = RTR_CLOSED; } diff --git a/rtrlib/rtr/rtr.h b/rtrlib/rtr/rtr.h index 162dadc9..5050d9d3 100644 --- a/rtrlib/rtr/rtr.h +++ b/rtrlib/rtr/rtr.h @@ -112,6 +112,7 @@ typedef void (*rtr_connection_state_fp)(const struct rtr_socket *rtr_socket, con * @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 + * @param aspa_table spki_table that stores the ASPA records obtained from the connected rtr server */ struct rtr_socket { struct tr_socket *tr_socket; @@ -132,6 +133,7 @@ struct rtr_socket { unsigned int version; bool has_received_pdus; struct spki_table *spki_table; + struct aspa_table *aspa_table; bool is_resetting; }; diff --git a/rtrlib/rtr/rtr_pdus.h b/rtrlib/rtr/rtr_pdus.h new file mode 100644 index 00000000..72b303ef --- /dev/null +++ b/rtrlib/rtr/rtr_pdus.h @@ -0,0 +1,157 @@ +#include "rtrlib/spki/spkitable.h" + +#include + +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, + ASPA = 11 +}; + +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_aspa { + uint8_t ver; + uint8_t type; + uint16_t zero; + uint32_t len; + uint8_t flags; + uint8_t afi_flags; + uint16_t provider_count; + uint32_t customer_asn; + uint32_t provider_asns[]; +}; + +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_v2 { + 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; +}; diff --git a/rtrlib/rtr/rtr_private.h b/rtrlib/rtr/rtr_private.h index 3e492153..824abaed 100644 --- a/rtrlib/rtr/rtr_private.h +++ b/rtrlib/rtr/rtr_private.h @@ -30,11 +30,13 @@ 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_0 = 0; static const uint8_t RTR_PROTOCOL_VERSION_1 = 1; +static const uint8_t RTR_PROTOCOL_VERSION_2 = 2; static const uint8_t RTR_PROTOCOL_MIN_SUPPORTED_VERSION; // = 0 -static const uint8_t RTR_PROTOCOL_MAX_SUPPORTED_VERSION = 1; + +static const uint8_t RTR_PROTOCOL_MAX_SUPPORTED_VERSION = 2; enum rtr_interval_range { RTR_BELOW_INTERVAL_RANGE = -1, RTR_INSIDE_INTERVAL_RANGE = 0, RTR_ABOVE_INTERVAL_RANGE = 1 }; @@ -66,9 +68,9 @@ enum rtr_interval_type { RTR_INTERVAL_TYPE_EXPIRATION, RTR_INTERVAL_TYPE_REFRESH * @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); + struct spki_table *spki_table, struct aspa_table *aspa_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 From f74457afbea1db33bb671ad356f0473cfec06978 Mon Sep 17 00:00:00 2001 From: revol-xut Date: Wed, 31 Jan 2024 09:29:43 +0100 Subject: [PATCH 18/55] aspa: add aspa data structures and verification algorithm - add `aspa_array`, an ordered dynamic array - add `aspa_table` for storing and managing aspa data - add aspa table update functions - add AS_PATH verification algorithm Co-authored-by: mrzslz Co-authored-by: carl <115627588+carl-tud@users.noreply.github.com> --- rtrlib/aspa/aspa.c | 934 ++++++++++++++++++++++++++++ rtrlib/aspa/aspa.h | 172 +++++ rtrlib/aspa/aspa_array/aspa_array.c | 230 +++++++ rtrlib/aspa/aspa_array/aspa_array.h | 106 ++++ rtrlib/aspa/aspa_private.h | 268 ++++++++ rtrlib/aspa/aspa_verification.c | 308 +++++++++ 6 files changed, 2018 insertions(+) create mode 100644 rtrlib/aspa/aspa.c create mode 100644 rtrlib/aspa/aspa.h create mode 100644 rtrlib/aspa/aspa_array/aspa_array.c create mode 100644 rtrlib/aspa/aspa_array/aspa_array.h create mode 100644 rtrlib/aspa/aspa_private.h create mode 100644 rtrlib/aspa/aspa_verification.c diff --git a/rtrlib/aspa/aspa.c b/rtrlib/aspa/aspa.c new file mode 100644 index 00000000..23c9f25a --- /dev/null +++ b/rtrlib/aspa/aspa.c @@ -0,0 +1,934 @@ +/* + * 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 "rtrlib/aspa/aspa_array/aspa_array.h" +#include "rtrlib/aspa/aspa_private.h" +#include "rtrlib/lib/alloc_utils_private.h" +#include "rtrlib/rtrlib_export_private.h" + +#include +#include +#include +#include + +static enum aspa_status aspa_table_notify_clients(struct aspa_table *aspa_table, struct aspa_record *record, + const struct rtr_socket *rtr_socket, + const enum aspa_operation_type operation_type) +{ + if (!aspa_table || !rtr_socket) + return ASPA_ERROR; + + if (aspa_table->update_fp && record) { + // Realloc in order not to expose internal record + struct aspa_record rec = *record; + + if (record->provider_asns && record->provider_count > 0) { + size_t size = sizeof(uint32_t) * record->provider_count; + + rec.provider_asns = lrtr_malloc(size); + memcpy(rec.provider_asns, record->provider_asns, size); + } else { + rec.provider_asns = NULL; + } + + aspa_table->update_fp(aspa_table, rec, rtr_socket, operation_type); + } + + return ASPA_SUCCESS; +} + +static enum aspa_status aspa_store_create_node(struct aspa_store_node **store, struct rtr_socket *rtr_socket, + struct aspa_array *aspa_array, struct aspa_store_node ***new_node) +{ + if (!rtr_socket) + return ASPA_ERROR; + + // Allocate new node + struct aspa_store_node *new = lrtr_malloc(sizeof(struct aspa_store_node)); + + if (!new) + return ASPA_ERROR; + + // Store socket and ASPA array + new->rtr_socket = rtr_socket; + new->aspa_array = aspa_array; + + // prepend new node + new->next = *store; // may be NULL + *store = new; + + if (new_node) + *new_node = store; + + return ASPA_SUCCESS; +} + +static void aspa_store_remove_node(struct aspa_store_node **node) +{ + struct aspa_store_node *tmp = *node; + *node = (*node)->next; + lrtr_free(tmp); +} + +static struct aspa_store_node **aspa_store_get_node(struct aspa_store_node **node, const struct rtr_socket *rtr_socket) +{ + if (!node || !*node || !rtr_socket) + return NULL; + + while (*node) { + if ((*node)->rtr_socket == rtr_socket) + return node; + node = &(*node)->next; + } + + return NULL; +} + +RTRLIB_EXPORT void aspa_table_init(struct aspa_table *aspa_table, aspa_update_fp update_fp) +{ + aspa_table->update_fp = update_fp; + aspa_table->store = NULL; + pthread_rwlock_init(&(aspa_table->lock), NULL); + pthread_rwlock_init(&(aspa_table->update_lock), NULL); +} + +static enum aspa_status aspa_table_remove_node(struct aspa_table *aspa_table, struct aspa_store_node **node, + bool notify) +{ + if (!node) + return ASPA_ERROR; + + if (!*node) + // Doesn't exist anymore + return ASPA_SUCCESS; + + struct aspa_array *array = (*node)->aspa_array; + struct rtr_socket *socket = (*node)->rtr_socket; + + // Remove node for socket + aspa_store_remove_node(node); + + if (!array) + // Doesn't exist anymore + return ASPA_SUCCESS; + + // Notify clients about these records being removed + if (notify) { + for (size_t i = 0; i < array->size; i++) + aspa_table_notify_clients(aspa_table, aspa_array_get_record(array, i), socket, false); + } + + // Release all records and their provider sets + aspa_array_free(array, true); + + return ASPA_SUCCESS; +} + +RTRLIB_EXPORT enum aspa_status aspa_table_src_remove(struct aspa_table *aspa_table, struct rtr_socket *rtr_socket, + bool notify) +{ + pthread_rwlock_wrlock(&aspa_table->update_lock); + pthread_rwlock_wrlock(&aspa_table->lock); + + struct aspa_store_node **node = aspa_store_get_node(&aspa_table->store, rtr_socket); + + if (!node || !*node) { + // Already gone + pthread_rwlock_unlock(&(aspa_table->lock)); + return ASPA_SUCCESS; + } + + enum aspa_status res = aspa_table_remove_node(aspa_table, node, notify); + + pthread_rwlock_unlock(&(aspa_table->lock)); + pthread_rwlock_unlock(&aspa_table->update_lock); + return res; +} + +RTRLIB_EXPORT void aspa_table_free(struct aspa_table *aspa_table, bool notify) +{ + pthread_rwlock_wrlock(&aspa_table->update_lock); + pthread_rwlock_wrlock(&aspa_table->lock); + + // Free store + while (aspa_table->store) + aspa_table_remove_node(aspa_table, &aspa_table->store, notify); + + aspa_table->store = NULL; + + pthread_rwlock_unlock(&aspa_table->lock); + pthread_rwlock_destroy(&aspa_table->lock); + pthread_rwlock_unlock(&aspa_table->update_lock); + pthread_rwlock_destroy(&aspa_table->update_lock); +} + +enum aspa_status aspa_table_src_replace(struct aspa_table *dst, struct aspa_table *src, struct rtr_socket *rtr_socket, + bool notify_dst, bool notify_src) +{ + if (!dst || !src || !rtr_socket || src == dst) + return ASPA_ERROR; + + pthread_rwlock_wrlock(&dst->update_lock); + pthread_rwlock_wrlock(&src->update_lock); + pthread_rwlock_wrlock(&dst->lock); + pthread_rwlock_wrlock(&src->lock); + + struct aspa_store_node **src_node = aspa_store_get_node(&src->store, rtr_socket); + + if (!src_node || !*src_node || !(*src_node)->aspa_array) { + pthread_rwlock_unlock(&src->lock); + pthread_rwlock_unlock(&dst->lock); + pthread_rwlock_unlock(&src->update_lock); + pthread_rwlock_unlock(&dst->update_lock); + return ASPA_ERROR; + } + + struct aspa_array *new_array = (*src_node)->aspa_array; + struct aspa_array *old_array = NULL; + + // Try to get an existing node in the source table's store + struct aspa_store_node **existing_dst_node = aspa_store_get_node(&dst->store, rtr_socket); + + if (existing_dst_node && *existing_dst_node) { + // Swap array + old_array = (*existing_dst_node)->aspa_array; + (*existing_dst_node)->aspa_array = new_array; + } else { + // There's no old_array. + // Destination table hasn't got an existing store node for the socket, so create a new one + if (aspa_store_create_node(&dst->store, rtr_socket, new_array, NULL) != ASPA_SUCCESS) { + pthread_rwlock_unlock(&src->lock); + pthread_rwlock_unlock(&dst->lock); + pthread_rwlock_unlock(&src->update_lock); + pthread_rwlock_unlock(&dst->update_lock); + return ASPA_ERROR; + } + } + + // Remove socket from source table's store + aspa_store_remove_node(src_node); + pthread_rwlock_unlock(&src->lock); + pthread_rwlock_unlock(&dst->lock); + pthread_rwlock_unlock(&src->update_lock); + pthread_rwlock_unlock(&dst->update_lock); + + if (notify_src) + // Notify src clients their records are being removed + for (size_t i = 0; i < new_array->size; i++) + aspa_table_notify_clients(src, aspa_array_get_record(new_array, i), rtr_socket, ASPA_REMOVE); + + if (old_array) { + if (notify_dst) + // Notify dst clients of their existing records are being removed + for (size_t i = 0; i < old_array->size; i++) + aspa_table_notify_clients(dst, aspa_array_get_record(old_array, i), rtr_socket, + ASPA_REMOVE); + + // Free the old array and their provider sets + aspa_array_free(old_array, true); + } + + if (notify_dst) + // Notify dst clients the records from src are added + for (size_t i = 0; i < new_array->size; i++) + aspa_table_notify_clients(dst, aspa_array_get_record(new_array, i), rtr_socket, ASPA_ADD); + + return ASPA_SUCCESS; +} + +// MARK: - Updating an ASPA table + +static int compare_update_operations(const void *a, const void *b) +{ + const struct aspa_update_operation *op1 = a; + const struct aspa_update_operation *op2 = b; + + // compare index in case customer ASNs match, so result is stable + if (op1->record.customer_asn < op2->record.customer_asn) + return -1; + else if (op1->record.customer_asn > op2->record.customer_asn) + return 1; + else if (op1->index > op2->index) + return 1; + else if (op1->index < op2->index) + return -1; + else + return 0; +} + +static int compare_asns(const void *a, const void *b) +{ + return *(uint32_t *)a - *(uint32_t *)b; +} + +// MARK: - Swap-In Update Mechanism + +/** + * @brief This function fills the given @p new_array with records based on a number of 'add' and 'remove' operations. + * + * @warning Do not call this function manually. This function fails if zero operations are supplied! + */ +static enum aspa_status aspa_table_update_compute_internal(struct rtr_socket *rtr_socket, struct aspa_array *array, + struct aspa_array *new_array, + struct aspa_update_operation *operations, size_t count, + struct aspa_update_operation **failed_operation) +{ + // Fail hard in debug builds. + assert(rtr_socket); + assert(array); + assert(operations); + assert(count > 0); + assert(failed_operation); + + size_t existing_i = 0; + + for (size_t i = 0; i < count; i++) { + struct aspa_update_operation *current = &operations[i]; + struct aspa_update_operation *next = (i < count - 1) ? &(operations[i + 1]) : NULL; + +#ifndef NDEBUG + // Sanity check record + if (current->type == ASPA_REMOVE) { + assert(current->record.provider_count == 0); + assert(!current->record.provider_asns); + } +#endif + + // Sort providers. + // We consider this an implementation detail, callers must not make any assumptions on the + // ordering of provider ASNs. + if (current->record.provider_count > 0 && current->record.provider_asns) + qsort(current->record.provider_asns, current->record.provider_count, sizeof(uint32_t), + compare_asns); + + while (existing_i < array->size) { + struct aspa_record *existing_record = aspa_array_get_record(array, existing_i); + + // Skip over records untouched by these add/remove operations + if (existing_record->customer_asn < current->record.customer_asn) { + existing_i += 1; + + // Append existing record (reuse existing provider array) + if (aspa_array_append(new_array, existing_record, false) != ASPA_SUCCESS) { + *failed_operation = current; + return ASPA_ERROR; + } + } else { + break; + } + } + + struct aspa_record *existing_record = aspa_array_get_record(array, existing_i); + + // existing record and current op have matching CAS + bool existing_matches_current = existing_record && + existing_record->customer_asn == current->record.customer_asn; + + // next record and current op have matching CAS + bool next_matches_current = next && next->record.customer_asn == current->record.customer_asn; + + // MARK: Handling 'add' operations + if (current->type == ASPA_ADD) { + // Attempt to add record with $CAS, but record with $CAS already exists + // Error: Duplicate Add. + if (existing_matches_current) { + *failed_operation = current; + return ASPA_DUPLICATE_RECORD; + } + + // Attempt to add record with $CAS twice. + // Error: Duplicate Add. + if (next_matches_current && next->type == ASPA_ADD) { + *failed_operation = next; + current->record.provider_asns = NULL; + return ASPA_DUPLICATE_RECORD; + } + + // This operation adds a record with $CAS, the next op however removes this $CAS record again. + // These form a no-op. + if (next_matches_current && next->type == ASPA_REMOVE) { +#if ASPA_NOTIFY_NO_OPS + // Complete record's providers for clients + next->record = current->record; +#endif + + // Mark as no-op. + current->is_no_op = true; + next->is_no_op = true; + + // Skip next + i += 1; + continue; + } + + // Add record by appending it to new array (copy providers) + if (aspa_array_append(new_array, ¤t->record, true) != ASPA_SUCCESS) { + *failed_operation = current; + return ASPA_ERROR; + } + + // If it's an add operation, we insert a reference to the newly created record's providers. + current->record.provider_asns = + aspa_array_get_record(new_array, new_array->size - 1)->provider_asns; + } + + // MARK: Handling 'remove' operations + else if (current->type == ASPA_REMOVE) { + // Attempt to remove record with $CAS, but record with $CAS does not exist + // Error: Removal of unknown record. + if (!existing_matches_current) { + *failed_operation = current; + return ASPA_RECORD_NOT_FOUND; + } + + // Attempt to remove record with $CAS twice. + // Error: Removal of unknown record. + if (next_matches_current && next->type == ASPA_REMOVE) { + *failed_operation = next; + return ASPA_RECORD_NOT_FOUND; + } + + // "Remove" record by simply not appending it to the new array + existing_i += 1; + + // If it's a remove operation, we insert a reference to the removed record's providers. + current->record.provider_count = existing_record->provider_count; + current->record.provider_asns = existing_record->provider_asns; + } + } + + // Append remaining records (reuse existing provider array) + for (; existing_i < array->size; existing_i++) + aspa_array_append(new_array, aspa_array_get_record(array, existing_i), false); + + return ASPA_SUCCESS; +} + +enum aspa_status aspa_table_update_swap_in_compute(struct aspa_table *aspa_table, struct rtr_socket *rtr_socket, + struct aspa_update_operation *operations, size_t count, + struct aspa_update **update) +{ + // Fail hard in debug builds. + assert(aspa_table); + assert(rtr_socket); + assert(update); + + if (!aspa_table || !rtr_socket || !update || ((count > 0) && !operations)) + return ASPA_ERROR; + + if (count == 0) + return ASPA_SUCCESS; + + if (!*update) { + *update = lrtr_malloc(sizeof(struct aspa_update)); + + if (!*update) + return ASPA_ERROR; + } + + // MARK: Update Lock + // Prevent interim writes to the table: We need to make sure the table stays the same + // until the cleanup since the computed update is based on the state the table is in right now + pthread_rwlock_wrlock(&aspa_table->update_lock); + + (*update)->table = aspa_table; + (*update)->operations = operations; + (*update)->operation_count = count; + (*update)->failed_operation = NULL; + + // stable sort operations, so operations dealing with the same customer ASN + // are located right next to each other + qsort(operations, count, sizeof(struct aspa_update_operation), compare_update_operations); + + pthread_rwlock_rdlock(&aspa_table->lock); + struct aspa_store_node **node = aspa_store_get_node(&aspa_table->store, rtr_socket); + + pthread_rwlock_unlock(&aspa_table->lock); + + if (!node || !*node) { + // The given table doesn't have a node for that socket, so create one + struct aspa_array *a = NULL; + + if (aspa_array_create(&a) != ASPA_SUCCESS) + return ASPA_ERROR; + + // Insert into table + pthread_rwlock_wrlock(&aspa_table->lock); + if (aspa_store_create_node(&aspa_table->store, rtr_socket, a, &node) != ASPA_SUCCESS || !node || + !*node) { + aspa_array_free(a, false); + pthread_rwlock_unlock(&aspa_table->lock); + return ASPA_ERROR; + } + pthread_rwlock_unlock(&aspa_table->lock); + } + + assert(node); + assert(*node); + assert((*node)->aspa_array); + + // Create new array that will hold updated record data + struct aspa_array *new_array = NULL; + + if (aspa_array_create(&new_array) != ASPA_SUCCESS) + return ASPA_ERROR; + + // Populate new_array + pthread_rwlock_rdlock(&aspa_table->lock); + enum aspa_status res = aspa_table_update_compute_internal(rtr_socket, (*node)->aspa_array, new_array, + operations, count, &(*update)->failed_operation); + pthread_rwlock_unlock(&aspa_table->lock); + + if (res == ASPA_SUCCESS) { + (*update)->node = *node; + (*update)->new_array = new_array; + } else { + (*update)->node = NULL; + (*update)->new_array = NULL; + + // Update computation failed so release newly created array. + // We must not release associated provider arrays here. + aspa_array_free(new_array, false); + } + return res; +} + +static void aspa_table_update_swap_in_consume(struct aspa_update **update_pointer, const bool apply) +{ + // Not going to re-apply update or apply if computation failed + if (!update_pointer) + return; + + struct aspa_update *update = *update_pointer; + + if (!update) + return; + + struct aspa_array *old_array = NULL; + + if (apply) { + if (!update->table || !update->operations || !update->node || !update->new_array || + !!update->failed_operation) + return; + + old_array = update->node->aspa_array; + pthread_rwlock_wrlock(&update->table->lock); + update->node->aspa_array = update->new_array; + pthread_rwlock_unlock(&update->table->lock); + } + + // Prevent access + *update_pointer = NULL; + + // MARK: Change Lock + // We're done with the update, allow changes again. + pthread_rwlock_unlock(&update->table->update_lock); + + // Handle notifications and cleanup... + for (size_t i = 0; i < update->operation_count; i++) { + struct aspa_update_operation *op = &update->operations[i]; + + // TODO: @carl check if this can get optimized away + if (apply) { + // Notify clients + // We can directly use the operation array as the source for the diff + // so we don't need to rely on first notifying clients about all records + // in the old_array being removed and then every record in the new_array + // being added again. +#if ASPA_NOTIFY_NO_OPS + aspa_table_notify_clients(update->table, &op->record, update->node->rtr_socket, op->type); +#else + if (!op->is_no_op) + aspa_table_notify_clients(update->table, &op->record, update->node->rtr_socket, + op->type); +#endif + + // If it's a remove operation, we need to deallocate the existing record's + // provider array as it is no longer present in the new ASPA array. + if (!op->is_no_op && op->type == ASPA_REMOVE) { + if (op->record.provider_asns) + lrtr_free(op->record.provider_asns); + + op->record.provider_asns = NULL; + op->record.provider_count = 0; + } + } else { + // No cleanup necessary for operations that haven't even been performed in the first place + if (update->failed_operation && (op == update->failed_operation)) + break; + + // If it's an add operation, we need to deallocate the newly created record's + // provider array as it is not needed + if (!op->is_no_op && op->type == ASPA_ADD) { + if (op->record.provider_asns) + lrtr_free(op->record.provider_asns); + + op->record.provider_asns = NULL; + } + } + } + + // Free + if (apply && old_array) + aspa_array_free(old_array, false); + + if (!apply && update->new_array) + aspa_array_free(update->new_array, false); + + if (update->operations) + lrtr_free(update->operations); + + update->operations = NULL; + update->operation_count = 0; + update->failed_operation = NULL; + update->new_array = NULL; + update->node = NULL; + update->table = NULL; + lrtr_free(update); +} + +void aspa_table_update_swap_in_apply(struct aspa_update **update_pointer) +{ + aspa_table_update_swap_in_consume(update_pointer, true); +} + +void aspa_table_update_swap_in_discard(struct aspa_update **update_pointer) +{ + aspa_table_update_swap_in_consume(update_pointer, false); +} + +// MARK: - In-Place Update Mechanism + +enum aspa_status aspa_table_update_in_place(struct aspa_table *aspa_table, struct rtr_socket *rtr_socket, + struct aspa_update_operation *operations, size_t count, + struct aspa_update_operation **failed_operation) +{ + // Fail hard in debug builds. + assert(aspa_table); + assert(rtr_socket); + assert(failed_operation); + + if (!aspa_table || !rtr_socket || !failed_operation || ((count > 0) && !operations)) + return ASPA_ERROR; + + if (count == 0) + return ASPA_SUCCESS; + + // stable sort operations, so operations dealing with the same customer ASN + // are located right next to each other + qsort(operations, count, sizeof(struct aspa_update_operation), compare_update_operations); + + pthread_rwlock_wrlock(&aspa_table->lock); + struct aspa_store_node **node = aspa_store_get_node(&aspa_table->store, rtr_socket); + + if (!node || !*node) { + // The given table doesn't have a node for that socket, so create one + struct aspa_array *a = NULL; + + if (aspa_array_create(&a) != ASPA_SUCCESS) + return ASPA_ERROR; + + // Insert into table + if (aspa_store_create_node(&aspa_table->store, rtr_socket, a, &node) != ASPA_SUCCESS || !node || + !*node) { + aspa_array_free(a, false); + pthread_rwlock_unlock(&aspa_table->lock); + return ASPA_ERROR; + } + } + + assert(node); + assert(*node); + assert((*node)->aspa_array); + + struct aspa_array *array = (*node)->aspa_array; + size_t existing_i = 0; + + for (size_t i = 0; i < count; i++) { + struct aspa_update_operation *current = &operations[i]; + struct aspa_update_operation *next = (i < count - 1) ? &(operations[i + 1]) : NULL; + +#ifndef NDEBUG + // Sanity check record + if (current->type == ASPA_REMOVE) { + assert(current->record.provider_count == 0); + assert(!current->record.provider_asns); + } +#endif + + // Sort providers. + // We consider this an implementation detail, callers must not make any assumptions on the + // ordering of provider ASNs. + if (current->record.provider_count > 0 && current->record.provider_asns) + qsort(current->record.provider_asns, current->record.provider_count, sizeof(uint32_t), + compare_asns); + + while (existing_i < array->size && + aspa_array_get_record(array, existing_i)->customer_asn < current->record.customer_asn) { + existing_i += 1; + } + + struct aspa_record *existing_record = aspa_array_get_record(array, existing_i); + + // existing record and current op have matching CAS + bool existing_matches_current = existing_record && + existing_record->customer_asn == current->record.customer_asn; + + // next record and current op have matching CAS + bool next_matches_current = next && next->record.customer_asn == current->record.customer_asn; + + // MARK: Handling 'add' operations + if (current->type == ASPA_ADD) { + // Attempt to add record with $CAS, but record with $CAS already exists + // Error: Duplicate Add. + if (existing_matches_current) { + *failed_operation = current; + pthread_rwlock_unlock(&aspa_table->lock); + return ASPA_DUPLICATE_RECORD; + } + + // This operation adds a record with $CAS, the next op however removes this $CAS record again. + if (next_matches_current && next->type == ASPA_REMOVE) { +#if ASPA_NOTIFY_NO_OPS + // Complete record's providers for clients + next->record = current->record; + aspa_table_notify_clients(aspa_table, ¤t->record, rtr_socket, current->type); + aspa_table_notify_clients(aspa_table, &next->record, rtr_socket, next->type); +#endif + + // Mark as no-op. + current->is_no_op = true; + next->is_no_op = true; + + // Skip next + i += 1; + continue; + } + + // Add record by appending it to new array (copy providers) + if (aspa_array_insert(array, existing_i, ¤t->record, true) != ASPA_SUCCESS) { + *failed_operation = current; + pthread_rwlock_unlock(&aspa_table->lock); + return ASPA_ERROR; + } + } + + // MARK: Handling 'remove' operations + else if (current->type == ASPA_REMOVE) { + // Attempt to remove record with $CAS, but record with $CAS does not exist + // Error: Removal of unknown record. + if (!existing_matches_current) { + *failed_operation = current; + pthread_rwlock_unlock(&aspa_table->lock); + return ASPA_RECORD_NOT_FOUND; + } + + // If it's a remove operation, we insert a reference to the removed record's providers. + current->record.provider_count = existing_record->provider_count; + current->record.provider_asns = existing_record->provider_asns; + + // Remove record (don't release providers) + if (aspa_array_remove(array, existing_i, false) != ASPA_SUCCESS) { + *failed_operation = current; + pthread_rwlock_unlock(&aspa_table->lock); + return ASPA_RECORD_NOT_FOUND; + } + } + + // Notify clients + aspa_table_notify_clients(aspa_table, ¤t->record, rtr_socket, current->type); + } + + pthread_rwlock_unlock(&aspa_table->lock); + return ASPA_SUCCESS; +} + +static enum aspa_status aspa_table_update_in_place_undo_internal(struct aspa_table *aspa_table, + struct rtr_socket *rtr_socket, + struct aspa_update_operation *operations, size_t count, + struct aspa_update_operation *failed_operation) +{ + // Fail hard in debug builds. + assert(aspa_table); + assert(rtr_socket); + assert(operations); + assert(count > 0); + + pthread_rwlock_wrlock(&aspa_table->lock); + struct aspa_store_node **node = aspa_store_get_node(&aspa_table->store, rtr_socket); + + if (!node || !*node || !(*node)->aspa_array) { + // Node/array is gone -- nothing we can undo + pthread_rwlock_unlock(&aspa_table->lock); + return ASPA_ERROR; + } + + assert(node); + assert(*node); + assert((*node)->aspa_array); + + struct aspa_array *array = (*node)->aspa_array; + size_t existing_i = 0; + + for (size_t i = 0; i < array->size; i++) { + struct aspa_update_operation *current = &operations[i]; + struct aspa_update_operation *next = (i < count - 1) ? &(operations[i + 1]) : NULL; + + // Check if this operation and the following were executed in the first place + if (failed_operation && current == failed_operation) + break; + + while (existing_i < array->size && + aspa_array_get_record(array, existing_i)->customer_asn < current->record.customer_asn) { + existing_i += 1; + } + + struct aspa_record *existing_record = aspa_array_get_record(array, existing_i); + + // existing record and current op have matching CAS + bool existing_matches_current = existing_record && + existing_record->customer_asn == current->record.customer_asn; + + // next record and current op have matching CAS + bool next_matches_current = next && next->record.customer_asn == current->record.customer_asn; + + // MARK: Undo 'add' operation + if (current->type == ASPA_ADD) { + // Attempt to remove record with $CAS, but record with $CAS does not exist + // Error: Removal of unknown record. + if (!existing_matches_current) { + pthread_rwlock_unlock(&aspa_table->lock); + return ASPA_RECORD_NOT_FOUND; + } + + // This operation adds a record with $CAS, the next op however removes this $CAS record again. + if (next_matches_current && next->type == ASPA_REMOVE) { +#if ASPA_NOTIFY_NO_OPS + // If it's a remove operation, we insert a reference to the removed record's providers. + next->record = current->record; + aspa_table_notify_clients(aspa_table, &next->record, rtr_socket, ASPA_ADD); + aspa_table_notify_clients(aspa_table, ¤t->record, rtr_socket, ASPA_REMOVE); +#endif + + // Mark as no-op. + current->is_no_op = true; + next->is_no_op = true; + + // Skip next + i += 1; + continue; + } + + // Remove record (release providers) + if (aspa_array_remove(array, existing_i, true) != ASPA_SUCCESS) { + pthread_rwlock_unlock(&aspa_table->lock); + return ASPA_RECORD_NOT_FOUND; + } + + aspa_table_notify_clients(aspa_table, ¤t->record, rtr_socket, ASPA_REMOVE); + } + + // MARK: Undo 'remove' operation + else if (current->type == ASPA_REMOVE) { + // Next adds record with $CAS again. + // Treat these two as a 'replace' op + if (existing_matches_current & next_matches_current && next->type == ASPA_ADD) { + if (existing_record->provider_asns) + lrtr_free(existing_record->provider_asns); + + // If it's a remove operation, we inserted a reference to the existing + // provider array. Put back reference. + existing_record->provider_asns = current->record.provider_asns; + existing_record->provider_count = current->record.provider_count; + i += 1; + + aspa_table_notify_clients(aspa_table, &next->record, rtr_socket, ASPA_REMOVE); + } else { + // Attempt to add record with $CAS, but record with $CAS already exists + // Error: Duplicate Add. + if (existing_matches_current) { + pthread_rwlock_unlock(&aspa_table->lock); + return ASPA_DUPLICATE_RECORD; + } + + // Insert record (don't copy providers) + if (aspa_array_insert(array, existing_i, ¤t->record, false) != ASPA_SUCCESS) { + pthread_rwlock_unlock(&aspa_table->lock); + return ASPA_ERROR; + } + } + + aspa_table_notify_clients(aspa_table, ¤t->record, rtr_socket, ASPA_ADD); + + // If it's a remove operation, we inserted a reference to the existing + // provider array. Restore that 'remove' operation back to its original state. + current->record.provider_count = 0; + current->record.provider_asns = NULL; + } + } + + pthread_rwlock_unlock(&aspa_table->lock); + return ASPA_SUCCESS; +} + +enum aspa_status aspa_table_update_in_place_undo(struct aspa_table *aspa_table, struct rtr_socket *rtr_socket, + struct aspa_update_operation *operations, size_t count, + struct aspa_update_operation *failed_operation) +{ + // Fail hard in debug builds. + assert(aspa_table); + assert(rtr_socket); + + if (!aspa_table || !rtr_socket || ((count > 0) && !operations)) + return ASPA_ERROR; + + if (count == 0) + return ASPA_SUCCESS; + + enum aspa_status res = + aspa_table_update_in_place_undo_internal(aspa_table, rtr_socket, operations, count, failed_operation); + + for (size_t i = 0; i < count; i++) { + struct aspa_update_operation *op = &operations[i]; + + // If it's a remove operation, we inserted a reference to the removed record's providers. + // Restore that 'remove' operation back to its original state. + if (op->type == ASPA_REMOVE) { + op->record.provider_asns = NULL; + op->record.provider_count = 0; + } + } + + return res; +} + +void aspa_table_update_in_place_cleanup(struct aspa_update_operation **operations, size_t count) +{ + if (!operations || !*operations) + return; + + // If count == 0, this won't be executed + for (size_t i = 0; i < count; i++) { + struct aspa_update_operation *op = &(*operations)[i]; + + // If it's a remove operation, we inserted a reference to the removed record's providers. + // Release that provider array now and restore that 'remove' + // operation back to its original state. + if (!op->is_no_op && op->type == ASPA_REMOVE) { + if (op->record.provider_asns) + lrtr_free(op->record.provider_asns); + + op->record.provider_asns = NULL; + op->record.provider_count = 0; + } + } + + lrtr_free(*operations); + *operations = NULL; +} diff --git a/rtrlib/aspa/aspa.h b/rtrlib/aspa/aspa.h new file mode 100644 index 00000000..38637435 --- /dev/null +++ b/rtrlib/aspa/aspa.h @@ -0,0 +1,172 @@ +/* + * 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_aspa_h ASPA validation table + * + * @brief The aspa_table is an abstract data structure to organize the validated + * Autonomous System Provider Authorization data received from an RPKI-RTR + * cache server. + * + * @{ + */ + +#ifndef RTR_ASPA_H +#define RTR_ASPA_H + +#include "rtrlib/aspa/aspa_array/aspa_array.h" +#include "rtrlib/lib/alloc_utils_private.h" +#include "rtrlib/rtr/rtr.h" + +#include +#include +#include + +/** + * @brief ASPA Record + * Customer (Customer Autonomous Systen, CAS) authorizes a set of provider AS numbers. + * + * @param customer_asn Customer ASN + * @param provider_count The number of providers this customer declares. + * @param provider_asns An array of provider ASNs. + */ +struct aspa_record { + uint32_t customer_asn; + size_t provider_count; + uint32_t *provider_asns; +}; + +// MARK: - ASPA Table + +struct aspa_table; + +/** + * @brief An enum describing the type of operation the ASPA table should perform using any given ASPA record. + */ +enum __attribute__((__packed__)) aspa_operation_type { + /** The existing record, identified by its customer ASN, shall be withdrawn from the ASPA table. */ + ASPA_REMOVE = 0, + + /** The new record, identified by its customer ASN, shall be added to the ASPA table. */ + ASPA_ADD = 1 +}; + +/** + * @brief A function pointer that is called if an record was added to the @p aspa_table + * or was removed from the @p aspa_table. + * + * @param aspa_table ASPA table which was updated. + * @param record ASPA record that was modified. + * @param rtr_socket The socket the record originated from + * @param operation_type The type of this operation. + */ +typedef void (*aspa_update_fp)(struct aspa_table *aspa_table, const struct aspa_record record, + const struct rtr_socket *rtr_socket, const enum aspa_operation_type operation_type); + +/** + * @brief ASPA Table + + * @param lock Read-Write lock to prevent data races. + * @param update_lock Read-Write lock to prevent changes made to the table while an update is in progress. + * @param update function, called when the dynamic ordered array changes. + * @param sockets sockets Sockets, each storing a dynamic ordered array + * + * An ASPA table consists of a linked list of a sockets and ASPA arrays, simplifying removing or replacing records + * originating from any given socket. + */ +struct aspa_table { + pthread_rwlock_t lock; + pthread_rwlock_t update_lock; + aspa_update_fp update_fp; + struct aspa_store_node *store; +}; + +/** + * @brief Possible return values for `aspa_*` functions. + */ +enum aspa_status { + /** Operation was successful. */ + ASPA_SUCCESS = 0, + + /** Error occurred. */ + ASPA_ERROR = -1, + + /** The supplied aspa_record already exists in the aspa_table. */ + ASPA_DUPLICATE_RECORD = -2, + + /** aspa_record wasn't found in the aspa_table. */ + ASPA_RECORD_NOT_FOUND = -3, +}; + +/** + * @brief Initializes the @p aspa_table struct. + * + * @param[in] aspa_table aspa_table that will be initialized. + * @param[in] update_fp Pointer to update function + */ +void aspa_table_init(struct aspa_table *aspa_table, aspa_update_fp update_fp); + +/** + * @brief Frees the memory associated with the @p aspa_table + * + * @param[in] aspa_table aspa_table that will be initialized. + * @param notify A boolean value determining whether to notify clients about records being removed from the table. + */ +void aspa_table_free(struct aspa_table *aspa_table, bool notify); + +/** + * @brief Removes all records in the @p aspa_table that originated from the socket. + * + * @param aspa_table ASPA table to use. + * @param rtr_socket Record's origin socket. + * @param notify A boolean value determining whether clients should be notified about the records' removal. + * @return @c SPKI_SUCCESS On success. + * @return @c SPKI_ERROR On error. + */ +enum aspa_status aspa_table_src_remove(struct aspa_table *aspa_table, struct rtr_socket *rtr_socket, bool notify); + +// MARK: - AS_PATH Verification + +enum aspa_direction { ASPA_UPSTREAM, ASPA_DOWNSTREAM }; + +/** + * @brief AS_PATH verification result. + */ +enum aspa_verification_result { + ASPA_AS_PATH_UNKNOWN, + ASPA_AS_PATH_INVALID, + ASPA_AS_PATH_VALID, +}; + +/** + * @brief Verifies an AS_PATH . + * + * Implements an optimized version of the ASPA verification algorithm described in section 6 of + * https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-verification/16/ . + * + * @param[in] aspa_table ASPA table to use. + * @param[in] direction `AS_PATH` direction, as explained in the draft + * @param[in] as_path `AS_PATH` array to be validated: concatenated of BGP UPDATEs' `AS_PATH`s + * @param[in] len the length of @p as_path array + * @return @c ASPA_AS_PATH_UNKNOWN if the `AS_PATH` cannot be fully verified + * @return @c ASPA_AS_PATH_INVALID if `AS_PATH` is invalid + * @return @c ASPA_AS_PATH_VALID if `AS_PATH` is valid + */ +enum aspa_verification_result aspa_verify_as_path(struct aspa_table *aspa_table, uint32_t as_path[], size_t len, + enum aspa_direction direction); + +/** + * @brief Collapses an `AS_PATH` in-place, replacing in-series repetitions with single occurences + * + * @return Length of the given array. + */ +size_t aspa_collapse_as_path(uint32_t as_path[], size_t len); + +#endif /* RTR_ASPA_H */ +/** @} */ diff --git a/rtrlib/aspa/aspa_array/aspa_array.c b/rtrlib/aspa/aspa_array/aspa_array.c new file mode 100644 index 00000000..75148106 --- /dev/null +++ b/rtrlib/aspa/aspa_array/aspa_array.c @@ -0,0 +1,230 @@ +/* + * 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 "aspa_array.h" + +#include "rtrlib/aspa/aspa_private.h" +#include "rtrlib/lib/alloc_utils_private.h" +#include "rtrlib/rtr/rtr.h" + +// MARK: - Initialization & Deinitialization + +enum aspa_status aspa_array_create(struct aspa_array **array_ptr) +{ + const size_t default_initial_size = 128; + + // allocation the chunk of memory of the provider as numbers + struct aspa_record *data_field = lrtr_malloc(sizeof(struct aspa_record) * default_initial_size); + + // malloc failed so returning an error + if (!data_field) + return ASPA_ERROR; + + // allocating the aspa_record itself + struct aspa_array *array = lrtr_malloc(sizeof(struct aspa_array)); + + // malloc for aspa_record failed hence we return an error + if (!array) { + lrtr_free(data_field); + return ASPA_ERROR; + } + + // initializing member variables of the aspa record + array->capacity = default_initial_size; + array->size = 0; + array->data = data_field; + + // returning the array + *array_ptr = array; + + return ASPA_SUCCESS; +} + +void aspa_array_free(struct aspa_array *array, bool free_provider_arrays) +{ + // if the array is null just return + if (!array) + return; + + if (array->data) { + if (free_provider_arrays) { + for (size_t i = 0; i < array->size; i++) { + if (array->data[i].provider_asns) { + lrtr_free(array->data[i].provider_asns); + array->data[i].provider_asns = NULL; + } + } + } + + // freeing the data + lrtr_free(array->data); + } + + // freeing the array itself + lrtr_free(array); +} + +// MARK: - Manipulation + +static enum aspa_status aspa_array_reallocate(struct aspa_array *array) +{ + // the factor by how much the capacity will increase: new_capacity = old_capacity * SIZE_INCREASE_EXPONENTIAL + const size_t SIZE_INCREASE_EXPONENTIAL = 2; + + // allocation the new chunk of memory + struct aspa_record *tmp = + lrtr_realloc(array->data, sizeof(struct aspa_record) * array->capacity * SIZE_INCREASE_EXPONENTIAL); + + // malloc failed so returning an error + if (!tmp) + return ASPA_ERROR; + + array->data = tmp; + array->capacity *= SIZE_INCREASE_EXPONENTIAL; + return ASPA_SUCCESS; +} + +static enum aspa_status aspa_array_insert_internal(struct aspa_array *array, size_t index, struct aspa_record *record, + bool copy_providers) +{ + // check if this element will fit into the array + if (array->size >= array->capacity) { + // increasing the array's size so the new element fits + if (aspa_array_reallocate(array) != ASPA_SUCCESS) + return ASPA_ERROR; + } + + uint32_t *provider_asns = NULL; + + if (record->provider_count > 0) { + if (copy_providers) { + size_t provider_size = record->provider_count * sizeof(uint32_t); + + provider_asns = lrtr_malloc(provider_size); + if (!provider_asns) + return ASPA_ERROR; + + memcpy(provider_asns, record->provider_asns, provider_size); + } else { + provider_asns = record->provider_asns; + } + } + + // No need to move if last element + if (index < array->size) { + size_t trailing = (array->size - index) * sizeof(struct aspa_record); + + /* trailing + * /-------------\ + * #3 #8 #11 #24 #30 #36 #37 + * #3 #8 #11 * #24 #30 #36 #37 + * ^ ^ + * index index + 1 + */ + memmove(&array->data[index + 1], &array->data[index], trailing); + } + + // append the record at the end + array->data[index] = *record; + array->data[index].provider_asns = provider_asns; + array->size += 1; + + return ASPA_SUCCESS; +} + +enum aspa_status aspa_array_insert(struct aspa_array *array, size_t index, struct aspa_record *record, + bool copy_providers) +{ + if (!array || index > array->size || !array->data || !record || + ((record->provider_count > 0) && !record->provider_asns)) + return ASPA_ERROR; + + return aspa_array_insert_internal(array, index, record, copy_providers); +} + +enum aspa_status aspa_array_append(struct aspa_array *array, struct aspa_record *record, bool copy_providers) +{ + if (!array || !array->data || !record || ((record->provider_count > 0) && !record->provider_asns)) + return ASPA_ERROR; + + return aspa_array_insert_internal(array, array->size, record, copy_providers); +} + +enum aspa_status aspa_array_remove(struct aspa_array *array, size_t index, bool free_providers) +{ + if (!array || index >= array->size || array->size == 0) + return ASPA_RECORD_NOT_FOUND; + + if (free_providers && array->data[index].provider_asns) + lrtr_free(array->data[index].provider_asns); + + // No need to move if last element + if (index < array->size - 1) { + size_t trailing = (array->size - index - 1) * sizeof(struct aspa_record); + + /* trailing + * /-------------\ + * #3 #8 #11 * #24 #30 #36 #37 + * #3 #8 #11 #24 #30 #36 #37 + * ^ ^ + * index index + 1 + */ + memmove(&array->data[index], &array->data[index + 1], trailing); + } + + array->size -= 1; + return ASPA_SUCCESS; +} + +inline struct aspa_record *aspa_array_get_record(struct aspa_array *array, size_t index) +{ + if (!array || index >= array->size || array->size == 0 || !array->data) + return NULL; + + return &array->data[index]; +} + +// MARK: - Retrieval + +struct aspa_record *aspa_array_search(struct aspa_array *array, uint32_t customer_asn) +{ + // if the array is empty we return an error + if (array->size == 0 || array->capacity == 0) + return NULL; + + // left and right bound of our search space + register size_t left = 0; + register size_t right = array->size - 1; + + // we stop if right and left crossed + while (left <= right) { + // current center + size_t center = (left + right) >> 1; + uint32_t center_value = array->data[center].customer_asn; + + // success found the value + if (center_value == customer_asn) { + return &array->data[center]; + + // value should be on the right side + } else if (center_value < customer_asn) { + left = center + 1; + + // value should be on the left side + } else if (center == 0) { + // value cannot be left of index 0 + return NULL; + } else { + right = center - 1; + } + } + + // element not found + return NULL; +} diff --git a/rtrlib/aspa/aspa_array/aspa_array.h b/rtrlib/aspa/aspa_array/aspa_array.h new file mode 100644 index 00000000..9f0858f5 --- /dev/null +++ b/rtrlib/aspa/aspa_array/aspa_array.h @@ -0,0 +1,106 @@ +/* + * 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_ASPA_DYN_ARRAY_H +#define RTR_ASPA_DYN_ARRAY_H + +#include "../aspa.h" + +#include +#include +#include +#include + +/** + * @brief Struct which is similar in function to std::vector from C++. + * If the vector is running full, a larger chunk of memory is reallocated. + * + * This structure stores ASPA records in a contiguous chunk of memory, + * sorted in ascending order by their customer ASN. + */ +struct aspa_array { + uint32_t size; + size_t capacity; + struct aspa_record *data; +}; + +// MARK: - Initialization & Deinitialization + +/** + * @brief Creates an vector object + * @param[in,out] array_ptr Pointer to a variable that will hold a reference to the newly created array. + * @return @c ASPA_SUCCESS if the operation succeeds, @c ASPA_ERROR if it fails. + */ +enum aspa_status aspa_array_create(struct aspa_array **array_ptr); + +/** + * @brief Deletes the given ASPA array. + * @param array ASPA array which will be deleted + * @param free_provider_arrays A boolean value determining whether each record's provider array should be deallocated. + */ +void aspa_array_free(struct aspa_array *array, bool free_provider_arrays); + +// MARK: - Manipulation + +/** + * @brief Inserts a given ASPA record into the array, preserving its order. + * + * @param array The ASPA array that will hold the new record. + * @param index The index at which the new record will be stored. + * @param record The new record. + * @param copy_providers A boolean value indicating whether the array should copy the record's + * providers before inserting the record. + * @return @c ASPA_SUCCESS if the operation succeeds, @c ASPA_ERROR if it fails. + */ +enum aspa_status aspa_array_insert(struct aspa_array *array, size_t index, struct aspa_record *record, + bool copy_providers); + +/** + * @brief Appends a given ASPA record to the array. + * + * @param array The ASPA array that will hold the new record. + * @param record The record that will be appended to the array. + * @param copy_providers A boolean value indicating whether the array should copy the record's + * providers before appending the record. + * @return @c ASPA_SUCCESS if the operation succeeds, @c ASPA_ERROR if it fails. + */ +enum aspa_status aspa_array_append(struct aspa_array *array, struct aspa_record *record, bool copy_providers); + +/** + * @brief Removes the record at the given index from the array. + * + * @param array The array to remove the record from. + * @param index The record's index. + * @param free_providers A boolean value determining whether to free the existing record's provider array. + * @return @c ASPA_SUCCESS if the operation succeeds, @c ASPA_RECORD_NOT_FOUND if the record's index doesn't exist, + * @c ASPA_ERROR otherwise. + */ +enum aspa_status aspa_array_remove(struct aspa_array *array, size_t index, bool free_providers); + +// MARK: - Retrieval + +/** + * @brief Returns a reference to the record at the given index. + * + * @param array ASPA array + * @param index The index in the ASPA array. + * @return A reference to the `aspa_record` if found, @c NULL otherwise. + */ +struct aspa_record *aspa_array_get_record(struct aspa_array *array, size_t index); + +/** + * @brief Searches the given ASPA array for a record matching its customer ASN. + * + * @param array The array to search. + * @param customer_asn Customer ASN + * @return A reference to the `aspa_record` if found, @c NULL otherwise. + */ +struct aspa_record *aspa_array_search(struct aspa_array *array, uint32_t customer_asn); + +#endif diff --git a/rtrlib/aspa/aspa_private.h b/rtrlib/aspa/aspa_private.h new file mode 100644 index 00000000..2df71d94 --- /dev/null +++ b/rtrlib/aspa/aspa_private.h @@ -0,0 +1,268 @@ +/* + * 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_aspa_h ASPA validation table + * + * @brief The @p aspa_table is an abstract data structure to organize the validated Autonomous System Provider + * Authorization data received from an RPKI-RTR cache server. + * + * # Updating an ASPA table + * ASPA tables implement aggregated updating using an array of 'add record' and 'remove record' operations -- + * reducing iterations and memory allocations. E.g., these operations can be derived from a RTR cache response. + * Currently, two distinct update mechanisms are supported: **Swap-In** and **In-Place** updates. Use the macro + * `ASPA_UPDATE_MECHANISM` in rtr/packets.c to configure which implementation is used during syncing. + * The array of operations is effectively a diff to the table's previous state. This diff can be conveniently used to + * notify callers about changes once the update is applied. + * + * ## Swap-In Update Mechanism + * The ASPA table's **Swap-In** update mechanism avoids blocking callers who want to + * verify an `AS_PATH` (and therefore need read access to the table) while an update is in progress and removes the + * need for an *undo mechanism* in case the update to the ASPA table itself or some other action performed + * inbetween fails. + * + * Performing an update using this mechanism involves these steps: + * - **Compute Update**: + * Every time you want to update a given ASPA table, call `aspa_table_update_swap_in_compute`. + * This will create a new ASPA array, appending both existing records and new records. Everything needed to update + * the table is stored in an update structure. + * - **Apply Update** or **Discard Update**: + * You either have to apply the update using `aspa_table_update_swap_in_apply` or discard it by calling + * `aspa_table_update_swap_in_discard`. This will either swap in the newly created ASPA array and notify + * clients about changes or discard and release data that's now unused. + * + * The implementation guarantess no changes are made to the ASPA table between + * calling `aspa_table_update_swap_in_compute` and `aspa_table_update_swap_in_apply` + * or `aspa_table_update_swap_in_discard`. + * + * ## In-Place Update Mechanism + * The ASPA table's **In-Place** update mechanism involves in-place modifications to the array of records + * and an undo function that undoes changes made previously. + * + * Performing an update using this mechanism involves these steps: + * - **Update**: + * Every time you want to update a given ASPA table, call `aspa_table_update_in_place`. This will modify the ASPA + * array. If the update fails, `failed_operation` will be set to the operation where the error occurring. + * - **Undo Update**: + * You may, but do not need to, undo the update using `aspa_table_update_in_place_undo`. This will undo all + * operations up to `failed_operation` or all operations. + * - **Clean Up**: + * After computing the update you should go through a cleanup step using `aspa_table_update_in_place_cleanup`. + * This will deallocate provider arrays and other data created during the update that's now unused. + * + * ## Special Cases + * There're various cases that need to be handled appropriately by both implementations. + * 1. **Add existing record**: + * The caller attempts to add a record that's already present in the table (`ASPA_DUPLICATE_RECORD`). + * 2. **Duplicate adds**: + * The caller attempts to add two or more records with the same customer ASN (`ASPA_DUPLICATE_RECORD`). + * 3. **Removal of unknown record**: + * The caller attempts to remove a record from the table that doesn't exist (`ASPA_RECORD_NOT_FOUND`). + * 4. **Duplicate removal**: + * The caller attempts to remove a record twice or more (`ASPA_RECORD_NOT_FOUND`). + * 5. **Complementary add/remove**: + * The caller attempts to first add a record and then wants to remove the same record. This is equivalent to a + * no-op. `ASPA_NOTIFY_NO_OPS` (`1` or `0`) determines if clients are notified about these no-ops. + * + * ## Implementation Details + * Both update mechanism implementations tackle the beforementioned cases by first sorting the array of 'add' and + * 'remove' operations by their customer ASN stably. That is, 'add' and 'remove' operations dealing with matching + * customer ASNs will remain in the same order as they arrived. This makes checking for cases 2 - *Duplicate + * Announcement* and 4 - *Duplicate Removal* easy as possible duplicates are neighbors in the operations array. + * Ordering the operations also enables skipping annihilating operations as described in case 5 - *Complementary + * Announcement/Withdrawal*. + * Both implementations are comprised of a loop iterating over operations and a nested loop that handles + * records from the existing ASPA array with an ASN smaller than the current operation's ASN. + * - If the record in the existing array and the current 'add' operation have a matching customer ASN, + * that's case 1 - *Announcement of Existing Record*. + * - If the record in the existing array and the current 'remove' operation do not have a matching customer ASN, + * that's case 3 - *Removal of Unknown Record*. + * + * @{ + */ + +#ifndef RTR_ASPA_PRIVATE_H +#define RTR_ASPA_PRIVATE_H + +#include "aspa.h" + +#include "rtrlib/rtr/rtr.h" + +#include +#include + +#define ASPA_NOTIFY_NO_OPS 0 + +// MARK: - Verification + +enum aspa_hop_result { ASPA_NO_ATTESTATION, ASPA_NOT_PROVIDER_PLUS, ASPA_PROVIDER_PLUS }; + +/** + * @brief Checks a hop in the given `AS_PATH`. + * @return @c aspa_hop_result . + */ +enum aspa_hop_result aspa_check_hop(struct aspa_table *aspa_table, uint32_t customer_asn, uint32_t provider_asn); + +// MARK: - Storage +/** + * @brief A linked list storing the bond between a @p rtr_socket and an @p aspa_array . + * + * @param aspa_array The node's array of ASPA records. + * @param rtr_socket The socket the records originate from. + * @param next Next node in the linked list. + */ +struct aspa_store_node { + struct aspa_array *aspa_array; + struct rtr_socket *rtr_socket; + struct aspa_store_node *next; +}; + +/** + * @brief Replaces all ASPA records associated with the given socket with the records in the src table. + * + * @param[in,out] dst The destination table. Existing records associated with the socket are replaced. + * @param[in,out] src The source table. + * @param[in] rtr_socket The socket the records are associated with. + * @param notify_dst A boolean value determining whether to notify the destination table's clients. + * @param notify_src A boolean value determining whether to notify the source table's clients. + * @return @c ASPA_SUCCESS if the operation succeeds, @c ASPA_ERROR if it fails. + */ +enum aspa_status aspa_table_src_replace(struct aspa_table *dst, struct aspa_table *src, struct rtr_socket *rtr_socket, + bool notify_dst, bool notify_src); + +// MARK: - Updating +/** + * @brief A struct describing a specific type of operation that should be performed using the attached ASPA record. + * + * @param index A value uniquely identifying this operation's position within the array of operations. + * @param type The operation's type. + * @param record The record that should be added or removed. + * @param is_no_op A boolean value indicating whether this operation is part of a pair + * of 'add ``' and 'remove ``' operations that form a no-op. + */ +struct aspa_update_operation { + size_t index; + enum aspa_operation_type type; + struct aspa_record record; + bool is_no_op; +}; + +// MARK: - Swap-In Update Mechanism +/** + * @brief Computed ASPA update. + * + * @param table The ASPA table the update was computed for. + * @param operations The array of update operations used to compute this update. + * @param operation_count The number of update operations in the operations array. + * @param failed_operation An optional pointer to the operation that failed. + * @param node The node in the given ASPA table's store whose ASPA array is going to be replaced + * by @p new_array if this update is applied. + * @param new_array The new ASPA array replacing the node's existing array. + */ +struct aspa_update { + struct aspa_table *table; + struct aspa_update_operation *operations; + size_t operation_count; + struct aspa_update_operation *failed_operation; + struct aspa_store_node *node; + struct aspa_array *new_array; +}; + +/** + * @brief Computes an update structure that can later be applied to the given ASPA table. + * + * @note Each record in an 'add' operation may have a provider array associated with it. Any record in a 'remove' + * operation must have its `provider_count` set to 0 and `provider_array` set to `NULL`. + * @note This function acquires an update lock on the given ASPA table ensuring no mutations occur while + * computing the update or before the update is applied. + * You must call `aspa_table_update_swap_in_apply` or `aspa_table_update_swap_in_discard` + * to either apply or discard the update. + * + * @param[in] aspa_table ASPA table to store new ASPA data in. + * @param[in] rtr_socket The socket the updates originate from. + * @param[in] operations Add and remove operations to perform. + * @param[in] count Number of operations. + * @param update The computed update. The update pointer must be non-NULL, but may point to a @c NULL + * value initially. Points to an update struct after this function returns. + * @return @c ASPA_SUCCESS On success. + * @return @c ASPA_RECORD_NOT_FOUND If a records is supposed to be removed but cannot be found. + * @return @c ASPA_DUPLICATE_RECORD If a records is supposed to be added but its corresponding + * customer ASN already exists. + * @return @c ASPA_ERROR On other failures. + */ +enum aspa_status aspa_table_update_swap_in_compute(struct aspa_table *aspa_table, struct rtr_socket *rtr_socket, + struct aspa_update_operation *operations, size_t count, + struct aspa_update **update); + +/** + * @brief Applys the given update, as previously computed by `aspa_table_update_swap_in_compute`, + * releases memory allocated while computing the update and unlocks update lock. The update is consumed. + * + * @param update The update that will be applied. + */ +void aspa_table_update_swap_in_apply(struct aspa_update **update); + +/** + * @brief Discards the given update, releases memory allocated while computing the update and unlocks update lock. + * The update is consumed. + * + * @param update The update to discard. + */ +void aspa_table_update_swap_in_discard(struct aspa_update **update); + +// MARK: - In-Place Update Mechanism + +/** + * @brief Updates the given ASPA table. + * + * @note Each record in an 'add' operation may have a provider array associated with it. Any record in a 'remove' + * operation must have its `provider_count` set to `0` and `provider_array` set to `NULL`. + * + * @param[in] aspa_table ASPA table to store new ASPA data in. + * @param[in] rtr_socket The socket the updates originate from. + * @param[in] operations Add and remove operations to perform. + * @param[in] count Number of operations. + * @param[out] failed_operation Failed operation, filled in if update fails. + * @return @c ASPA_SUCCESS On success. + * @return @c ASPA_RECORD_NOT_FOUND If a records is supposed to be removed but cannot be found. + * @return @c ASPA_DUPLICATE_RECORD If a records is supposed to be added but its corresponding customer ASN already + * exists. + * @return @c ASPA_ERROR On other failures. + */ +enum aspa_status aspa_table_update_in_place(struct aspa_table *aspa_table, struct rtr_socket *rtr_socket, + struct aspa_update_operation *operations, size_t count, + struct aspa_update_operation **failed_operation); + +/** + * @brief Tries to undo @c operations up to @p failed_operation and then releases all operations. + * + * @param[in] aspa_table ASPA table to store new ASPA data in. + * @param[in] rtr_socket The socket the updates originate from. + * @param[in] operations Add and remove operations to perform. + * @param[in] count Number of operations. + * @param[in] failed_operation Failed operation. + * @return @c ASPA_SUCCESS On success. + * @return @c ASPA_RECORD_NOT_FOUND If a records is supposed to be removed but cannot be found. + * @return @c ASPA_DUPLICATE_RECORD If a records is supposed to be added but its corresponding customer ASN already + * exists. + * @return @c ASPA_ERROR On other failures. + */ +enum aspa_status aspa_table_update_in_place_undo(struct aspa_table *aspa_table, struct rtr_socket *rtr_socket, + struct aspa_update_operation *operations, size_t count, + struct aspa_update_operation *failed_operation); + +/** + * @brief Releases operations and unused provider arrays. + * @param[in] operations Add and remove operations. + * @param[in] count Number of operations. + */ +void aspa_table_update_in_place_cleanup(struct aspa_update_operation **operations, size_t count); + +#endif /* RTR_ASPA_PRIVATE_H */ +/** @} */ diff --git a/rtrlib/aspa/aspa_verification.c b/rtrlib/aspa/aspa_verification.c new file mode 100644 index 00000000..c4e8bd49 --- /dev/null +++ b/rtrlib/aspa/aspa_verification.c @@ -0,0 +1,308 @@ +/* + * 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 "rtrlib/aspa/aspa_private.h" +#include "rtrlib/lib/alloc_utils_private.h" +#include "rtrlib/rtrlib_export_private.h" + +#include +#include +#include +#include + +static void *binary_search_asns(const uint32_t cas, uint32_t *array, size_t len) +{ + size_t mid, top; + int val; + uint32_t *pivot, *base = array; + + mid = len; + top = len; + + while (mid) { + mid = top / 2; + pivot = base + mid; + + val = cas - *pivot; + + if (val == 0) + return pivot; + + if (val >= 0) + base = pivot; + + top -= mid; + } + return NULL; +} + +enum aspa_hop_result aspa_check_hop(struct aspa_table *aspa_table, uint32_t customer_asn, uint32_t provider_asn) +{ + bool customer_found = false; + + for (struct aspa_store_node *node = aspa_table->store; !!node; node = node->next) { + struct aspa_record *aspa_record = aspa_array_search(node->aspa_array, customer_asn); + + if (!aspa_record) + continue; + + customer_found = true; + + // Provider ASNs are sorted in ascending order. + // We consider this an implementation detail, callers must not make any assumptions on the + // ordering of provider ASNs. + uint32_t *provider = + binary_search_asns(provider_asn, aspa_record->provider_asns, aspa_record->provider_count); + + if (provider) + return ASPA_PROVIDER_PLUS; + } + + return customer_found ? ASPA_NOT_PROVIDER_PLUS : ASPA_NO_ATTESTATION; +} + +static enum aspa_verification_result aspa_verify_as_path_upstream(struct aspa_table *aspa_table, uint32_t as_path[], + size_t len) +{ + // Optimized AS_PATH verification algorithm using zero based array + // where the origin AS has index N - 1 and the latest AS in the AS_PATH + // has index 0. + // Doesn't check any hop twice. + if (len <= 1) + // Trivially VALID AS_PATH + return ASPA_AS_PATH_VALID; + + pthread_rwlock_rdlock(&aspa_table->lock); + + // Find apex of up-ramp + size_t r = len - 1; + enum aspa_hop_result last_hop_right; + + while (r > 0 && (last_hop_right = aspa_check_hop(aspa_table, as_path[r], as_path[r - 1])) == ASPA_PROVIDER_PLUS) + r -= 1; + + if (r == 0) { + // Complete customer-provider chain, VALID upstream AS_PATH + pthread_rwlock_unlock(&aspa_table->lock); + return ASPA_AS_PATH_VALID; + } + + bool found_nP_from_right = false; + + /* + * Look for nP+ in the right-to-left/upwards direction + * Check if there's a nP+ hop in the gap from the right (facing left/up). + * a, The next hop right after the up-ramp was already retrieved from the database, + * so just check if that hop was nP+. + * b, Also, don't check last hop before down-ramp starts + * because there must be a hop of space in order for two + * nP+ hops to oppose each other. + * c, RR points to the left end of the hop last checked. + * d, Checking stops if the hop is nP+. + * + * Last chance of finding a relevant nP+ hop + * / + * L /\ R + * * -- * -- * . . . . . * -- * + * 0 L+1 R-1 \ + * |<------------------| * + * N-1 + * + */ + + size_t rr = r; + + if (last_hop_right == ASPA_NOT_PROVIDER_PLUS) { + found_nP_from_right = true; + } else { + while (rr > 0) { + size_t c = rr; + + rr--; + if (aspa_check_hop(aspa_table, as_path[c], as_path[rr]) == ASPA_NOT_PROVIDER_PLUS) { + found_nP_from_right = true; + break; + } + } + } + + // If nP+ occurs upstream customer-provider chain, return INVALID. + if (found_nP_from_right) { + pthread_rwlock_unlock(&aspa_table->lock); + return ASPA_AS_PATH_INVALID; + } + + pthread_rwlock_unlock(&aspa_table->lock); + return ASPA_AS_PATH_UNKNOWN; +} + +static enum aspa_verification_result aspa_verify_as_path_downstream(struct aspa_table *aspa_table, uint32_t as_path[], + size_t len) +{ + // Optimized AS_PATH verification algorithm using zero based array + // where the origin AS has index N - 1 and the latest AS in the AS_PATH + // has index 0. + // Doesn't check any hop twice. + if (len <= 2) + // Trivially VALID AS_PATH + return ASPA_AS_PATH_VALID; + + pthread_rwlock_rdlock(&aspa_table->lock); + + // Find apex of up-ramp + size_t r = len - 1; + enum aspa_hop_result last_hop_right; + + while (r > 0 && (last_hop_right = aspa_check_hop(aspa_table, as_path[r], as_path[r - 1])) == ASPA_PROVIDER_PLUS) + r--; + + bool found_nP_from_right = false; + bool found_nP_from_left = false; + + size_t l = 0; + enum aspa_hop_result last_hop_left; + + // Find down-ramp end + while (l < r && (last_hop_left = aspa_check_hop(aspa_table, as_path[l], as_path[l + 1])) == ASPA_PROVIDER_PLUS) + l++; + assert(l <= r); + + // If gap does not exist (sharp tip) or is just a single hop wide, + // there's no way to create a route leak, return VALID. + if (r - l <= 1) { + pthread_rwlock_unlock(&aspa_table->lock); + return ASPA_AS_PATH_VALID; + } + + /* + * I. Look for nP+ in the right-to-left/upwards direction + * Check if there's a nP+ hop in the gap from the right (facing left/up). + * a, The next hop right after the up-ramp was already retrieved from the database, + * so just check if that hop was nP+. + * b, Also, don't check last hop before down-ramp starts + * because there must be a hop of space in order for two + * nP+ hops to oppose each other. + * c, RR points to the left end of the hop last checked. + * d, Checking stops if the hop is nP+. + * + * Last chance of finding a relevant nP+ hop + * / + * L /\ R + * * -- * -- * . . . . . * -- * + * / L+1 R-1 \ + * * |<------------------| * + * 0 N-1 + * + */ + + size_t rr = r; + + if (last_hop_right == ASPA_NOT_PROVIDER_PLUS) { + found_nP_from_right = true; + } else { + while (rr > l + 1) { + size_t c = rr; + + rr--; + if (aspa_check_hop(aspa_table, as_path[c], as_path[rr]) == ASPA_NOT_PROVIDER_PLUS) { + found_nP_from_right = true; + break; + } + } + } + + // II. Look for nP+ in the left-to-right/down direction + // Check if there's a nP+ hop in the gap from the right (facing left/down). + if (found_nP_from_right) { + /* + * a, There's no need to check for an nP+ from the left if we + * didn't find an nP+ from the right before. + * b, The next hop right after the down-ramp was already retrieved from the database, + * so just check if that hop was nP+. + * c, LL points to the right end of the hop last checked. + * d, Checking stops if the hop is nP+. + * + * Last chance of finding a relevant nP+ hop + * / + * L LL /\ + * * -- * . . . . . * -- * . . . . . + * / L+1 RR + * * |------------->| + * 0 + * + */ + size_t ll = l + 1; + + if (last_hop_left == ASPA_NOT_PROVIDER_PLUS) { + found_nP_from_left = true; + } else { + while (ll < rr) { + size_t c = ll; + + ll++; + if (aspa_check_hop(aspa_table, as_path[c], as_path[ll]) == ASPA_NOT_PROVIDER_PLUS) { + found_nP_from_left = true; + break; + } + } + } + } + + // If two nP+ occur in opposing directions, return INVALID. + if (found_nP_from_right && found_nP_from_left) { + pthread_rwlock_unlock(&aspa_table->lock); + return ASPA_AS_PATH_INVALID; + } + + pthread_rwlock_unlock(&aspa_table->lock); + return ASPA_AS_PATH_UNKNOWN; +} + +RTRLIB_EXPORT enum aspa_verification_result aspa_verify_as_path(struct aspa_table *aspa_table, uint32_t as_path[], + size_t len, enum aspa_direction direction) +{ + switch (direction) { + case ASPA_UPSTREAM: + return aspa_verify_as_path_upstream(aspa_table, as_path, len); + case ASPA_DOWNSTREAM: + return aspa_verify_as_path_downstream(aspa_table, as_path, len); + } + + return ASPA_AS_PATH_UNKNOWN; +} + +RTRLIB_EXPORT size_t aspa_collapse_as_path(uint32_t as_path[], size_t len) +{ + if (len == 0) + return 0; + + size_t i = 1; + + while (i < len && as_path[i - 1] != as_path[i]) + i++; + + if (i == len) + return len; + + size_t j = i; + + i++; + + while (true) { // equivalent to while (i < len) + while (i < len && as_path[i - 1] == as_path[i]) + i++; + + if (i == len) + break; + + as_path[j++] = as_path[i++]; + } + + return j; +} From c3a6f37bf2808409cb3d012818724c8f9530db3d Mon Sep 17 00:00:00 2001 From: revol-xut Date: Wed, 31 Jan 2024 09:30:15 +0100 Subject: [PATCH 19/55] rtrlib: add aspa to central management data structure - add aspa_table to rtr_mgr functions - fix typos and format Co-authored-by: mrzslz Co-authored-by: carl <115627588+carl-tud@users.noreply.github.com> --- rtrlib/rtr_mgr.c | 31 ++++++++++++++++++++++++---- rtrlib/rtr_mgr.h | 6 +++++- rtrlib/rtr_mgr_private.h | 2 +- rtrlib/spki/spkitable_private.h | 2 +- rtrlib/transport/tcp/tcp_transport.c | 18 ++++++---------- 5 files changed, 40 insertions(+), 19 deletions(-) diff --git a/rtrlib/rtr_mgr.c b/rtrlib/rtr_mgr.c index aedec013..908ec1e4 100644 --- a/rtrlib/rtr_mgr.c +++ b/rtrlib/rtr_mgr.c @@ -9,6 +9,7 @@ #include "rtr_mgr_private.h" +#include "rtrlib/aspa/aspa_private.h" #include "rtrlib/config.h" #include "rtrlib/lib/alloc_utils_private.h" #include "rtrlib/lib/log_private.h" @@ -71,8 +72,8 @@ static int rtr_mgr_init_sockets(struct rtr_mgr_group *group, struct rtr_mgr_conf { 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); + config->aspa_table, refresh_interval, expire_interval, + retry_interval, iv_mode, rtr_mgr_cb, config, group); if (err_code) return err_code; } @@ -292,16 +293,19 @@ int rtr_mgr_config_cmp_tommy(const void *a, const void *b) return rtr_mgr_config_cmp(ar->group, br->group); } +// TODO: Additional arguments trailing? 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) + const aspa_update_fp aspa_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 aspa_table *aspa_table = NULL; struct rtr_mgr_config *config = NULL; struct rtr_mgr_group *cg = NULL; struct rtr_mgr_group_node *group_node; @@ -352,8 +356,14 @@ RTRLIB_EXPORT int rtr_mgr_init(struct rtr_mgr_config **config_out, struct rtr_mg goto err; spki_table_init(spki_table, spki_update_fp); + aspa_table = lrtr_malloc(sizeof(*aspa_table)); + if (!aspa_table) + goto err; + aspa_table_init(aspa_table, aspa_update_fp); + config->pfx_table = pfxt; config->spki_table = spki_table; + config->aspa_table = aspa_table; /* Copy the groups from the array into linked list config->groups */ config->len = groups_len; @@ -395,8 +405,11 @@ RTRLIB_EXPORT int rtr_mgr_init(struct rtr_mgr_config **config_out, struct rtr_mg spki_table_free(spki_table); if (pfxt) pfx_table_free(pfxt); + if (aspa_table) + aspa_table_free(aspa_table, false); lrtr_free(pfxt); lrtr_free(spki_table); + lrtr_free(aspa_table); lrtr_free(cg); @@ -454,6 +467,7 @@ RTRLIB_EXPORT void rtr_mgr_free(struct rtr_mgr_config *config) pfx_table_free(config->pfx_table); spki_table_free(config->spki_table); + aspa_table_free(config->aspa_table, true); lrtr_free(config->spki_table); lrtr_free(config->pfx_table); @@ -487,6 +501,15 @@ RTRLIB_EXPORT inline int rtr_mgr_validate(struct rtr_mgr_config *config, const u return pfx_table_validate(config->pfx_table, asn, prefix, mask_len, result); } +/* cppcheck-suppress unusedFunction */ +RTRLIB_EXPORT inline enum aspa_status rtr_mgr_verify_as_path(struct rtr_mgr_config *config, uint32_t as_path[], + size_t len, enum aspa_direction direction, + enum aspa_verification_result *result) +{ + *result = aspa_verify_as_path(config->aspa_table, as_path, len, direction); + return ASPA_SUCCESS; +} + /* 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) @@ -534,7 +557,7 @@ RTRLIB_EXPORT int rtr_mgr_add_group(struct rtr_mgr_config *config, const struct goto err; } - // TODO This is not pretty. It wants to get the same intervals + // 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) diff --git a/rtrlib/rtr_mgr.h b/rtrlib/rtr_mgr.h index aa33d94e..c1d45a44 100644 --- a/rtrlib/rtr_mgr.h +++ b/rtrlib/rtr_mgr.h @@ -35,6 +35,7 @@ #include "config.h" +#include "rtrlib/aspa/aspa.h" #include "rtrlib/pfx/pfx.h" #include "rtrlib/spki/spkitable.h" #ifdef RTRLIB_BGPSEC_ENABLED @@ -88,6 +89,7 @@ struct rtr_mgr_config { void *status_fp_data; struct pfx_table *pfx_table; struct spki_table *spki_table; + struct aspa_table *aspa_table; }; /** @@ -118,6 +120,8 @@ struct rtr_mgr_config { 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] aspa_update_fp Pointer to aspa_update_fp callback, that is + executed for every added and removed aspa_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 @@ -131,7 +135,7 @@ struct rtr_mgr_config { 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); + const aspa_update_fp aspa_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. diff --git a/rtrlib/rtr_mgr_private.h b/rtrlib/rtr_mgr_private.h index d6548202..31bb53d7 100644 --- a/rtrlib/rtr_mgr_private.h +++ b/rtrlib/rtr_mgr_private.h @@ -18,7 +18,7 @@ struct tommy_list_wrapper { tommy_list list; }; -// TODO Find a nicer way todo a linked list (without writing our own) +// 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; diff --git a/rtrlib/spki/spkitable_private.h b/rtrlib/spki/spkitable_private.h index 65c3481c..7aefe73b 100644 --- a/rtrlib/spki/spkitable_private.h +++ b/rtrlib/spki/spkitable_private.h @@ -124,7 +124,7 @@ int spki_table_copy_except_socket(struct spki_table *src, struct spki_table *des /** * @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 + * @details old_table will be modified and should probably be freed after calling this function * @param[in] new_table * @param[in] old_table * @param[in] socket socket which entries should be diffed diff --git a/rtrlib/transport/tcp/tcp_transport.c b/rtrlib/transport/tcp/tcp_transport.c index 1dbc903e..38804fe5 100644 --- a/rtrlib/transport/tcp/tcp_transport.c +++ b/rtrlib/transport/tcp/tcp_transport.c @@ -110,8 +110,7 @@ int tr_tcp_open(void *tr_socket) 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)); + TCP_DBG("Couldn't establish TCP connection, %s", tcp_socket, strerror(errno)); goto end; } } @@ -119,11 +118,9 @@ int tr_tcp_open(void *tr_socket) 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)); + TCP_DBG("getaddrinfo error, %s", tcp_socket, strerror(errno)); } else { - TCP_DBG("getaddrinfo error, %s", tcp_socket, - gai_strerror(tcp_rtval)); + TCP_DBG("getaddrinfo error, %s", tcp_socket, gai_strerror(tcp_rtval)); } return TR_ERROR; } @@ -138,11 +135,9 @@ int tr_tcp_open(void *tr_socket) 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)); + TCP_DBG("getaddrinfo error, %s", tcp_socket, strerror(errno)); } else { - TCP_DBG("getaddrinfo error, %s", tcp_socket, - gai_strerror(tcp_rtval)); + TCP_DBG("getaddrinfo error, %s", tcp_socket, gai_strerror(tcp_rtval)); } goto end; } @@ -161,8 +156,7 @@ int tr_tcp_open(void *tr_socket) } 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)); + TCP_DBG("Couldn't establish TCP connection, %s", tcp_socket, strerror(errno)); goto end; } From 25e16f509114ab056f5949a54002269985d71503 Mon Sep 17 00:00:00 2001 From: revol-xut Date: Wed, 31 Jan 2024 09:30:36 +0100 Subject: [PATCH 20/55] tests: add aspa tests - add tests for AS_PATH verification - add tests for `aspa_array` - add tests for aspa pdu parsing and `aspa_table` updating - add tests for live interaction with rtr cache servers Co-authored-by: mrzslz Co-authored-by: carl <115627588+carl-tud@users.noreply.github.com> --- tests/CMakeLists.txt | 26 + tests/test_as_path_verification.c | 1014 ++++++++++++++++++++++ tests/test_aspa.c | 1142 +++++++++++++++++++++++++ tests/test_aspa_array.c | 212 +++++ tests/test_dynamic_groups.c | 2 +- tests/test_live_fetching.c | 114 +++ tests/test_live_validation.c | 2 +- tests/unittests/test_packets_static.c | 102 ++- 8 files changed, 2593 insertions(+), 21 deletions(-) create mode 100644 tests/test_as_path_verification.c create mode 100644 tests/test_aspa.c create mode 100644 tests/test_aspa_array.c create mode 100644 tests/test_live_fetching.c diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 81bd9f55..4368d224 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -3,30 +3,56 @@ set(CMAKE_BUILD_TYPE Debug) add_executable(test_pfx test_pfx.c) target_link_libraries(test_pfx rtrlib_static) add_coverage(test_pfx) + add_executable(test_trie test_trie.c) target_link_libraries(test_trie rtrlib_static) add_coverage(test_trie) + add_executable(test_pfx_locks test_pfx_locks.c) target_link_libraries(test_pfx_locks rtrlib_static) add_coverage(test_pfx_locks) + add_executable(test_ht_spkitable test_ht_spkitable.c) target_link_libraries(test_ht_spkitable rtrlib_static) add_coverage(test_ht_spkitable) + add_executable(test_ht_spkitable_locks test_ht_spkitable_locks.c) target_link_libraries(test_ht_spkitable_locks rtrlib_static) add_coverage(test_ht_spkitable_locks) + add_executable(test_live_validation test_live_validation.c) target_link_libraries(test_live_validation rtrlib_static) add_coverage(test_live_validation) + +add_executable(test_live_fetching test_live_fetching.c) +target_link_libraries(test_live_fetching rtrlib_static) +add_coverage(test_live_fetching) + add_executable(test_ipaddr test_ipaddr.c) target_link_libraries(test_ipaddr rtrlib_static) add_coverage(test_ipaddr) + add_executable(test_getbits test_getbits.c) target_link_libraries(test_getbits rtrlib_static) add_coverage(test_getbits) + add_executable(test_dynamic_groups test_dynamic_groups.c) target_link_libraries(test_dynamic_groups rtrlib_static) add_coverage(test_dynamic_groups) + +add_executable(test_aspa test_aspa.c) +target_link_libraries(test_aspa rtrlib_static) +add_coverage(test_aspa) + +add_executable(test_aspa_array test_aspa_array.c) +target_link_libraries(test_aspa_array rtrlib_static) +add_coverage(test_aspa_array) + +add_executable(test_as_path_verification test_as_path_verification.c) +target_link_libraries(test_as_path_verification rtrlib_static) +add_coverage(test_as_path_verification) + + if(RTRLIB_BGPSEC_ENABLED) add_executable(test_bgpsec test_bgpsec.c) target_link_libraries(test_bgpsec rtrlib_static) diff --git a/tests/test_as_path_verification.c b/tests/test_as_path_verification.c new file mode 100644 index 00000000..4d120a3e --- /dev/null +++ b/tests/test_as_path_verification.c @@ -0,0 +1,1014 @@ +/* + * 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/ + */ + +/* + * AS_PATH verification tests are derived from + * https://github.com/netd-tud/IETF-ASPA/blob/master/ietf-hackathon/tests.py + */ + +#include "rtrlib/aspa/aspa_array/aspa_array.h" +#include "rtrlib/aspa/aspa_private.h" +#include "rtrlib/lib/alloc_utils_private.h" + +#include +#include + +#define ASNS(...) ((uint32_t[]){__VA_ARGS__}) + +// clang-format off + +#define RECORD(cas, providers) ((struct aspa_record) { \ + .customer_asn = cas, \ + .provider_count = (size_t)(sizeof(providers) / sizeof(uint32_t)), \ + .provider_asns = sizeof(providers) == 0 ? NULL : providers \ +}) + +#define ADD_OPERATION(idx, rec) ((struct aspa_update_operation) { \ + .index = idx, \ + .record = rec, \ + .type = ASPA_ADD, \ + .is_no_op = false \ +}) + +#define BUILD_ASPA_TABLE(tablename, socketname, ...) \ + struct aspa_table *(tablename) = lrtr_malloc(sizeof(*tablename)); \ + assert((tablename)); \ + aspa_table_init((tablename), NULL); \ + \ + NEW_SOCKET_ADD_RECORDS((tablename), (socketname), __VA_ARGS__) \ + +#define NEW_SOCKET_ADD_RECORDS(tablename, socketname, ...) \ + struct rtr_socket *socketname = lrtr_malloc(sizeof(struct rtr_socket)); \ + assert(socketname); \ + socketname->aspa_table = (tablename); \ + { \ + struct aspa_record records[] = { __VA_ARGS__ }; \ + size_t len = sizeof(records) / sizeof(struct aspa_record); \ + \ + if (len) { \ + struct aspa_update *update = NULL; \ + struct aspa_update_operation *operations = \ + lrtr_malloc(len * sizeof(struct aspa_update_operation)); \ + for (size_t i = 0; i < len; i++) \ + operations[i] = ADD_OPERATION(i, records[i]); \ + \ + assert(aspa_table_update_swap_in_compute((tablename), \ + (socketname), operations, len, &update) \ + == ASPA_SUCCESS); \ + aspa_table_update_swap_in_apply(&update); \ + } \ + } + +#define VERIFY_AS_PATH(aspa_table, direction, result, asns) \ + assert((result) == aspa_verify_as_path(aspa_table, asns, sizeof(asns) / sizeof(uint32_t), direction)) + +static struct aspa_table *test_create_aspa_table(void) +{ + struct aspa_table *aspa_table = lrtr_malloc(sizeof(*aspa_table)); + + assert(aspa_table); + aspa_table_init(aspa_table, NULL); + + NEW_SOCKET_ADD_RECORDS(aspa_table, rtr_socket_1, + RECORD(100, ASNS(200, 201)), + RECORD(200, ASNS(300)), + RECORD(300, ASNS(400)), + RECORD(400, ASNS(500)), + + RECORD(501, ASNS(601)), + RECORD(401, ASNS(501)), + RECORD(301, ASNS(401)), + RECORD(201, ASNS(301)), + + RECORD(502, ASNS(602)), + RECORD(402, ASNS(502)), + RECORD(302, ASNS(402)), + RECORD(202, ASNS(302)), + + // 103 --> 203 <--> 303 <--> 403 <-- 304 + RECORD(103, ASNS(203)), + RECORD(203, ASNS(303)), + RECORD(303, ASNS(203, 403)), + RECORD(403, ASNS(303)), + RECORD(304, ASNS(403)), + ); + + NEW_SOCKET_ADD_RECORDS(aspa_table, rtr_socket_2, + RECORD(100, ASNS(200, 202)) + ); + + return aspa_table; +} + +// clang-format on + +static void test_hopping(struct aspa_table *aspa_table) +{ + // check that provider and not provider holds + assert(aspa_check_hop(aspa_table, 100, 200) == ASPA_PROVIDER_PLUS); + assert(aspa_check_hop(aspa_table, 200, 100) == ASPA_NOT_PROVIDER_PLUS); + + assert(aspa_check_hop(aspa_table, 200, 300) == ASPA_PROVIDER_PLUS); + assert(aspa_check_hop(aspa_table, 500, 999) == ASPA_NO_ATTESTATION); + + assert(aspa_check_hop(aspa_table, 999, 999) == ASPA_NO_ATTESTATION); + + // multiple dissimilar aspas + assert(aspa_check_hop(aspa_table, 100, 201) == ASPA_PROVIDER_PLUS); + assert(aspa_check_hop(aspa_table, 100, 202) == ASPA_PROVIDER_PLUS); +} + +// test multiple routes +// - upstream (only customer-provider-hops) +// - one not provider and one not attested: invalid +// - one not attested: unknown +// - all attested: valid + +static void test_upstream(struct aspa_table *aspa_table) +{ + // empty paths are valid + assert(aspa_verify_as_path(aspa_table, NULL, 0, ASPA_UPSTREAM) == ASPA_AS_PATH_VALID); + + // paths of length 1 are valid + VERIFY_AS_PATH(aspa_table, ASPA_UPSTREAM, ASPA_AS_PATH_VALID, ASNS(100)); + + // valid upstream paths + VERIFY_AS_PATH(aspa_table, ASPA_UPSTREAM, ASPA_AS_PATH_VALID, ASNS(200, 100)); + VERIFY_AS_PATH(aspa_table, ASPA_UPSTREAM, ASPA_AS_PATH_VALID, ASNS(300, 200)); + VERIFY_AS_PATH(aspa_table, ASPA_UPSTREAM, ASPA_AS_PATH_VALID, ASNS(300, 200, 100)); + + // single not-provider hop (nP) + + VERIFY_AS_PATH(aspa_table, ASPA_UPSTREAM, ASPA_AS_PATH_INVALID, ASNS(999, 100)); + VERIFY_AS_PATH(aspa_table, ASPA_UPSTREAM, ASPA_AS_PATH_INVALID, ASNS(300, 999, 100)); + VERIFY_AS_PATH(aspa_table, ASPA_UPSTREAM, ASPA_AS_PATH_INVALID, ASNS(999, 999, 100)); + VERIFY_AS_PATH(aspa_table, ASPA_UPSTREAM, ASPA_AS_PATH_INVALID, ASNS(999, 100, 999)); + + // single unattested hop (nA) + VERIFY_AS_PATH(aspa_table, ASPA_UPSTREAM, ASPA_AS_PATH_UNKNOWN, ASNS(999, 500, 400, 300)); +} + +static void test_downstream(struct aspa_table *aspa_table) +{ + // paths of length 1 <= N <= 2 are valid + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_VALID, ASNS(999)); + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_VALID, ASNS(998, 999)); + + // either up- or down-ramp is valid, not both + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_VALID, ASNS(300, 400, 500)); + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_VALID, ASNS(500, 400, 300)); + + // w/o customer-provider gap + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_VALID, ASNS(300, 400, 500, 400, 300)); + + // single not-provider (nP) in between + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_VALID, ASNS(302, 402, 502, 500, 400, 300)); + + // two highest-level hops are nP + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_INVALID, ASNS(301, 401, 501, 502, 502, 402, 302)); + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_UNKNOWN, ASNS(302, 402, 502, 999, 500, 400, 300)); + + // single nA at highest level is valid + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_VALID, ASNS(999, 500, 400, 300)); + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_VALID, ASNS(300, 400, 500, 999)); + + // single nP at highest level is valid + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_VALID, ASNS(999, 502, 402, 302)); + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_VALID, ASNS(302, 402, 502, 999)); + + // the last hop in the down ramp must be valid + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_UNKNOWN, ASNS(999, 300, 400, 500)); + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_INVALID, ASNS(100, 300, 400, 500)); + + // the first hop in the up ramp must be valid + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_UNKNOWN, ASNS(500, 400, 300, 999)); + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_INVALID, ASNS(500, 400, 300, 100)); + + // consecutive up-ramps are invalid + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_INVALID, ASNS(400, 300, 200, 502, 402, 302)); + + // consecutive down-ramps are invalid + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_INVALID, ASNS(200, 300, 400, 302, 402, 502)); + + // both down- and up-ramp are invalid + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_INVALID, ASNS(400, 300, 200, 302, 402, 502)); + + // overlapping customer-provider-relationships + // 103 --> 203 <--> 303 <--> 403 <-- 304 + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_VALID, ASNS(304, 403, 303, 203, 103)); + + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_UNKNOWN, ASNS(20, 30, 90, 40, 70, 80)); +} + +// clang-format off + +/** + * Example 1 (downstream) (valid) + * + * as_path: 20, 30, 40, 70, 80 + * + * 30 40 + * 10 20 70 + * 80 (origin) + * + * customer-providers: + * 80: 70 + * 70: 40 + * 20: 30 + * + */ +static void test_verify_example_1(void) +{ + BUILD_ASPA_TABLE(aspa_table, rtr_socket, + RECORD(80, ASNS(70)), + RECORD(70, ASNS(40)), + RECORD(20, ASNS(30)), + ) + + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_VALID, + ASNS(20, 30, 40, 70, 80)); + + aspa_table_free(aspa_table, false); + lrtr_free(aspa_table); + lrtr_free(rtr_socket); +} + +/** + * Example 2 (downstream) (unknown) + * + * as_path: 20, 30, 90, 40, 70, 80 + * + * 30 40 + * 10 20 90 70 + * 80 (origin) + * + * customer-providers: + * 80: 70 + * 70: 40 + * 20: 30 + * 90: 30, 40 + * + */ +static void test_verify_example_2(void) +{ + BUILD_ASPA_TABLE(aspa_table, rtr_socket, + RECORD(80, ASNS(70)), + RECORD(70, ASNS(40)), + RECORD(20, ASNS(30)), + RECORD(90, ASNS(30, 40)), + ) + + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_UNKNOWN, + ASNS(20, 30, 90, 40, 70, 80)); + + aspa_table_free(aspa_table, false); + lrtr_free(aspa_table); + lrtr_free(rtr_socket); +} + +/** + * Example 2b (downstream) (invalid) + * + * as_path: 20, 30, 90, 40, 70, 80 + * + * 30* 40* + * 10 20 90 70 + * 80 (origin) + * + * customer-providers: + * 80: 70 + * 70: 40 + * 20: 30 + * 90: 30, 40 + * 30: (none) + * 40: (none) + * + */ +static void test_verify_example_2b(void) +{ + BUILD_ASPA_TABLE(aspa_table, rtr_socket, + RECORD(80, ASNS(70)), + RECORD(70, ASNS(40)), + RECORD(20, ASNS(30)), + RECORD(90, ASNS(30, 40)), + RECORD(30, ASNS()), + RECORD(40, ASNS()), + ) + + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_INVALID, + ASNS(20, 30, 90, 40, 70, 80)); + + aspa_table_free(aspa_table, false); + lrtr_free(aspa_table); + lrtr_free(rtr_socket); +} + +/** + * Example 3a (downstream) (unknown) + * + * as_path: 20, 30, 90, 40, 70, 80 + * + * 30 90 40 + * 20 70 + * 10 80 (origin) + * + * customer-providers: + * 80: 70 + * 70: 40 + * 20: 30 + * + */ +static void test_verify_example_3a(void) +{ + BUILD_ASPA_TABLE(aspa_table, rtr_socket, + RECORD(80, ASNS(70)), + RECORD(70, ASNS(40)), + RECORD(20, ASNS(30)), + ) + + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_UNKNOWN, + ASNS(20, 30, 90, 40, 70, 80)); + + aspa_table_free(aspa_table, false); + lrtr_free(aspa_table); + lrtr_free(rtr_socket); +} + +/** + * Example 3b (downstream) (unknown) + * + * as_path: 20, 30, 90, 100, 40, 70, 80 + * + * 30 90 100 40 + * 10 20 70 + * 80 (origin) + * + * customer-providers: + * 80: 70 + * 70: 40 + * 20: 30 + * + */ +static void test_verify_example_3b(void) +{ + BUILD_ASPA_TABLE(aspa_table, rtr_socket, + RECORD(80, ASNS(70)), + RECORD(70, ASNS(40)), + RECORD(20, ASNS(30)), + ) + + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_UNKNOWN, + ASNS(20, 30, 90, 100, 40, 70, 80)); + + aspa_table_free(aspa_table, false); + lrtr_free(aspa_table); + lrtr_free(rtr_socket); +} + +/** + * Example 3c (downstream) (invalid) + * + * as_path: 20, 30, 90, 100, 40, 70, 80 + * + * 30* 90 100 40* + * 20 70 + * 10 80 (origin) + * + * customer-providers: + * 80: 70 + * 70: 40 + * 20: 30 + * 30: (none) + * 40: (none) + * + */ +static void test_verify_example_3c(void) +{ + BUILD_ASPA_TABLE(aspa_table, rtr_socket, + RECORD(80, ASNS(70)), + RECORD(70, ASNS(40)), + RECORD(20, ASNS(30)), + RECORD(30, ASNS()), + RECORD(40, ASNS()), + ) + + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_INVALID, + ASNS(20, 30, 90, 100, 40, 70, 80)); + + aspa_table_free(aspa_table, false); + lrtr_free(aspa_table); + lrtr_free(rtr_socket); +} + +/** + * Example 3d (downstream) (unknown) + * + * as_path: 20, 30, 40, 100, 90, 70, 80 + * + * 30* 40* 100? 90? + * 10 20 70 + * 80 (origin) + * + * customer-providers: + * 80: 70 + * 70: 90 + * 20: 30 + * 30: (none) + * 40: (none) + * + */ +static void test_verify_example_3d(void) +{ + BUILD_ASPA_TABLE(aspa_table, rtr_socket, + RECORD(80, ASNS(70)), + RECORD(70, ASNS(90)), + RECORD(20, ASNS(30)), + RECORD(30, ASNS()), + RECORD(40, ASNS()), + ) + + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_UNKNOWN, + ASNS(20, 30, 40, 100, 90, 70, 80)); + + aspa_table_free(aspa_table, false); + lrtr_free(aspa_table); + lrtr_free(rtr_socket); +} + +/** + * Example 3f (downstream) (unknown) + * + * as_path: 20, 30, 40, 100, 90, 70, 80 + * + * 30 40 100 90 + * 10 20 70 + * 80 (origin) + * + * customer-providers: + * 80: 70 + * 70: 90 + * 20: 30 + * 100: (none) + * 40: (none) + * + */ +static void test_verify_example_3f(void) +{ + BUILD_ASPA_TABLE(aspa_table, rtr_socket, + RECORD(80, ASNS(70)), + RECORD(70, ASNS(90)), + RECORD(20, ASNS(30)), + RECORD(100, ASNS()), + RECORD(40, ASNS()), + ) + + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_UNKNOWN, + ASNS(20, 30, 40, 100, 90, 70, 80)); + + aspa_table_free(aspa_table, false); + lrtr_free(aspa_table); + lrtr_free(rtr_socket); +} + +/** + * Example 4 (upstream) (invalid) + * + * as_path: 20, 30, 40, 50, 60, 70, 80 + * + * 10 80 (origin) + * 20 30 40 50 60 70 + * + * customer-providers: + * 70: 80 + * + */ +static void test_verify_example_4(void) +{ + BUILD_ASPA_TABLE(aspa_table, rtr_socket, + RECORD(70, ASNS(80)), + ) + + VERIFY_AS_PATH(aspa_table, ASPA_UPSTREAM, ASPA_AS_PATH_INVALID, + ASNS(20, 30, 40, 50, 60, 70, 80)); + + aspa_table_free(aspa_table, false); + lrtr_free(aspa_table); + lrtr_free(rtr_socket); +} + +/** + * Example 4-fixed (upstream) (invalid) + * + * as_path: 20, 30, 40, 50, 60, 70, 80 + * + * 10 80 (origin) + * 20 70 + * 30 40 50 60 + * + * customer-providers: + * 70: 80 + * 60: 70 + * 30: 20 + * + */ +static void test_verify_example_4_fixed(void) +{ + BUILD_ASPA_TABLE(aspa_table, rtr_socket, + RECORD(70, ASNS(80)), + RECORD(60, ASNS(70)), + RECORD(30, ASNS(20)), + ) + + VERIFY_AS_PATH(aspa_table, ASPA_UPSTREAM, ASPA_AS_PATH_INVALID, + ASNS(20, 30, 40, 50, 60, 70, 80)); + + aspa_table_free(aspa_table, false); + lrtr_free(aspa_table); + lrtr_free(rtr_socket); +} + +/** + * Example 5 (upstream) (valid) + * + * as_path: 20, 30, 40 + * + * 10 20 + * 30 + * 40 (origin) + * + * customer-providers: + * 40: 30 + * 30: 20 + * + */ +static void test_verify_example_5(void) +{ + BUILD_ASPA_TABLE(aspa_table, rtr_socket, + RECORD(40, ASNS(30)), + RECORD(30, ASNS(20)), + ) + + VERIFY_AS_PATH(aspa_table, ASPA_UPSTREAM, ASPA_AS_PATH_VALID, + ASNS(20, 30, 40)); + + aspa_table_free(aspa_table, false); + lrtr_free(aspa_table); + lrtr_free(rtr_socket); +} + +/** + * Example 6 (downstream) (invalid) + * + * as_path: 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120 + * + * 50 90 + * 40 60 70 80 100 + * 30 110 + * 20 120 + * 10 + * + * customer-providers: + * 120: 110 + * 110: 100 + * 100: 90 + * 80: 90 + * 60: 50 + * 40: 50 + * 30: 40 + * 20: 30 + * + */ +static void test_verify_example_6(void) +{ + BUILD_ASPA_TABLE(aspa_table, rtr_socket, + RECORD(120, ASNS(110)), + RECORD(110, ASNS(100)), + RECORD(100, ASNS(90)), + RECORD(80, ASNS(90)), + RECORD(60, ASNS(50)), + RECORD(40, ASNS(50)), + RECORD(30, ASNS(40)), + RECORD(20, ASNS(30)), + ) + + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_INVALID, + ASNS(20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120)); + + aspa_table_free(aspa_table, false); + lrtr_free(aspa_table); + lrtr_free(rtr_socket); +} + +/** + * Example 7 (downstream) (unknown) + * + * as_path: 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140 + * + * read from right: 100 -U-> 90 -U-> 80 -U-> 70 -U-> 60 -U-> 50 + * read from left: 50 -U-> 60 -U-> 70 -U-> 80 -P+-> 90 -P+-> 100 + * + * 100 + * 90 110 + * 50 60 70 80 120 + * 40 130 + * 30 140 + * 20 + * 10 + * + * customer-providers: + * 20: 30 + * 30: 40 + * 40: 50 + * 80: 90 + * 90: 100 + * 110: 100 + * 120: 110 + * 130: 120 + * 140: 130 + * + */ +static void test_verify_example_7(void) +{ + BUILD_ASPA_TABLE(aspa_table, rtr_socket, + RECORD(20, ASNS(30)), + RECORD(30, ASNS(40)), + RECORD(40, ASNS(50)), + RECORD(80, ASNS(90)), + RECORD(90, ASNS(100)), + RECORD(110, ASNS(100)), + RECORD(120, ASNS(110)), + RECORD(130, ASNS(120)), + RECORD(140, ASNS(130)), + ) + + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_UNKNOWN, + ASNS(20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140)); + + aspa_table_free(aspa_table, false); + lrtr_free(aspa_table); + lrtr_free(rtr_socket); +} + +/** + * Example 8 (downstream) (valid) + * + * as_path: 20 + * + * 20 + * 10 + * + * customer-providers: + * (none) + * + */ +static void test_verify_example_8(void) +{ + BUILD_ASPA_TABLE(aspa_table, rtr_socket, + ) + + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_VALID, + ASNS(20)); + + aspa_table_free(aspa_table, false); + lrtr_free(aspa_table); + lrtr_free(rtr_socket); +} + +/** + * Example 9 (upstream) (valid) + * + * as_path: 20 + * + * 10 + * 20 + * + * customer-providers: + * (none) + * + */ +static void test_verify_example_9(void) +{ + BUILD_ASPA_TABLE(aspa_table, rtr_socket, + ) + + VERIFY_AS_PATH(aspa_table, ASPA_UPSTREAM, ASPA_AS_PATH_VALID, + ASNS(20)); + + aspa_table_free(aspa_table, false); + lrtr_free(aspa_table); + lrtr_free(rtr_socket); +} + +/** + * Example 11 (downstream) (valid) + * + * as_path: 20, 30 + * + * 20 30 + * + * customer-providers: + * 20: (none) + * 30: (none) + * + */ +static void test_verify_example_11(void) +{ + BUILD_ASPA_TABLE(aspa_table, rtr_socket, + RECORD(20, ASNS()), + RECORD(30, ASNS()), + ) + + VERIFY_AS_PATH(aspa_table, ASPA_DOWNSTREAM, ASPA_AS_PATH_VALID, + ASNS(20, 30)); + + aspa_table_free(aspa_table, false); + lrtr_free(aspa_table); + lrtr_free(rtr_socket); +} + +/** + * Example 12 (upstream) (unknown) + * + * as_path: 20, 30 + * + * 20 30 + * + * customer-providers: + * (none) + * + */ +static void test_verify_example_12(void) +{ + BUILD_ASPA_TABLE(aspa_table, rtr_socket, + ) + + VERIFY_AS_PATH(aspa_table, ASPA_UPSTREAM, ASPA_AS_PATH_UNKNOWN, + ASNS(20, 30)); + + aspa_table_free(aspa_table, false); + lrtr_free(aspa_table); + lrtr_free(rtr_socket); +} + +/** + * Example 13 (upstream) (invalid) + * + * as_path: 20, 30, 40, 50, 60 + * + * 20 + * 30 + * 40 50 + * 60 + * + * customer-providers: + * 60: 50 + * 50: (none) + * 40: 30 + * 30: 20 + * 20: (none) + * + */ +static void test_verify_example_13(void) +{ + BUILD_ASPA_TABLE(aspa_table, rtr_socket, + RECORD(60, ASNS(50)), + RECORD(50, ASNS()), + RECORD(40, ASNS(30)), + RECORD(30, ASNS(20)), + RECORD(20, ASNS()), + ) + + VERIFY_AS_PATH(aspa_table, ASPA_UPSTREAM, ASPA_AS_PATH_INVALID, + ASNS(20, 30, 40, 50, 60)); + + aspa_table_free(aspa_table, false); + lrtr_free(aspa_table); + lrtr_free(rtr_socket); +} + +/** + * Example 14 (upstream) (invalid) + * + * as_path: 20, 30, 40, 50, 60 + * + * 30 <> 40 <> 50 <> 60 + * 20 + * + * customer-providers: + * 60: 50 + * 50: 40, 60 + * 40: 30, 50 + * 30: 40 + * 20: 30 + * + */ +static void test_verify_example_14(void) +{ + BUILD_ASPA_TABLE(aspa_table, rtr_socket, + RECORD(60, ASNS(50)), + RECORD(50, ASNS(40, 60)), + RECORD(40, ASNS(30, 50)), + RECORD(30, ASNS(40)), + RECORD(20, ASNS(30)), + ) + + VERIFY_AS_PATH(aspa_table, ASPA_UPSTREAM, ASPA_AS_PATH_INVALID, + ASNS(20, 30, 40, 50, 60)); + + aspa_table_free(aspa_table, false); + lrtr_free(aspa_table); + lrtr_free(rtr_socket); +} + +/** + * Example 15 (upstream) (invalid) + * + * as_path: 20, 30, 40, 50, 60 + * + * 30 <> 40 <> 50 <> 60 + * 20 + * + * customer-providers: + * 60: 50, 20 + * 50: 40, 60 + * 40: 30, 50 + * 30: 40 + * 20: 30 + * + */ +static void test_verify_example_15(void) +{ + BUILD_ASPA_TABLE(aspa_table, rtr_socket, + RECORD(60, ASNS(50, 20)), + RECORD(50, ASNS(40, 60)), + RECORD(40, ASNS(30, 50)), + RECORD(30, ASNS(40)), + RECORD(20, ASNS(30)), + ) + + VERIFY_AS_PATH(aspa_table, ASPA_UPSTREAM, ASPA_AS_PATH_INVALID, + ASNS(20, 30, 40, 50, 60)); + + aspa_table_free(aspa_table, false); + lrtr_free(aspa_table); + lrtr_free(rtr_socket); +} + +/** + * Example 16 (upstream) (invalid) + * + * as_path: 10, 20, 30, 40 + * + * 20 30 + * 10 40 + * + * customer-providers: + * 10: 20 + * 20: 100 + * 40: 30 + * + */ +static void test_verify_example_16(void) +{ + BUILD_ASPA_TABLE(aspa_table, rtr_socket, + RECORD(10, ASNS(20)), + RECORD(20, ASNS(100)), + RECORD(40, ASNS(30)), + ) + + VERIFY_AS_PATH(aspa_table, ASPA_UPSTREAM, ASPA_AS_PATH_INVALID, + ASNS(10, 20, 30, 40)); + + aspa_table_free(aspa_table, false); + lrtr_free(aspa_table); + lrtr_free(rtr_socket); +} + +/** + * Example 17 (upstream) (invalid) + * + * as_path: 10, 20, 30, 40 + * + * X + * 20 40 + * 10 30 + * + * customer-providers: + * 10: 20 + * 20: 100 + * 40: 30, 50 + * 50: 40 + * + */ +static void test_verify_example_17(void) +{ + BUILD_ASPA_TABLE(aspa_table, rtr_socket, + RECORD(10, ASNS(20)), + RECORD(20, ASNS(100)), + RECORD(40, ASNS(30, 50)), + RECORD(50, ASNS(40)), + ) + + VERIFY_AS_PATH(aspa_table, ASPA_UPSTREAM, ASPA_AS_PATH_INVALID, + ASNS(10, 20, 30, 40)); + + aspa_table_free(aspa_table, false); + lrtr_free(aspa_table); + lrtr_free(rtr_socket); +} + +/** + * Example 18 (upstream) (invalid) + * + * as_path: 30, 20, 40 + * + * 30 20* 40 + * + * customer-providers: + * 20: (none) + * + */ +static void test_verify_example_18(void) +{ + BUILD_ASPA_TABLE(aspa_table, rtr_socket, + RECORD(20, ASNS()), + ) + + VERIFY_AS_PATH(aspa_table, ASPA_UPSTREAM, ASPA_AS_PATH_INVALID, + ASNS(30, 20, 40)); + + aspa_table_free(aspa_table, false); + lrtr_free(aspa_table); + lrtr_free(rtr_socket); +} + +// clang-format on + +static void test_single_collapse(uint32_t input[], size_t input_len, uint32_t output[], size_t output_len) +{ + size_t retlen = aspa_collapse_as_path(input, input_len); + + assert(retlen == output_len); + + for (size_t i = 0; i < output_len; i++) + assert(input[i] == output[i]); +} + +static void test_collapse(void) +{ + test_single_collapse(NULL, 0, NULL, 0); + test_single_collapse((uint32_t[]){1}, 1, (uint32_t[]){1}, 1); + test_single_collapse((uint32_t[]){1, 1}, 2, (uint32_t[]){1}, 1); + test_single_collapse((uint32_t[]){1, 2}, 2, (uint32_t[]){1, 2}, 2); + test_single_collapse((uint32_t[]){1, 1, 1}, 3, (uint32_t[]){1}, 1); + test_single_collapse((uint32_t[]){1, 1, 2}, 3, (uint32_t[]){1, 2}, 2); + test_single_collapse((uint32_t[]){1, 2, 2}, 3, (uint32_t[]){1, 2}, 2); + test_single_collapse((uint32_t[]){1, 2, 2, 2}, 4, (uint32_t[]){1, 2}, 2); + test_single_collapse((uint32_t[]){1, 2, 2, 3}, 4, (uint32_t[]){1, 2, 3}, 3); +} + +int main(void) +{ + struct aspa_table *aspa_table = test_create_aspa_table(); + + test_hopping(aspa_table); + test_upstream(aspa_table); + test_downstream(aspa_table); + + lrtr_free(aspa_table->store->next->rtr_socket); + lrtr_free(aspa_table->store->rtr_socket); + aspa_table_free(aspa_table, false); + lrtr_free(aspa_table); + + test_verify_example_1(); + test_verify_example_2(); + test_verify_example_2b(); + test_verify_example_3a(); + test_verify_example_3b(); + test_verify_example_3c(); + test_verify_example_3d(); + test_verify_example_3f(); + test_verify_example_4(); + test_verify_example_4_fixed(); + test_verify_example_5(); + test_verify_example_6(); + test_verify_example_7(); + test_verify_example_8(); + test_verify_example_9(); + test_verify_example_11(); + test_verify_example_12(); + test_verify_example_13(); + test_verify_example_14(); + test_verify_example_15(); + test_verify_example_16(); + test_verify_example_17(); + test_verify_example_18(); + + test_collapse(); +} diff --git a/tests/test_aspa.c b/tests/test_aspa.c new file mode 100644 index 00000000..194cc0c6 --- /dev/null +++ b/tests/test_aspa.c @@ -0,0 +1,1142 @@ +/* + * 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 "rtrlib/aspa/aspa_private.h" +#include "rtrlib/lib/alloc_utils_private.h" +#include "rtrlib/lib/convert_byte_order_private.h" +#include "rtrlib/pfx/pfx_private.h" +#include "rtrlib/rtr/packets_private.h" +#include "rtrlib/rtr/rtr_pdus.h" +#include "rtrlib/spki/hashtable/ht-spkitable_private.h" +#include "rtrlib/transport/transport.h" + +#include +#include +#include +#include +#include + +char *data; +size_t data_size = 0; +size_t data_index = 0; + +bool assert_callbacks = true; +struct update_callback *expected_callbacks; +size_t callback_index; +size_t callback_count; + +struct update_callback { + struct aspa_table *source; + struct aspa_record record; + enum aspa_operation_type type; +}; + +#define BYTES16(X) lrtr_convert_short(TO_HOST_HOST_BYTE_ORDER, X) +#define BYTES32(X) lrtr_convert_long(TO_HOST_HOST_BYTE_ORDER, X) + +#define ASNS(...) ((uint32_t[]){__VA_ARGS__}) + +// clang-format off + +#define APPEND_ASPA(version, flag, cas, providers) \ + append_aspa(version, flag, cas, sizeof(providers) == 0 ? NULL : providers, \ + (size_t)(sizeof(providers) / sizeof(uint32_t))) + +#define RECORD(cas, providers) \ + ((struct aspa_record){.customer_asn = cas, \ + .provider_count = (size_t)(sizeof(providers) / sizeof(uint32_t)), \ + .provider_asns = sizeof(providers) == 0 ? NULL : providers}) + +#define _CAT_(a, b) a##b +#define _CAT(a, b) _CAT_(a, b) +#define _LINEVAR(V) _CAT(V, __LINE__) + +#define ASSERT_TABLE(socket, ...) \ + struct aspa_record _LINEVAR(_records)[] = {__VA_ARGS__}; \ + assert_table(socket, _LINEVAR(_records), (size_t)(sizeof(_LINEVAR(_records)) / sizeof(struct aspa_record))) + +#define ASSERT_EMPTY_TABLE(socket) assert_table(socket, NULL, 0) + +#define ADDED(rec) ((struct update_callback){.source = NULL, .record = rec, .type = ASPA_ADD}) + +#define ADDED_TO(table, rec) ((struct update_callback){.source = table, .record = rec, .type = ASPA_ADD}) + +#define REMOVED(rec) ((struct update_callback){.source = NULL, .record = rec, .type = ASPA_REMOVE}) + +#define REMOVED_FROM(table, rec) ((struct update_callback){.source = table, .record = rec, .type = ASPA_REMOVE}) + +#define EXPECT_UPDATE_CALLBACKS(...) \ + struct update_callback _LINEVAR(_callbacks)[] = {__VA_ARGS__}; \ + expect_update_callbacks(_LINEVAR(_callbacks), \ + (size_t)(sizeof(_LINEVAR(_callbacks)) / sizeof(struct update_callback))) + +#define EXPECT_NO_UPDATE_CALLBACKS() \ + expect_update_callbacks(NULL, 0) +// clang-format on + +#define ASPA_ANNOUNCE 1 +#define ASPA_WITHDRAW 0 + +static int custom_send(const struct tr_socket *socket __attribute__((unused)), const void *pdu, const size_t len, + const time_t timeout __attribute__((unused))) +{ + const struct pdu_error *err = pdu; + + if (err->type == 10) { + uint32_t *errlen = (uint32_t *)((char *)err->rest + err->len_enc_pdu); + + if ((char *)errlen < (char *)err + BYTES32(err->len)) + printf("err msg: %.*s\n", *errlen, (char *)(errlen + 1)); + } + return len; +} + +static int custom_recv(const struct tr_socket *socket __attribute__((unused)), const void *buf, const size_t len, + const time_t timeout __attribute__((unused))) +{ + size_t rlen = len; + + if (data_index + rlen > data_size) + rlen = data_size - data_index; + + memcpy((char *)buf, data + data_index, rlen); + data_index += rlen; + return rlen; +} + +static struct pdu_cache_response *begin_cache_response(uint8_t version, uint16_t session_id) +{ + if (!data) + data = lrtr_malloc(sizeof(struct pdu_cache_response)); + else + data = lrtr_realloc(data, data_size + sizeof(struct pdu_cache_response)); + + assert(data); + + struct pdu_cache_response *cache_response = (struct pdu_cache_response *)(data + data_size); + + cache_response->ver = version; + cache_response->type = CACHE_RESPONSE; + cache_response->session_id = BYTES16(session_id); + cache_response->len = BYTES32(8); + + data_size += sizeof(struct pdu_cache_response); + + return cache_response; +} + +static struct pdu_aspa *append_aspa(uint8_t version, uint8_t flags, uint32_t customer_asn, uint32_t provider_asns[], + size_t provider_count) +{ + size_t pdu_size = sizeof(struct pdu_aspa) + sizeof(uint32_t) * provider_count; + + data = lrtr_realloc(data, data_size + pdu_size); + assert(data); + + struct pdu_aspa *aspa = (struct pdu_aspa *)(data + data_size); + + aspa->ver = version; + aspa->type = ASPA; + aspa->zero = 0; + aspa->len = BYTES32(pdu_size); + aspa->flags = flags; + aspa->afi_flags = 0x3; + aspa->provider_count = BYTES16((uint16_t)provider_count); + aspa->customer_asn = BYTES32(customer_asn); + + for (size_t i = 0; i < provider_count; i++) + provider_asns[i] = BYTES32(provider_asns[i]); + + if (provider_asns) + memcpy(aspa->provider_asns, provider_asns, sizeof(uint32_t) * provider_count); + + data_size += pdu_size; + + return aspa; +} + +static struct pdu_end_of_data_v1_v2 *end_cache_response(uint8_t version, uint16_t session_id, uint32_t sn) +{ + data = lrtr_realloc(data, data_size + sizeof(struct pdu_end_of_data_v1_v2)); + assert(data); + + struct pdu_end_of_data_v1_v2 *eod = (struct pdu_end_of_data_v1_v2 *)(data + data_size); + + eod->ver = version; + eod->type = EOD; + eod->session_id = BYTES16(session_id); + eod->len = BYTES32(24); + eod->sn = BYTES32(sn); + eod->refresh_interval = BYTES32(RTR_REFRESH_MIN); + eod->retry_interval = BYTES32(RTR_RETRY_MIN); + eod->expire_interval = BYTES32(RTR_EXPIRATION_MIN); + + data_size += sizeof(struct pdu_end_of_data_v1_v2); + + return eod; +} + +static void clear_expected_callbacks(void) +{ + expected_callbacks = NULL; + callback_index = 0; + callback_count = 0; +} + +static void expect_update_callbacks(struct update_callback callbacks[], size_t count) +{ + if (callbacks && count > 0) { + expected_callbacks = callbacks; + callback_count = count; + callback_index = 0; + } else { + clear_expected_callbacks(); + } +} + +static void aspa_update_callback(struct aspa_table *s, const struct aspa_record record, + const struct rtr_socket *rtr_sockt __attribute__((unused)), + const enum aspa_operation_type operation_type) +{ + if (assert_callbacks) { + assert(callback_count > 0); + assert(callback_index < callback_count); + assert(expected_callbacks); + + if (expected_callbacks[callback_index].source) { + if (expected_callbacks[callback_index].source != s) + printf("Assertion failed: Callback originates from unexpected table.\n"); + + assert(expected_callbacks[callback_index].source == s); + } + + assert(expected_callbacks[callback_index].record.customer_asn == record.customer_asn); + assert(expected_callbacks[callback_index].type == operation_type); + assert(expected_callbacks[callback_index].record.provider_count == record.provider_count); + + for (size_t k = 0; k < record.provider_count; k++) + assert(expected_callbacks[callback_index].record.provider_asns[k] == record.provider_asns[k]); + + callback_index += 1; + } + + char c; + + switch (operation_type) { + case ASPA_ADD: + c = '+'; + break; + case ASPA_REMOVE: + c = '-'; + break; + default: + c = '?'; + break; + } + + printf("%c ASPA ", c); + printf("%u => [ ", record.customer_asn); + + size_t i; + size_t count = record.provider_count; + + for (i = 0; i < count; i++) { + printf("%u", record.provider_asns[i]); + if (i < count - 1) + printf(", "); + } + + printf(" ]\n"); + lrtr_free(record.provider_asns); +} + +static void assert_table(struct rtr_socket *socket, struct aspa_record records[], size_t record_count) +{ + assert(socket); + assert(socket->aspa_table); + + if (record_count == 0) { + // Assert no data exists for that socket! + struct aspa_store_node *node = socket->aspa_table->store; + + while (node) { + if (node->rtr_socket == socket) { + assert(node->aspa_array); + assert(node->aspa_array->size == 0); + return; + } + + node = node->next; + } + + return; + } + + // Assert data exists for that socket... + assert(socket->aspa_table->store); + assert(socket->aspa_table->store->aspa_array); + assert(socket->aspa_table->store->rtr_socket); + assert(socket->aspa_table->store->rtr_socket == socket); + assert(!socket->aspa_table->store->next); + + struct aspa_array *array = socket->aspa_table->store->aspa_array; + + if (array->size != record_count) + printf("error: unexpected number of stored records!"); + + assert(array->size == record_count); + + if (record_count <= 0) + return; + + assert(records); + assert(array->data); + + for (size_t i = 0; i < record_count; i++) { + assert(array->data[i].customer_asn == records[i].customer_asn); + assert(array->data[i].provider_count == records[i].provider_count); + + for (size_t k = 0; k < records[i].provider_count; k++) + assert(array->data[i].provider_asns[k] == records[i].provider_asns[k]); + } +} + +// clang-format off + +static void test_no_aspa(struct rtr_socket *socket) +{ + // Test: regular announcement + // Expect: OK + // DB: inserted + begin_cache_response(RTR_PROTOCOL_VERSION_2, 0); + end_cache_response(RTR_PROTOCOL_VERSION_2, 0, 437); + + EXPECT_NO_UPDATE_CALLBACKS(); + + assert(rtr_sync(socket) == RTR_SUCCESS); + assert(callback_index == callback_count); + + ASSERT_EMPTY_TABLE(socket); + assert(socket->aspa_table->store == NULL); +} + +static void test_regular_announcement(struct rtr_socket *socket) +{ + // Test: regular announcement + // Expect: OK + // DB: inserted + begin_cache_response(RTR_PROTOCOL_VERSION_2, 0); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 1100, ASNS(1101, 1102, 1103, 1104)); + end_cache_response(RTR_PROTOCOL_VERSION_2, 0, 437); + + EXPECT_UPDATE_CALLBACKS( + ADDED(RECORD(1100, ASNS(1101, 1102, 1103, 1104))), + ); + + assert(rtr_sync(socket) == RTR_SUCCESS); + assert(callback_index == callback_count); + + ASSERT_TABLE(socket, RECORD(1100, ASNS(1101, 1102, 1103, 1104))); +} + +static void test_withdraw(struct rtr_socket *socket) +{ + // Test: Withdraw existing record + // Expect: OK + // DB: record gets removed + + // Announces 1100 => 1101, 1102, 1103, 1104 + test_regular_announcement(socket); + + begin_cache_response(RTR_PROTOCOL_VERSION_2, 0); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_WITHDRAW, 1100, ASNS(42)); + end_cache_response(RTR_PROTOCOL_VERSION_2, 0, 444); + + EXPECT_UPDATE_CALLBACKS( + REMOVED(RECORD(1100, ASNS(1101, 1102, 1103, 1104))), + ); + + assert(rtr_sync(socket) == RTR_SUCCESS); + assert(callback_index == callback_count); + + ASSERT_EMPTY_TABLE(socket); +} + +static void test_regular_announcements(struct rtr_socket *socket) +{ + // Test: regular announcement + // Expect: OK + // DB: inserted + begin_cache_response(RTR_PROTOCOL_VERSION_2, 0); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 1100, ASNS(1101, 1102, 1103, 1104)); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 4400, ASNS(4401)); + end_cache_response(RTR_PROTOCOL_VERSION_2, 0, 437); + + EXPECT_UPDATE_CALLBACKS( + ADDED(RECORD(1100, ASNS(1101, 1102, 1103, 1104))), + ADDED(RECORD(4400, ASNS(4401))), + ); + + assert(rtr_sync(socket) == RTR_SUCCESS); + assert(callback_index == callback_count); + + ASSERT_TABLE(socket, + RECORD(1100, ASNS(1101, 1102, 1103, 1104)), + RECORD(4400, ASNS(4401)) + ); +} + +static void test_announce_existing(struct rtr_socket *socket) +{ + // Test: Announce for existing customer_asn + // Expect: ERROR + // DB: no change + + // Announces 1100 => 1101, 1102, 1103, 1104 + test_regular_announcement(socket); + + begin_cache_response(RTR_PROTOCOL_VERSION_2, 0); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 1100, ASNS(2201, 2202, 2203, 2204)); + end_cache_response(RTR_PROTOCOL_VERSION_2, 0, 444); + + // No updates expected, fails at first PDU + EXPECT_NO_UPDATE_CALLBACKS(); + + assert(rtr_sync(socket) == RTR_ERROR); + assert(callback_index == callback_count); + + ASSERT_TABLE(socket, RECORD(1100, ASNS(1101, 1102, 1103, 1104))); +} + +static void test_announce_twice(struct rtr_socket *socket) +{ + // Test: Announce new record again (duplicate) + // Expect: ERROR + // DB: no change + + // Announces 1100 => 1101, 1102, 1103, 1104 + test_regular_announcement(socket); + + begin_cache_response(RTR_PROTOCOL_VERSION_2, 0); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 1100, ASNS(1101, 1102, 1103, 1104)); + end_cache_response(RTR_PROTOCOL_VERSION_2, 0, 444); + + // No updates expected, fails at first PDU + EXPECT_NO_UPDATE_CALLBACKS(); + + assert(rtr_sync(socket) == RTR_ERROR); + assert(callback_index == callback_count); + + ASSERT_TABLE(socket, RECORD(1100, ASNS(1101, 1102, 1103, 1104))); +} + +static void test_withdraw_nonexisting(struct rtr_socket *socket) +{ + // Test: Withdraw non-existent record + // Expect: ERROR + // DB: no change + + // announces 1100 -> 1101, 1102, 1103, 1104 + test_regular_announcement(socket); + + begin_cache_response(RTR_PROTOCOL_VERSION_2, 0); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_WITHDRAW, 3300, ASNS()); + end_cache_response(RTR_PROTOCOL_VERSION_2, 0, 444); + + // No updates expected, fails at first PDU + EXPECT_NO_UPDATE_CALLBACKS(); + + assert(rtr_sync(socket) == RTR_ERROR); + assert(callback_index == callback_count); + + ASSERT_TABLE(socket, RECORD(1100, ASNS(1101, 1102, 1103, 1104))); +} + +static void test_announce_withdraw(struct rtr_socket *socket) +{ + // Test: Announce record, immediately withdraw within same sync op + // Expect: OK + // DB: no change + + // Announces 1100 => 1101, 1102, 1103, 1104 + test_regular_announcement(socket); + + begin_cache_response(RTR_PROTOCOL_VERSION_2, 0); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 3300, ASNS(3301, 3302, 3303, 3304)); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_WITHDRAW, 3300, ASNS()); + end_cache_response(RTR_PROTOCOL_VERSION_2, 0, 444); + +#if ASPA_NOTIFY_NO_OPS + EXPECT_UPDATE_CALLBACKS( + ADDED(RECORD(3300, ASNS(3301, 3302, 3303, 3304))), + REMOVED(RECORD(3300, ASNS(3301, 3302, 3303, 3304))), + ); +#else + EXPECT_NO_UPDATE_CALLBACKS(); +#endif + + assert(rtr_sync(socket) == RTR_SUCCESS); + assert(callback_index == callback_count); + + ASSERT_TABLE(socket, RECORD(1100, ASNS(1101, 1102, 1103, 1104))); +} + +static void test_withdraw_announce(struct rtr_socket *socket) +{ + // Test: Withdraw existing record, immediately announce record with + // same ASN within same sync op, include no-op + // Expect: OK + // DB: record gets replaced + + // announces 1100 -> 1101, 1102, 1103, 1104 + test_regular_announcement(socket); + + begin_cache_response(RTR_PROTOCOL_VERSION_2, 0); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_WITHDRAW, 1100, ASNS()); + // The following two are complementary (no-op) + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 1100, ASNS(2201, 2202, 2203, 2204)); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_WITHDRAW, 1100, ASNS()); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 1100, ASNS(2201, 2202, 2203, 2204)); + end_cache_response(RTR_PROTOCOL_VERSION_2, 0, 444); + +#if ASPA_NOTIFY_NO_OPS + EXPECT_UPDATE_CALLBACKS( + REMOVED(RECORD(1100, ASNS(1101, 1102, 1103, 1104))), + ADDED(RECORD(1100, ASNS(2201, 2202, 2203, 2204))), + REMOVED(RECORD(1100, ASNS(2201, 2202, 2203, 2204))), + ADDED(RECORD(1100, ASNS(2201, 2202, 2203, 2204))), + ); +#else + EXPECT_UPDATE_CALLBACKS( + REMOVED(RECORD(1100, ASNS(1101, 1102, 1103, 1104))), + ADDED(RECORD(1100, ASNS(2201, 2202, 2203, 2204))), + ); +#endif + + assert(rtr_sync(socket) == RTR_SUCCESS); + assert(callback_index == callback_count); + + ASSERT_TABLE(socket, RECORD(1100, ASNS(2201, 2202, 2203, 2204))); +} + +static void test_regular(struct rtr_socket *socket) +{ + // Test: regular announcements and withdrawals + // Expect: OK + // DB: withdrawn records get removed, newly announced are added + + begin_cache_response(RTR_PROTOCOL_VERSION_2, 0); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 1100, ASNS(1101, 1102, 1103, 1104)); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 1101, ASNS(1100, 1102, 1103, 1104)); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 2200, ASNS(2201, 2202, 2203, 2204)); + end_cache_response(RTR_PROTOCOL_VERSION_2, 0, 437); + + EXPECT_UPDATE_CALLBACKS( + ADDED(RECORD(1100, ASNS(1101, 1102, 1103, 1104))), + ADDED(RECORD(1101, ASNS(1100, 1102, 1103, 1104))), + ADDED(RECORD(2200, ASNS(2201, 2202, 2203, 2204))), + ); + + assert(rtr_sync(socket) == RTR_SUCCESS); + assert(callback_index == callback_count); + + ASSERT_TABLE(socket, + RECORD(1100, ASNS(1101, 1102, 1103, 1104)), + RECORD(1101, ASNS(1100, 1102, 1103, 1104)), + RECORD(2200, ASNS(2201, 2202, 2203, 2204)), + ); + + begin_cache_response(RTR_PROTOCOL_VERSION_2, 0); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 3300, ASNS(3301, 3302, 3303, 3304)); + end_cache_response(RTR_PROTOCOL_VERSION_2, 0, 444); + + EXPECT_UPDATE_CALLBACKS( + ADDED(RECORD(3300, ASNS(3301, 3302, 3303, 3304))), + ); + + assert(rtr_sync(socket) == RTR_SUCCESS); + assert(callback_index == callback_count); + + ASSERT_TABLE(socket, + RECORD(1100, ASNS(1101, 1102, 1103, 1104)), + RECORD(1101, ASNS(1100, 1102, 1103, 1104)), + RECORD(2200, ASNS(2201, 2202, 2203, 2204)), + RECORD(3300, ASNS(3301, 3302, 3303, 3304)) + ); + + begin_cache_response(RTR_PROTOCOL_VERSION_2, 0); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_WITHDRAW, 1100, ASNS()); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_WITHDRAW, 2200, ASNS()); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 1100, ASNS(1201, 1202, 1203, 1204)); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 0, ASNS()); + end_cache_response(RTR_PROTOCOL_VERSION_2, 0, 444); + + EXPECT_UPDATE_CALLBACKS( + ADDED(RECORD(0, ASNS())), + REMOVED(RECORD(1100, ASNS(1101, 1102, 1103, 1104))), + ADDED(RECORD(1100, ASNS(1201, 1202, 1203, 1204))), + REMOVED(RECORD(2200, ASNS(2201, 2202, 2203, 2204))), + ); + + assert(rtr_sync(socket) == RTR_SUCCESS); + assert(callback_index == callback_count); + + ASSERT_TABLE(socket, + RECORD(0, ASNS()), + RECORD(1100, ASNS(1201, 1202, 1203, 1204)), + RECORD(1101, ASNS(1100, 1102, 1103, 1104)), + RECORD(3300, ASNS(3301, 3302, 3303, 3304)), + ); + + begin_cache_response(RTR_PROTOCOL_VERSION_2, 0); + // The following two are complementary (no-op) + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 1901, ASNS(1201, 1202, 1203, 1204)); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_WITHDRAW, 1901, ASNS()); + end_cache_response(RTR_PROTOCOL_VERSION_2, 0, 444); + +#if ASPA_NOTIFY_NO_OPS + printf("Notifying No-Ops!"); + EXPECT_UPDATE_CALLBACKS( + ADDED(RECORD(1901, ASNS(1201, 1202, 1203, 1204))), + REMOVED(RECORD(1901, ASNS(1201, 1202, 1203, 1204))), + ); +#else + printf("Ignoring No-Ops!"); + EXPECT_NO_UPDATE_CALLBACKS(); +#endif + + assert(rtr_sync(socket) == RTR_SUCCESS); + assert(callback_index == callback_count); + + ASSERT_TABLE(socket, + RECORD(0, ASNS()), + RECORD(1100, ASNS(1201, 1202, 1203, 1204)), + RECORD(1101, ASNS(1100, 1102, 1103, 1104)), + RECORD(3300, ASNS(3301, 3302, 3303, 3304)), + ); +} + +static void test_regular_swap(struct rtr_socket *socket) +{ + test_regular(socket); + + ASSERT_TABLE(socket, + RECORD(0, ASNS()), + RECORD(1100, ASNS(1201, 1202, 1203, 1204)), + RECORD(1101, ASNS(1100, 1102, 1103, 1104)), + RECORD(3300, ASNS(3301, 3302, 3303, 3304)), + ); + + struct aspa_table *src_table = socket->aspa_table; + struct aspa_table *dst_table = lrtr_malloc(sizeof(struct aspa_table)); + + assert(dst_table); + aspa_table_init(dst_table, aspa_update_callback); + socket->aspa_table = dst_table; + + printf("creating existing data, soon to be overwritten...\n"); + + begin_cache_response(RTR_PROTOCOL_VERSION_2, 0); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 1100, ASNS(1101, 1102, 1103, 1104)); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 4400, ASNS(4401)); + end_cache_response(RTR_PROTOCOL_VERSION_2, 0, 437); + + EXPECT_UPDATE_CALLBACKS( + ADDED_TO(dst_table, RECORD(1100, ASNS(1101, 1102, 1103, 1104))), + ADDED_TO(dst_table, RECORD(4400, ASNS(4401))) + ); + + assert(rtr_sync(socket) == RTR_SUCCESS); + assert(callback_index == callback_count); + + ASSERT_TABLE(socket, + RECORD(1100, ASNS(1101, 1102, 1103, 1104)), + RECORD(4400, ASNS(4401)) + ); + + socket->aspa_table = src_table; + + printf("swapping...\n"); + + EXPECT_UPDATE_CALLBACKS( + // records in existing table will be removed... + REMOVED_FROM(src_table, RECORD(0, ASNS())), + REMOVED_FROM(src_table, RECORD(1100, ASNS(1201, 1202, 1203, 1204))), + REMOVED_FROM(src_table, RECORD(1101, ASNS(1100, 1102, 1103, 1104))), + REMOVED_FROM(src_table, RECORD(3300, ASNS(3301, 3302, 3303, 3304))), + + // records in new table will be replaced (removed, then added) + REMOVED_FROM(dst_table, RECORD(1100, ASNS(1101, 1102, 1103, 1104))), + REMOVED_FROM(dst_table, RECORD(4400, ASNS(4401))), + + // record previously removed from the existing table are added to new table + ADDED_TO(dst_table, RECORD(0, ASNS())), + ADDED_TO(dst_table, RECORD(1100, ASNS(1201, 1202, 1203, 1204))), + ADDED_TO(dst_table, RECORD(1101, ASNS(1100, 1102, 1103, 1104))), + ADDED_TO(dst_table, RECORD(3300, ASNS(3301, 3302, 3303, 3304))), + ); + + assert(aspa_table_src_replace(dst_table, src_table, socket, true, true) == ASPA_SUCCESS); + assert(callback_index == callback_count); + + aspa_table_free(dst_table, false); + lrtr_free(dst_table); +} + +static void test_withdraw_twice(struct rtr_socket *socket) +{ + // Test: duplicate in-sequence withdrawal + // Expect: Error + // DB: records get removed, newly announced are added + + begin_cache_response(RTR_PROTOCOL_VERSION_2, 0); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 1900, ASNS(1901, 1902, 1903, 1904)); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 1901, ASNS(1900, 1902, 1903, 1904)); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 2200, ASNS(2201, 2202, 2203, 2204)); + end_cache_response(RTR_PROTOCOL_VERSION_2, 0, 437); + + EXPECT_UPDATE_CALLBACKS( + ADDED(RECORD(1900, ASNS(1901, 1902, 1903, 1904))), + ADDED(RECORD(1901, ASNS(1900, 1902, 1903, 1904))), + ADDED(RECORD(2200, ASNS(2201, 2202, 2203, 2204))), + ); + + assert(rtr_sync(socket) == RTR_SUCCESS); + assert(callback_index == callback_count); + + ASSERT_TABLE(socket, + RECORD(1900, ASNS(1901, 1902, 1903, 1904)), + RECORD(1901, ASNS(1900, 1902, 1903, 1904)), + RECORD(2200, ASNS(2201, 2202, 2203, 2204)), + ); + + begin_cache_response(RTR_PROTOCOL_VERSION_2, 0); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 3300, ASNS(3301, 3302, 3303, 3304)); + end_cache_response(RTR_PROTOCOL_VERSION_2, 0, 444); + + EXPECT_UPDATE_CALLBACKS( + ADDED(RECORD(3300, ASNS(3301, 3302, 3303, 3304))), + ); + + assert(rtr_sync(socket) == RTR_SUCCESS); + assert(callback_index == callback_count); + + ASSERT_TABLE(socket, + RECORD(1900, ASNS(1901, 1902, 1903, 1904)), + RECORD(1901, ASNS(1900, 1902, 1903, 1904)), + RECORD(2200, ASNS(2201, 2202, 2203, 2204)), + RECORD(3300, ASNS(3301, 3302, 3303, 3304)) + ); + + begin_cache_response(RTR_PROTOCOL_VERSION_2, 0); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_WITHDRAW, 1900, ASNS()); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_WITHDRAW, 2200, ASNS()); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_WITHDRAW, 1900, ASNS()); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 1900, ASNS(1201, 1202, 1203, 1204)); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 0, ASNS()); + end_cache_response(RTR_PROTOCOL_VERSION_2, 0, 444); + + // Callback behavior deviates for different update mechanisms + // Swap-In: No callback because update computation fails + // In-Place: Callbacks until failed operation, then callbacks from undo + assert_callbacks = false; + assert(rtr_sync(socket) == RTR_ERROR); + assert_callbacks = true; + + ASSERT_TABLE(socket, + RECORD(1900, ASNS(1901, 1902, 1903, 1904)), + RECORD(1901, ASNS(1900, 1902, 1903, 1904)), + RECORD(2200, ASNS(2201, 2202, 2203, 2204)), + RECORD(3300, ASNS(3301, 3302, 3303, 3304)), + ); +} + +static void test_announce_withdraw_announce_twice(struct rtr_socket *socket) +{ + // Test: regular announcements and withdrawals + // Expect: OK + // DB: records get removed, newly announced are added + + begin_cache_response(RTR_PROTOCOL_VERSION_2, 0); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 1400, ASNS(1401, 1402, 1403, 1404)); + end_cache_response(RTR_PROTOCOL_VERSION_2, 0, 437); + + EXPECT_UPDATE_CALLBACKS( + ADDED(RECORD(1400, ASNS(1401, 1402, 1403, 1404))), + ); + + assert(rtr_sync(socket) == RTR_SUCCESS); + + ASSERT_TABLE(socket, + RECORD(1400, ASNS(1401, 1402, 1403, 1404)), + ); + + begin_cache_response(RTR_PROTOCOL_VERSION_2, 0); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_WITHDRAW, 1400, ASNS()); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 1400, ASNS(1201, 1202, 1203, 1204)); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 1400, ASNS(1201, 1202, 1203)); + end_cache_response(RTR_PROTOCOL_VERSION_2, 0, 444); + + // Callback behavior deviates for different update mechanisms + // Swap-In: No callback because update computation fails + // In-Place: Callbacks until failed operation, then callbacks from undo + assert_callbacks = false; + assert(rtr_sync(socket) == RTR_ERROR); + assert_callbacks = true; + + ASSERT_TABLE(socket, + RECORD(1400, ASNS(1401, 1402, 1403, 1404)), + ); +} + +static void test_multiple_syncs(struct rtr_socket *socket) +{ + const size_t PROVIDER_COUNT = 4; + // amount of ASPAs to be withdrawn (twice as much are added) + const size_t N = 256 + 4; + + // initial cycle: add two new aspas + begin_cache_response(RTR_PROTOCOL_VERSION_2, 0); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 1, ASNS(2, 3, 4, 5)); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 2, ASNS(3, 4, 5, 6)); + end_cache_response(RTR_PROTOCOL_VERSION_2, 0, 437); + + EXPECT_UPDATE_CALLBACKS( + ADDED(RECORD(1, ASNS(2, 3, 4, 5))), + ADDED(RECORD(2, ASNS(3, 4, 5, 6))), + ); + + assert(rtr_sync(socket) == RTR_SUCCESS); + assert(callback_index == callback_count); + + ASSERT_TABLE(socket, + RECORD(1, ASNS(2, 3, 4, 5)), + RECORD(2, ASNS(3, 4, 5, 6)), + ); + + // repeated cycle: withdraw one, announce two new + // store expected provider asns, starting with providers for c_an = 2 + uint32_t provider_asns[(2 * N - 1) * PROVIDER_COUNT]; + + // enter provider_asns for ASPA record 2 => {3, 4, 5, 6} + for (size_t i = 0; i < PROVIDER_COUNT; i++) + provider_asns[i] = i + 3; + + // customer asn to be withdrawn + for (uint32_t c_wd = 1; c_wd < N; c_wd++) { + // first customer asn to be announced + uint32_t c_an = 2 * c_wd + 1; + + begin_cache_response(RTR_PROTOCOL_VERSION_2, 0); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_WITHDRAW, c_wd, + ASNS()); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, c_an, + ASNS(c_an + 1, c_an + 2, c_an + 3, c_an + 4)); + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, c_an + 1, + ASNS(c_an + 2, c_an + 3, c_an + 4, c_an + 5)); + + end_cache_response(RTR_PROTOCOL_VERSION_2, 0, 444); + + EXPECT_UPDATE_CALLBACKS( + REMOVED(RECORD(c_wd, ASNS(c_wd + 1, c_wd + 2, c_wd + 3, c_wd + 4))), + ADDED(RECORD(c_an, ASNS(c_an + 1, c_an + 2, c_an + 3, c_an + 4))), + ADDED(RECORD(c_an + 1, ASNS(c_an + 2, c_an + 3, c_an + 4, c_an + 5))), + ); + assert(rtr_sync(socket) == RTR_SUCCESS); + assert(callback_index == callback_count); + + // store announced ASPAs' providers for validation + for (size_t i = 0; i < PROVIDER_COUNT; i++) { + provider_asns[PROVIDER_COUNT * (size_t)(c_an - 2) + i] = c_an + (uint32_t)i + 1; + provider_asns[PROVIDER_COUNT * (size_t)(c_an - 1) + i] = c_an + (uint32_t)i + 2; + } + + // build array of all records expected to be in aspa_table + // (customer asns after last withdrawn until including last announced) + size_t record_count = c_wd + 2; + struct aspa_record records[record_count]; + + for (size_t c_ex = c_wd + 1; c_ex <= c_an + 1; c_ex++) { + records[c_ex - (c_wd + 1)] = + ((struct aspa_record) { + .customer_asn = (uint32_t)c_ex, + .provider_count = PROVIDER_COUNT, + .provider_asns = &provider_asns[PROVIDER_COUNT * (c_ex - 2)] + }); + } + assert_table(socket, records, record_count); + } +} + +static void test_many_pdus(struct rtr_socket *socket) +{ + const size_t PROVIDER_COUNT = 4; + // amount of ASPAs to be withdrawn (twice as much are added) + const size_t N = 256 + 4; + + // providers (ascending integers) + uint32_t provider_asns[N + PROVIDER_COUNT - 1]; + + for (size_t i = 0; i < N + PROVIDER_COUNT - 1; i++) + provider_asns[i] = i + 2; + + // aspa pdus + begin_cache_response(RTR_PROTOCOL_VERSION_2, 0); + + for (size_t i = 0; i < N; i++) + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, i + 1, ASNS(i + 2, i + 3, i + 4, i + 5)); + + end_cache_response(RTR_PROTOCOL_VERSION_2, 0, 437); + + // records to verify aspa_table + struct aspa_record records[N]; + // callbacks + struct update_callback callbacks[N]; + + for (size_t i = 0; i < N; i++) { + records[i] = (struct aspa_record){ + .customer_asn = i + 1, + .provider_count = PROVIDER_COUNT, + .provider_asns = &provider_asns[i] + }; + callbacks[i] = (struct update_callback){ + .source = NULL, + .record = records[i], + .type = ASPA_ADD + }; + } + + expect_update_callbacks(callbacks, N); + + // sync + assert(rtr_sync(socket) == RTR_SUCCESS); + assert(callback_index == callback_count); + + assert_table(socket, records, N); +} + +static void test_corrupt(struct rtr_socket *socket) +{ + // Test: send corrupt pdu after having received valid data + // Expect: Error + // DB: DB stays the same + + test_regular(socket); + + begin_cache_response(RTR_PROTOCOL_VERSION_2, 0); + struct pdu_aspa *aspa = + APPEND_ASPA(RTR_PROTOCOL_VERSION_2, ASPA_ANNOUNCE, 4400, ASNS(4401, 4402, 4403, 4404)); + + // corrupt ASPA len + aspa->len = BYTES32(BYTES32(aspa->len) + 1); + + end_cache_response(RTR_PROTOCOL_VERSION_2, 0, 444); + + // No updates expected, fails at first PDU + EXPECT_NO_UPDATE_CALLBACKS(); + assert(rtr_sync(socket) == RTR_ERROR); + assert(callback_index == callback_count); + + ASSERT_TABLE(socket, + RECORD(0, ASNS()), + RECORD(1100, ASNS(1201, 1202, 1203, 1204)), + RECORD(1101, ASNS(1100, 1102, 1103, 1104)), + RECORD(3300, ASNS(3301, 3302, 3303, 3304)) + ); +} + +// clang-format on + +static void cleanup(struct rtr_socket **socket) +{ + printf("cleaning...\n"); + + if (data) { + lrtr_free(data); + data = NULL; + data_size = 0; + data_index = 0; + } + + if (socket && *socket) { + if ((*socket)->aspa_table) { + aspa_table_free((*socket)->aspa_table, false); + lrtr_free((*socket)->aspa_table); + } + + if ((*socket)->spki_table) { + spki_table_free_without_notify((*socket)->spki_table); + lrtr_free((*socket)->spki_table); + } + + if ((*socket)->pfx_table) { + pfx_table_free_without_notify((*socket)->pfx_table); + lrtr_free((*socket)->pfx_table); + } + + if ((*socket)->tr_socket) + lrtr_free((*socket)->tr_socket); + + lrtr_free(*socket); + *socket = NULL; + } + + clear_expected_callbacks(); +} + +static struct rtr_socket *create_socket(bool is_resetting) +{ + struct tr_socket *tr_socket = lrtr_calloc(1, sizeof(struct tr_socket)); + + tr_socket->recv_fp = (tr_recv_fp)&custom_recv; + tr_socket->send_fp = (tr_send_fp)&custom_send; + + struct rtr_socket *socket = lrtr_calloc(1, sizeof(struct rtr_socket)); + + assert(socket); + socket->is_resetting = is_resetting; + socket->version = 2; + socket->state = RTR_SYNC; + socket->tr_socket = tr_socket; + + struct aspa_table *aspa_table = lrtr_malloc(sizeof(struct aspa_table)); + + assert(aspa_table); + aspa_table_init(aspa_table, aspa_update_callback); + socket->aspa_table = aspa_table; + + struct spki_table *spki_table = lrtr_malloc(sizeof(struct spki_table)); + + spki_table_init(spki_table, NULL); + socket->spki_table = spki_table; + + struct pfx_table *pfx_table = lrtr_malloc(sizeof(struct pfx_table)); + + pfx_table_init(pfx_table, NULL); + socket->pfx_table = pfx_table; + + return socket; +} + +static void run_tests(bool is_resetting) +{ + struct rtr_socket *socket = NULL; + + cleanup(&socket); + + printf("\nTEST: test_regular_announcement\n"); + socket = create_socket(is_resetting); + test_no_aspa(socket); + + cleanup(&socket); + + printf("\nTEST: test_regular_announcement\n"); + socket = create_socket(is_resetting); + test_regular_announcement(socket); + + cleanup(&socket); + + printf("\nTEST: regular_announcement\n"); + socket = create_socket(is_resetting); + test_regular_announcement(socket); + + cleanup(&socket); + + printf("\nTEST: regular_announcements\n"); + socket = create_socket(is_resetting); + test_regular_announcements(socket); + + cleanup(&socket); + + printf("\nTEST: withdraw\n"); + socket = create_socket(is_resetting); + test_withdraw(socket); + + cleanup(&socket); + + printf("\nTEST: announce_existing\n"); + socket = create_socket(is_resetting); + test_announce_existing(socket); + + cleanup(&socket); + + printf("\nTEST: announce_twice\n"); + socket = create_socket(is_resetting); + test_announce_twice(socket); + + cleanup(&socket); + + printf("\nTEST: withdraw_nonexisting\n"); + socket = create_socket(is_resetting); + test_withdraw_nonexisting(socket); + + cleanup(&socket); + + printf("\nTEST: announce_withdraw\n"); + socket = create_socket(is_resetting); + test_announce_withdraw(socket); + + cleanup(&socket); + + printf("\nTEST: withdraw_announce\n"); + socket = create_socket(is_resetting); + test_withdraw_announce(socket); + + cleanup(&socket); + + printf("\nTEST: regular\n"); + socket = create_socket(is_resetting); + test_regular(socket); + + cleanup(&socket); + + printf("\nTEST: regular_swap\n"); + socket = create_socket(is_resetting); + test_regular_swap(socket); + + cleanup(&socket); + + printf("\nTEST: withdraw_twice\n"); + socket = create_socket(is_resetting); + test_withdraw_twice(socket); + + cleanup(&socket); + + printf("\nTEST: corrupt\n"); + socket = create_socket(is_resetting); + test_corrupt(socket); + + cleanup(&socket); + + printf("\nTEST: announce_withdraw_announce_twice\n"); + socket = create_socket(is_resetting); + test_announce_withdraw_announce_twice(socket); + + cleanup(&socket); + + printf("\nTEST: multiple_syncs\n"); + socket = create_socket(is_resetting); + test_multiple_syncs(socket); + + cleanup(&socket); + + printf("\nTEST: many_pdus\n"); + socket = create_socket(is_resetting); + test_many_pdus(socket); + + cleanup(&socket); +} + +int main(void) +{ + run_tests(false); + + printf("\n\nTesting with atomic reset...\n"); + run_tests(true); +} diff --git a/tests/test_aspa_array.c b/tests/test_aspa_array.c new file mode 100644 index 00000000..dd51b237 --- /dev/null +++ b/tests/test_aspa_array.c @@ -0,0 +1,212 @@ +/* + * 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 "rtrlib/aspa/aspa_array/aspa_array.h" +#include "rtrlib/aspa/aspa_private.h" +#include "rtrlib/lib/alloc_utils_private.h" + +#include + +#define ASNS(...) ((uint32_t[]){__VA_ARGS__}) + +#define SEEDED_ASNS(seed) ASNS(seed, seed + 1, seed + 2) + +// clang-format off + +#define RECORD(cas, providers) \ + ((struct aspa_record){.customer_asn = cas, \ + .provider_count = (size_t)(sizeof(providers) / sizeof(uint32_t)), \ + .provider_asns = sizeof(providers) == 0 ? NULL : providers}) + +// clang-format on + +static void test_create_array(void) +{ + struct aspa_array *array; + + assert(aspa_array_create(&array) == ASPA_SUCCESS); + assert(array->data); + assert(array->size == 0); + assert(array->capacity >= 128); + + aspa_array_free(array, true); +}; + +static void test_add_element(void) +{ + struct aspa_array *array; + + assert(aspa_array_create(&array) == ASPA_SUCCESS); + + struct aspa_record record = RECORD(42, SEEDED_ASNS(300)); + + assert(aspa_array_insert(array, 0, &record, true) == 0); + assert(array->data[0].customer_asn == 42); + assert(array->data[0].provider_count == 3); + assert(array->data[0].provider_asns[0] == 300); + assert(array->data[0].provider_asns[1] == 300 + 1); + assert(array->data[0].provider_asns[2] == 300 + 2); + + aspa_array_free(array, true); +} + +static void test_insert(void) +{ + struct aspa_array *array; + + assert(aspa_array_create(&array) == ASPA_SUCCESS); + array->capacity = 2; + struct aspa_record *old_pointer = array->data; + + struct aspa_record record_4 = RECORD(4, SEEDED_ASNS(600)); + + assert(aspa_array_insert(array, 0, &record_4, true) == ASPA_SUCCESS); + + struct aspa_record record_1 = RECORD(1, SEEDED_ASNS(300)); + + assert(aspa_array_insert(array, 1, &record_1, true) == ASPA_SUCCESS); + + struct aspa_record record_2 = RECORD(2, SEEDED_ASNS(400)); + + // Verify shifting works + assert(aspa_array_insert(array, 1, &record_2, true) == ASPA_SUCCESS); + + struct aspa_record record_3 = RECORD(3, SEEDED_ASNS(500)); + + assert(aspa_array_insert(array, 3, &record_3, true) == ASPA_SUCCESS); + + // New pointer because reallocated + assert(old_pointer != array->data); + assert(array->capacity >= 4); + assert(array->size == 4); + + assert(array->data[0].customer_asn == 4); + assert(array->data[1].customer_asn == 2); + assert(array->data[2].customer_asn == 1); + assert(array->data[3].customer_asn == 3); + + aspa_array_free(array, true); +} + +static void test_append(void) +{ + struct aspa_array *array; + + assert(aspa_array_create(&array) == ASPA_SUCCESS); + array->capacity = 2; + struct aspa_record *old_pointer = array->data; + + struct aspa_record record_4 = RECORD(4, SEEDED_ASNS(600)); + + assert(aspa_array_append(array, &record_4, true) == ASPA_SUCCESS); + + struct aspa_record record_2 = RECORD(2, SEEDED_ASNS(400)); + + assert(aspa_array_append(array, &record_2, true) == ASPA_SUCCESS); + + struct aspa_record record_1 = RECORD(1, SEEDED_ASNS(300)); + + assert(aspa_array_append(array, &record_1, true) == ASPA_SUCCESS); + + struct aspa_record record_3 = RECORD(3, SEEDED_ASNS(500)); + + assert(aspa_array_append(array, &record_3, true) == ASPA_SUCCESS); + + // new pointer because reallocated + assert(old_pointer != array->data); + assert(array->capacity >= 4); + assert(array->size == 4); + + assert(array->data[0].customer_asn == 4); + assert(array->data[1].customer_asn == 2); + assert(array->data[2].customer_asn == 1); + assert(array->data[3].customer_asn == 3); + + aspa_array_free(array, true); +} + +static void test_remove_element(void) +{ + struct aspa_array *array; + + assert(aspa_array_create(&array) == 0); + + struct aspa_record record_1 = RECORD(1, SEEDED_ASNS(300)); + + assert(aspa_array_insert(array, 0, &record_1, true) == ASPA_SUCCESS); + + struct aspa_record record_2 = RECORD(2, SEEDED_ASNS(400)); + + assert(aspa_array_insert(array, 1, &record_2, true) == ASPA_SUCCESS); + + struct aspa_record record_3 = RECORD(3, SEEDED_ASNS(500)); + + assert(aspa_array_insert(array, 2, &record_3, true) == ASPA_SUCCESS); + + struct aspa_record record_4 = RECORD(4, SEEDED_ASNS(600)); + + assert(aspa_array_insert(array, 3, &record_4, true) == ASPA_SUCCESS); + + assert(array->data[2].customer_asn == 3); + + assert(aspa_array_remove(array, 2, true) == ASPA_SUCCESS); + assert(aspa_array_remove(array, 100, true) == ASPA_RECORD_NOT_FOUND); + + assert(array->size == 3); + assert(array->data[0].customer_asn == 1); + assert(array->data[1].customer_asn == 2); + assert(array->data[2].customer_asn == 4); + + aspa_array_free(array, true); +} + +static void test_find_element(void) +{ + struct aspa_array *array; + + assert(aspa_array_create(&array) == 0); + + struct aspa_record record_1 = RECORD(1, SEEDED_ASNS(300)); + + assert(aspa_array_insert(array, 0, &record_1, true) == 0); + + struct aspa_record record_2 = RECORD(2, SEEDED_ASNS(400)); + + assert(aspa_array_insert(array, 1, &record_2, true) == 0); + + struct aspa_record record_3 = RECORD(3, SEEDED_ASNS(500)); + + assert(aspa_array_insert(array, 2, &record_3, true) == 0); + + struct aspa_record record_4 = RECORD(4, SEEDED_ASNS(600)); + + assert(aspa_array_insert(array, 3, &record_4, true) == 0); + + struct aspa_record record_5 = RECORD(5, SEEDED_ASNS(700)); + + assert(aspa_array_insert(array, 4, &record_5, true) == 0); + + assert(aspa_array_search(array, 1) == &array->data[0]); + assert(aspa_array_search(array, 2) == &array->data[1]); + assert(aspa_array_search(array, 3) == &array->data[2]); + assert(aspa_array_search(array, 4) == &array->data[3]); + assert(aspa_array_search(array, 5) == &array->data[4]); + + aspa_array_free(array, true); +} + +int main(void) +{ + test_create_array(); + test_add_element(); + test_insert(); + test_append(); + test_remove_element(); + test_find_element(); +} diff --git a/tests/test_dynamic_groups.c b/tests/test_dynamic_groups.c index ae511ca6..984574f7 100644 --- a/tests/test_dynamic_groups.c +++ b/tests/test_dynamic_groups.c @@ -61,7 +61,7 @@ int main(void) struct rtr_mgr_config *conf; - rtr_mgr_init(&conf, groups, 1, 30, 600, 600, NULL, NULL, &connection_status_callback, NULL); + rtr_mgr_init(&conf, groups, 1, 30, 600, 600, NULL, NULL, NULL, &connection_status_callback, NULL); //start the connection manager rtr_mgr_start(conf); diff --git a/tests/test_live_fetching.c b/tests/test_live_fetching.c new file mode 100644 index 00000000..5a622029 --- /dev/null +++ b/tests/test_live_fetching.c @@ -0,0 +1,114 @@ +/* + * 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 "rtrlib/aspa/aspa_private.h" +#include "rtrlib/rtrlib.h" + +#include +#include +#include +#include + +struct test_validity_query { + const char *pfx; + int len; + int asn; + unsigned int val; +}; + +/* + * Verification is based on ROAs for RIPE RIS Routing Beacons, see: + * (https://www.ripe.net/analyse/internet-measurements/ + * routing-information-service-ris/current-ris-routing-beacons) + */ +const int connection_timeout = 20; +enum rtr_mgr_status connection_status = -1; + +static void connection_status_callback(const struct rtr_mgr_group *group __attribute__((unused)), + enum rtr_mgr_status status, + const struct rtr_socket *socket __attribute__((unused)), + void *data __attribute__((unused))) +{ + if (status == RTR_MGR_ERROR) + connection_status = status; +} + +/** + * @brief live prefix validation test + * This test requires an active network connection. It runs an on-line live + * validation of specific IP prefixes, i.e., RIPE BGP beacons, that have known + * RPKI validation states. This tests uses a TCP transport connection. + */ +int main(void) +{ + /* These variables are not in the global scope + * because it would cause warnings about discarding constness + */ + + //char RPKI_CACHE_HOST[] = "rpki-validator.realmv6.org"; + //char RPKI_CACHE_PORT[] = "8283"; + + // REPLACE THIS BY YOUR RTR SERVER + char RPKI_CACHE_HOST[] = "rtrlab.tanneberger.me"; + char RPKI_CACHE_PORT[] = "3325"; // rir rtr + //char RPKI_CACHE_PORT[] = "3324"; // aspa rtr + + /* create a TCP transport socket */ + struct tr_socket tr_tcp; + struct tr_tcp_config tcp_config = {RPKI_CACHE_HOST, RPKI_CACHE_PORT, NULL, NULL, NULL, 0}; + struct rtr_socket rtr_tcp; + struct rtr_mgr_group groups[1]; + + /* init a TCP transport and create rtr socket */ + tr_tcp_init(&tcp_config, &tr_tcp); + rtr_tcp.tr_socket = &tr_tcp; + + /* create a rtr_mgr_group array with 1 element */ + groups[0].sockets = malloc(1 * sizeof(struct rtr_socket *)); + groups[0].sockets_len = 1; + groups[0].sockets[0] = &rtr_tcp; + groups[0].preference = 1; + + struct rtr_mgr_config *conf; + + if (rtr_mgr_init(&conf, groups, 1, 30, 600, 600, NULL, NULL, NULL, &connection_status_callback, NULL) < 0) + return EXIT_FAILURE; + + rtr_mgr_start(conf); + int sleep_counter = 0; + /* wait for connection, or timeout and exit eventually */ + while (!rtr_mgr_conf_in_sync(conf)) { + if (connection_status == RTR_MGR_ERROR) + return EXIT_FAILURE; + + sleep(1); + sleep_counter++; + if (sleep_counter >= connection_timeout) + return EXIT_FAILURE; + } + + /* printing all fetched objects */ + struct aspa_array *array = ((*groups[0].sockets)->aspa_table->store)->aspa_array; + + printf("ASPA:\n"); + for (uint32_t i = 0; i < array->size; i++) { + printf("CAS %u => [ ", array->data[i].customer_asn); + for (uint32_t j = 0; j < array->data[i].provider_count; j++) { + printf("%u", array->data[i].provider_asns[j]); + if (j < array->data[i].provider_count - 1) + printf(", "); + } + printf(" ]\n"); + } + + rtr_mgr_stop(conf); + rtr_mgr_free(conf); + + return EXIT_SUCCESS; +} diff --git a/tests/test_live_validation.c b/tests/test_live_validation.c index b90754c4..2f4a2800 100644 --- a/tests/test_live_validation.c +++ b/tests/test_live_validation.c @@ -85,7 +85,7 @@ int main(void) struct rtr_mgr_config *conf; - if (rtr_mgr_init(&conf, groups, 1, 30, 600, 600, NULL, NULL, &connection_status_callback, NULL) < 0) + if (rtr_mgr_init(&conf, groups, 1, 30, 600, 600, NULL, NULL, NULL, &connection_status_callback, NULL) < 0) return EXIT_FAILURE; rtr_mgr_start(conf); diff --git a/tests/unittests/test_packets_static.c b/tests/unittests/test_packets_static.c index b8589d78..88eac9c5 100644 --- a/tests/unittests/test_packets_static.c +++ b/tests/unittests/test_packets_static.c @@ -112,29 +112,59 @@ static void test_rtr_get_pdu_type(void **state) static void test_pdu_to_network_byte_order(void **state) { - struct pdu_serial_query pdu; + struct pdu_serial_query pdu_serial; + struct pdu_aspa *aspa = malloc(24); - UNUSED(state); - - pdu.ver = 0; - pdu.type = SERIAL_QUERY; - pdu.session_id = 0x32A5; - pdu.len = 0xC; - pdu.sn = 0xCC56E9BB; + memset(aspa, 0, 24); - rtr_pdu_to_network_byte_order(&pdu); + UNUSED(state); - assert_int_equal(pdu.ver, 0); - assert_int_equal(pdu.type, SERIAL_QUERY); - assert_int_equal(pdu.session_id, 0xA532); - assert_int_equal(pdu.len, 0x0C000000); - assert_int_equal(pdu.sn, 0xBBE956CC); + pdu_serial.ver = 0; + pdu_serial.type = SERIAL_QUERY; + pdu_serial.session_id = 0x32A5; + pdu_serial.len = 0xC; + pdu_serial.sn = 0xCC56E9BB; + + rtr_pdu_to_network_byte_order(&pdu_serial); + + assert_int_equal(pdu_serial.ver, 0); + assert_int_equal(pdu_serial.type, SERIAL_QUERY); + assert_int_equal(pdu_serial.session_id, 0xA532); + assert_int_equal(pdu_serial.len, 0x0C000000); + assert_int_equal(pdu_serial.sn, 0xBBE956CC); + + aspa->ver = 2; + aspa->type = ASPA; + aspa->zero = 0; + aspa->flags = 0xC6; + aspa->afi_flags = 0xC6; + aspa->provider_count = 2; + aspa->len = 0x18; + aspa->customer_asn = 0xCC56E9BB; + aspa->provider_asns[0] = 0xBADCFE00; + aspa->provider_asns[1] = 0x1122; + + rtr_pdu_to_network_byte_order(aspa); + + assert_int_equal(aspa->ver, 2); + assert_int_equal(aspa->type, ASPA); + assert_int_equal(aspa->zero, 0); + assert_int_equal(aspa->flags, 0xC6); + assert_int_equal(aspa->afi_flags, 0xC6); + assert_int_equal(aspa->len, 0x18000000); + assert_int_equal(aspa->customer_asn, 0xBBE956CC); + assert_int_equal(aspa->provider_count, 0x0200); + assert_int_equal(aspa->provider_asns[0], 0xFEDCBA); + assert_int_equal(aspa->provider_asns[1], 0x22110000); } static void test_pdu_to_host_byte_order(void **state) { struct pdu_serial_notify pdu_serial; - struct pdu_end_of_data_v1 pdu_eod; + struct pdu_end_of_data_v1_v2 pdu_eod; + struct pdu_aspa *aspa = malloc(24); + + memset(aspa, 0, 24); UNUSED(state); @@ -172,17 +202,41 @@ static void test_pdu_to_host_byte_order(void **state) assert_int_equal(pdu_eod.refresh_interval, 0xAF000000); assert_int_equal(pdu_eod.retry_interval, 0xDC000000); assert_int_equal(pdu_eod.expire_interval, 0xCF0C0000); + + aspa->ver = 2; + aspa->type = ASPA; + aspa->zero = 0; + aspa->flags = 0xC6; + aspa->afi_flags = 0xC6; + aspa->provider_count = 0x0200; + aspa->len = 0x18000000; + aspa->customer_asn = 0xBBE956CC; + aspa->provider_asns[0] = 0xFEDCBA; + aspa->provider_asns[1] = 0x22110000; + + rtr_pdu_header_to_host_byte_order(aspa); + rtr_pdu_footer_to_host_byte_order(aspa); + + assert_int_equal(aspa->ver, 2); + assert_int_equal(aspa->type, ASPA); + assert_int_equal(aspa->zero, 0); + assert_int_equal(aspa->flags, 0xC6); + assert_int_equal(aspa->afi_flags, 0xC6); + assert_int_equal(aspa->len, 0x18); + assert_int_equal(aspa->customer_asn, 0xCC56E9BB); + assert_int_equal(aspa->provider_count, 2); + assert_int_equal(aspa->provider_asns[0], 0xBADCFE00); + assert_int_equal(aspa->provider_asns[1], 0x1122); } static void test_rtr_pdu_check_size(void **state) { struct pdu_header pdu; - struct pdu_error *error = malloc(30); + struct pdu_error *error = lrtr_calloc(1, 30); + struct pdu_aspa *aspa = lrtr_calloc(1, sizeof(struct pdu_aspa) + 2 * sizeof(uint32_t)); UNUSED(state); - memset(error, 0, 30); - pdu.type = SERIAL_NOTIFY; pdu.len = 20; assert_false(rtr_pdu_check_size(&pdu)); @@ -258,6 +312,16 @@ static void test_rtr_pdu_check_size(void **state) error->len = 24; error->rest[11] = 0xA; assert_false(rtr_pdu_check_size((struct pdu_header *)error)); + + /* Test ASPA pdu size checks */ + aspa->type = ASPA; + aspa->len = 25; + aspa->provider_count = 0x0200; + assert_false(rtr_pdu_check_size((struct pdu_header *)aspa)); + aspa->len = 23; + assert_false(rtr_pdu_check_size((struct pdu_header *)aspa)); + aspa->len = 24; + assert_true(rtr_pdu_check_size((struct pdu_header *)aspa)); } static void test_rtr_send_error_pdu(void **state) @@ -313,7 +377,7 @@ static void test_rtr_pdu_check_interval(void **state) UNUSED(state); struct rtr_socket rtr_socket; - struct pdu_end_of_data_v1 pdu_eod; + struct pdu_end_of_data_v1_v2 pdu_eod; int retval; From 7301f7bbafee0045fba459b2b44cf69f02bbc769 Mon Sep 17 00:00:00 2001 From: revol-xut Date: Wed, 31 Jan 2024 09:31:07 +0100 Subject: [PATCH 21/55] tools: update rtrclient to support aspa - update main cmake file Co-authored-by: mrzslz Co-authored-by: carl <115627588+carl-tud@users.noreply.github.com> --- CMakeLists.txt | 5 +++- tools/rpki-rov.c | 2 +- tools/rtrclient.1 | 16 +++++++---- tools/rtrclient.c | 71 +++++++++++++++++++++++++++++++++++++++++------ tools/templates.h | 25 +++++++++++++++++ 5 files changed, 102 insertions(+), 17 deletions(-) create mode 100644 tools/templates.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a4de639..c23fd321 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,7 +47,8 @@ set(RTRLIB_SRC rtrlib/rtr_mgr.c rtrlib/lib/utils.c rtrlib/lib/alloc_utils.c rtrl rtrlib/lib/ip.c rtrlib/lib/ipv4.c rtrlib/lib/ipv6.c rtrlib/lib/log.c rtrlib/pfx/trie/trie.c rtrlib/pfx/trie/trie-pfx.c rtrlib/transport/transport.c rtrlib/transport/tcp/tcp_transport.c rtrlib/rtr/rtr.c rtrlib/rtr/packets.c - rtrlib/spki/hashtable/ht-spkitable.c ${tommyds}) + rtrlib/spki/hashtable/ht-spkitable.c rtrlib/aspa/aspa_array/aspa_array.c + rtrlib/aspa/aspa.c rtrlib/aspa/aspa_verification.c ${tommyds}) set(RTRLIB_LINK ${RT_LIB} ${CMAKE_THREAD_LIBS_INIT}) include(FindPkgConfig) @@ -140,6 +141,8 @@ ADD_TEST(test_getbits tests/test_getbits) ADD_TEST(test_dynamic_groups tests/test_dynamic_groups) list(APPEND CMAKE_CTEST_ARGUMENTS "--output-on-failure") +ADD_TEST(test_as_path_verification tests/test_as_path_verification) + if(RTRLIB_BGPSEC_ENABLED) ADD_TEST(test_bgpsec tests/test_bgpsec) endif(RTRLIB_BGPSEC_ENABLED) diff --git a/tools/rpki-rov.c b/tools/rpki-rov.c index 4089a888..f24bcad4 100644 --- a/tools/rpki-rov.c +++ b/tools/rpki-rov.c @@ -82,7 +82,7 @@ int main(int argc, char *argv[]) groups[0].sockets[0] = &rtr_tcp; groups[0].preference = 1; - if (rtr_mgr_init(&conf, groups, 1, 30, 600, 600, NULL, NULL, &connection_status_callback, NULL) < 0) + if (rtr_mgr_init(&conf, groups, 1, 30, 600, 600, NULL, NULL, NULL, &connection_status_callback, NULL) < 0) return EXIT_FAILURE; rtr_mgr_start(conf); diff --git a/tools/rtrclient.1 b/tools/rtrclient.1 index f9123a4e..fdb4f5c6 100644 --- a/tools/rtrclient.1 +++ b/tools/rtrclient.1 @@ -11,23 +11,23 @@ rtrclient \- rtr rpki client .SH SYNOPSIS .B rtrclient -[\fB\-kph\fR] +[\fB\-kpah\fR] .I SOCKETS\fR... .SH SOCKETS .B tcp -[\fB\-kpb \fIbindaddr\fR] +[\fB\-kpab \fIbindaddr\fR] .IR HOST .IR PORT .br .B ssh -[\fB\-kpb \fIbindaddr\fR] +[\fB\-kpab \fIbindaddr\fR] .IR HOST .IR PORT .IR USERNAME (\fIPRIVATE_KEY\fR|\fIPASSWORD\fR) [\fIHOST_KEY\fR] .SH DESCRIPTION -\fBrtrclient\fR connects to an RPKI/RTR cache server and prints prefix, origin AS, and router key updates. +\fBrtrclient\fR connects to an RPKI/RTR cache server and prints prefix, origin AS, router key and ASPA updates. \fBrtrclient\fR can use plain tcp or ssh transport to connect to an RPKI/RTR cache server. The amount is not limited and different transport types can be mixed arbitrarily. .LP @@ -55,6 +55,10 @@ Print information about router key updates .RS 4 Print information about prefix and origin AS updates .RE +\fB-a\fR +.RS 4 +Print information about ASPA updates +.RE \fB-s\fR .RS 4 Print information about connection status updates @@ -96,11 +100,11 @@ rtrclient tcp -k rpki.example.com 323 .RE .fi .PP -Print prefix and router key updates from a ssh based server +Print prefix, router key and ASPA updates from a ssh based server .PP .nf .RS -rtrclient ssh -k -p rpki.example.com 22 rtr-ssh ~/.ssh/id_rsa ~/.ssh/known_hosts +rtrclient ssh -k -p -a rpki.example.com 22 rtr-ssh ~/.ssh/id_rsa ~/.ssh/known_hosts .RE .fi .PP diff --git a/tools/rtrclient.c b/tools/rtrclient.c index 6282fac7..a9b88180 100644 --- a/tools/rtrclient.c +++ b/tools/rtrclient.c @@ -60,6 +60,7 @@ struct socket_config { enum socket_type type; bool print_pfx_updates; bool print_spki_updates; + bool print_aspa_updates; char *bindaddr; char *host; char *port; @@ -76,10 +77,12 @@ struct socket_config { /* activate update callback */ bool activate_pfx_update_cb = false; bool activate_spki_update_cb = false; +bool activate_aspa_update_cb = false; /* print all updates regardless of socket configuration */ bool print_all_pfx_updates = false; bool print_all_spki_updates = false; +bool print_all_aspa_updates = false; bool print_status_updates = false; @@ -426,6 +429,7 @@ static void print_usage(char **argv) printf("-k Print information about SPKI updates.\n"); printf("-p Print information about PFX updates.\n"); + printf("-a Print information about ASPA updates.\n"); printf("-s Print information about connection status updates.\n\n"); printf("-e export pfx table and exit\n"); @@ -498,12 +502,7 @@ static void update_spki(struct spki_table *s __attribute__((unused)), const stru pthread_mutex_lock(&stdout_mutex); - char c; - - if (added) - c = '+'; - else - c = '-'; + char c = added ? '+' : '-'; printf("%c ", c); printf("HOST: %s:%s\n", config->host, config->port); @@ -536,13 +535,55 @@ static void update_spki(struct spki_table *s __attribute__((unused)), const stru pthread_mutex_unlock(&stdout_mutex); } +static void update_aspa(struct aspa_table *s __attribute__((unused)), const struct aspa_record record, + const struct rtr_socket *rtr_sockt, const enum aspa_operation_type operation_type) +{ + const struct socket_config *config = (const struct socket_config *)rtr_sockt; + + if (!print_all_aspa_updates && !config->print_aspa_updates) + return; + + pthread_mutex_lock(&stdout_mutex); + + printf("HOST: %s:%s\n", config->host, config->port); + + char c; + + switch (operation_type) { + case ASPA_ADD: + c = '+'; + break; + case ASPA_REMOVE: + c = '-'; + break; + default: + c = '?'; + break; + } + + printf("%c ASPA %u => [ ", c, record.customer_asn); + + size_t i; + size_t count = record.provider_count; + + for (i = 0; i < count; i++) { + printf("%u", record.provider_asns[i]); + if (i < count - 1) + printf(", "); + } + + printf(" ]\n"); + pthread_mutex_unlock(&stdout_mutex); + free(record.provider_asns); +} + static void parse_global_opts(int argc, char **argv) { int opt; bool print_template = false; - while ((opt = getopt(argc, argv, "+kphelo:t:s")) != -1) { + while ((opt = getopt(argc, argv, "+kpahelo:t:s")) != -1) { switch (opt) { case 'k': activate_spki_update_cb = true; @@ -554,6 +595,11 @@ static void parse_global_opts(int argc, char **argv) print_all_pfx_updates = true; break; + case 'a': + activate_aspa_update_cb = true; + print_all_aspa_updates = true; + break; + case 'e': export_pfx = true; break; @@ -596,7 +642,7 @@ static void parse_socket_opts(int argc, char **argv, struct socket_config *confi { int opt; - while ((opt = getopt(argc, argv, "+kphwrb:")) != -1) { + while ((opt = getopt(argc, argv, "+kpahwrb:")) != -1) { switch (opt) { case 'k': activate_spki_update_cb = true; @@ -608,6 +654,11 @@ static void parse_socket_opts(int argc, char **argv, struct socket_config *confi config->print_pfx_updates = true; break; + case 'a': + activate_aspa_update_cb = true; + config->print_aspa_updates = true; + break; + case 'b': config->bindaddr = optarg; break; @@ -905,8 +956,10 @@ int main(int argc, char **argv) spki_update_fp spki_update_fp = activate_spki_update_cb ? update_spki : NULL; pfx_update_fp pfx_update_fp = activate_pfx_update_cb ? update_cb : NULL; + aspa_update_fp aspa_update_fp = activate_aspa_update_cb ? update_aspa : NULL; - int ret = rtr_mgr_init(&conf, groups, 1, 30, 600, 600, pfx_update_fp, spki_update_fp, status_fp, NULL); + int ret = rtr_mgr_init(&conf, groups, 1, 30, 600, 600, pfx_update_fp, spki_update_fp, aspa_update_fp, status_fp, + NULL); if (ret == RTR_ERROR) fprintf(stderr, "Error in rtr_mgr_init!\n"); diff --git a/tools/templates.h b/tools/templates.h new file mode 100644 index 00000000..4b03ad87 --- /dev/null +++ b/tools/templates.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/ + */ + + #include + + +struct pfx_output_template { + const char *name; + const char *template; +}; + + +const struct pfx_output_template templates[] = { +{ .name = "default", .template = "\x7b\x7b\x23\x72\x6f\x61\x73\x7d\x7d\x0a\x7b\x7b\x70\x72\x65\x66\x69\x78\x7d\x7d\x2f\x7b\x7b\x6c\x65\x6e\x67\x74\x68\x7d\x7d\x2d\x7b\x7b\x6d\x61\x78\x6c\x65\x6e\x7d\x7d\x20\x41\x53\x20\x7b\x7b\x6f\x72\x69\x67\x69\x6e\x7d\x7d\x0a\x7b\x7b\x2f\x72\x6f\x61\x73\x7d\x7d\x0a "}, +{ .name = "csv", .template = "\x7b\x7b\x23\x72\x6f\x61\x73\x7d\x7d\x0a\x7b\x7b\x70\x72\x65\x66\x69\x78\x7d\x7d\x2c\x20\x7b\x7b\x6c\x65\x6e\x67\x74\x68\x7d\x7d\x2c\x20\x7b\x7b\x6d\x61\x78\x6c\x65\x6e\x7d\x7d\x2c\x20\x7b\x7b\x6f\x72\x69\x67\x69\x6e\x7d\x7d\x0a\x7b\x7b\x2f\x72\x6f\x61\x73\x7d\x7d\x0a "}, +{ .name = "csvwithheader", .template = "\x70\x72\x65\x66\x69\x78\x2c\x20\x6d\x69\x6e\x6c\x65\x6e\x2c\x20\x6d\x61\x78\x6c\x65\x6e\x2c\x20\x61\x73\x6e\x0a\x7b\x7b\x23\x72\x6f\x61\x73\x7d\x7d\x0a\x7b\x7b\x70\x72\x65\x66\x69\x78\x7d\x7d\x2c\x20\x7b\x7b\x6c\x65\x6e\x67\x74\x68\x7d\x7d\x2c\x20\x7b\x7b\x6d\x61\x78\x6c\x65\x6e\x7d\x7d\x2c\x20\x7b\x7b\x6f\x72\x69\x67\x69\x6e\x7d\x7d\x0a\x7b\x7b\x2f\x72\x6f\x61\x73\x7d\x7d\x0a "}, +{ .name = "json", .template = "\x5b\x0a\x7b\x7b\x23\x72\x6f\x61\x73\x7d\x7d\x0a\x09\x7b\x0a\x09\x09\x22\x70\x72\x65\x66\x69\x78\x22\x3a\x20\x22\x7b\x7b\x70\x72\x65\x66\x69\x78\x7d\x7d\x22\x2c\x0a\x09\x09\x22\x6c\x65\x6e\x67\x74\x68\x22\x3a\x20\x22\x7b\x7b\x6c\x65\x6e\x67\x74\x68\x7d\x7d\x22\x2c\x0a\x09\x09\x22\x6d\x61\x78\x6c\x65\x6e\x22\x3a\x20\x22\x7b\x7b\x6d\x61\x78\x6c\x65\x6e\x7d\x7d\x22\x2c\x0a\x09\x09\x22\x6f\x72\x69\x67\x69\x6e\x22\x3a\x20\x22\x7b\x7b\x6f\x72\x69\x67\x69\x6e\x7d\x7d\x22\x0a\x09\x7d\x7b\x7b\x5e\x6c\x61\x73\x74\x7d\x7d\x2c\x7b\x7b\x2f\x6c\x61\x73\x74\x7d\x7d\x0a\x7b\x7b\x2f\x72\x6f\x61\x73\x7d\x7d\x0a\x5d\x0a "}, +{NULL, NULL} +}; From 727611a08bdd9c3c2e03499f3a02a0751503f615 Mon Sep 17 00:00:00 2001 From: revol-xut Date: Wed, 13 Mar 2024 22:57:27 +0100 Subject: [PATCH 22/55] rtrlib: incorperating suggestions from Fabian Holler - removing self-explanatory comments - renaming include guards of ASPA_ARRAY - removing double negation - moving pthread_unlock --- CMakeLists.txt | 4 +-- rtrlib/aspa/aspa.c | 6 +++++ rtrlib/aspa/aspa_array/aspa_array.c | 38 ++++++++++++++++++++++++----- rtrlib/aspa/aspa_array/aspa_array.h | 16 +++++++++--- rtrlib/aspa/aspa_verification.c | 6 ++--- rtrlib/rtr/packets.c | 13 +++++----- 6 files changed, 61 insertions(+), 22 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c23fd321..55090066 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,7 @@ +cmake_minimum_required(VERSION 2.6) project(rtrlib C) set(PROJECT_DESCRIPTION "Lightweight C library that implements the RPKI/RTR protocol and prefix origin validation.") - -cmake_minimum_required(VERSION 2.6) - set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/modules) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -std=gnu99 -fstack-protector-all") if(CMAKE_BUILD_TYPE STREQUAL Debug) diff --git a/rtrlib/aspa/aspa.c b/rtrlib/aspa/aspa.c index 23c9f25a..28b77061 100644 --- a/rtrlib/aspa/aspa.c +++ b/rtrlib/aspa/aspa.c @@ -288,6 +288,9 @@ static enum aspa_status aspa_table_update_compute_internal(struct rtr_socket *rt size_t existing_i = 0; + // preemptively allocate space for the arrays + aspa_array_reserve(new_array, array->size + count); + for (size_t i = 0; i < count; i++) { struct aspa_update_operation *current = &operations[i]; struct aspa_update_operation *next = (i < count - 1) ? &(operations[i + 1]) : NULL; @@ -650,6 +653,9 @@ enum aspa_status aspa_table_update_in_place(struct aspa_table *aspa_table, struc struct aspa_array *array = (*node)->aspa_array; size_t existing_i = 0; + // preemptively allocating enough space + aspa_array_reserve(array, count + array->size); + for (size_t i = 0; i < count; i++) { struct aspa_update_operation *current = &operations[i]; struct aspa_update_operation *next = (i < count - 1) ? &(operations[i + 1]) : NULL; diff --git a/rtrlib/aspa/aspa_array/aspa_array.c b/rtrlib/aspa/aspa_array/aspa_array.c index 75148106..21b80326 100644 --- a/rtrlib/aspa/aspa_array/aspa_array.c +++ b/rtrlib/aspa/aspa_array/aspa_array.c @@ -22,14 +22,11 @@ enum aspa_status aspa_array_create(struct aspa_array **array_ptr) // allocation the chunk of memory of the provider as numbers struct aspa_record *data_field = lrtr_malloc(sizeof(struct aspa_record) * default_initial_size); - // malloc failed so returning an error if (!data_field) return ASPA_ERROR; - // allocating the aspa_record itself struct aspa_array *array = lrtr_malloc(sizeof(struct aspa_array)); - // malloc for aspa_record failed hence we return an error if (!array) { lrtr_free(data_field); return ASPA_ERROR; @@ -48,7 +45,6 @@ enum aspa_status aspa_array_create(struct aspa_array **array_ptr) void aspa_array_free(struct aspa_array *array, bool free_provider_arrays) { - // if the array is null just return if (!array) return; @@ -62,11 +58,9 @@ void aspa_array_free(struct aspa_array *array, bool free_provider_arrays) } } - // freeing the data lrtr_free(array->data); } - // freeing the array itself lrtr_free(array); } @@ -228,3 +222,35 @@ struct aspa_record *aspa_array_search(struct aspa_array *array, uint32_t custome // element not found return NULL; } + + +enum aspa_status aspa_array_reserve(struct aspa_array *array, size_t size) { + // the given array is null + if (array == NULL) { + return ASPA_ERROR; + } + + // we already have enough space allocated + if (array->capacity >= size) { + return ASPA_SUCCESS; + } + + struct aspa_record* data = malloc(sizeof(struct aspa_record) * size); + + // malloc failed + if (data == NULL) { + return ASPA_ERROR; + } + + // coping the data from the old array into the new one + if (memcpy(data, array->data, array->size * sizeof(struct aspa_record)) == NULL) { + return ASPA_ERROR; + } + + // updating the array + free(array->data); + array->capacity = size; + array->data = data; + + return ASPA_SUCCESS; +} diff --git a/rtrlib/aspa/aspa_array/aspa_array.h b/rtrlib/aspa/aspa_array/aspa_array.h index 9f0858f5..7a15f656 100644 --- a/rtrlib/aspa/aspa_array/aspa_array.h +++ b/rtrlib/aspa/aspa_array/aspa_array.h @@ -7,8 +7,8 @@ * Website: http://rtrlib.realmv6.org/ */ -#ifndef RTR_ASPA_DYN_ARRAY_H -#define RTR_ASPA_DYN_ARRAY_H +#ifndef RTR_ASPA_ARRAY_H +#define RTR_ASPA_ARRAY_H #include "../aspa.h" @@ -103,4 +103,14 @@ struct aspa_record *aspa_array_get_record(struct aspa_array *array, size_t index */ struct aspa_record *aspa_array_search(struct aspa_array *array, uint32_t customer_asn); -#endif +/** + * @brief Reserves some space in the array + * + * @param array The array to remove the record from. + * @param size the number of object that should definetly fit into the array + * @return @c ASPA_SUCCESS if the operation succeeds @c ASPA_ERROR otherwise. + */ +enum aspa_status aspa_array_reserve(struct aspa_array *array, size_t size); + + +#endif // RTR_ASPA_ARRAY_H \ No newline at end of file diff --git a/rtrlib/aspa/aspa_verification.c b/rtrlib/aspa/aspa_verification.c index c4e8bd49..c141d1d7 100644 --- a/rtrlib/aspa/aspa_verification.c +++ b/rtrlib/aspa/aspa_verification.c @@ -46,7 +46,7 @@ enum aspa_hop_result aspa_check_hop(struct aspa_table *aspa_table, uint32_t cust { bool customer_found = false; - for (struct aspa_store_node *node = aspa_table->store; !!node; node = node->next) { + for (struct aspa_store_node *node = aspa_table->store; node != NULL; node = node->next) { struct aspa_record *aspa_record = aspa_array_search(node->aspa_array, customer_asn); if (!aspa_record) @@ -132,13 +132,13 @@ static enum aspa_verification_result aspa_verify_as_path_upstream(struct aspa_ta } } + pthread_rwlock_unlock(&aspa_table->lock); + // If nP+ occurs upstream customer-provider chain, return INVALID. if (found_nP_from_right) { - pthread_rwlock_unlock(&aspa_table->lock); return ASPA_AS_PATH_INVALID; } - pthread_rwlock_unlock(&aspa_table->lock); return ASPA_AS_PATH_UNKNOWN; } diff --git a/rtrlib/rtr/packets.c b/rtrlib/rtr/packets.c index f2fa7261..0ec69430 100644 --- a/rtrlib/rtr/packets.c +++ b/rtrlib/rtr/packets.c @@ -402,7 +402,7 @@ static bool rtr_pdu_check_size(const struct pdu_header *pdu) // ASN is 4 bytes each expected_size += asn_count * sizeof(aspa_pdu->provider_asns[0]); if (aspa_pdu->len != expected_size) { - RTR_DBG1("PDU is too small to contain valid ASPA PDU!"); + RTR_DBG1("The PDU is malformed, because the specified size doesn't match the real PDU size."); break; } @@ -1116,13 +1116,12 @@ static int rtr_undo_update_aspa_table(struct rtr_socket *rtr_socket, struct aspa if (res == ASPA_SUCCESS) { return RTR_SUCCESS; - } else { - // Undo failed, cannot recover, remove all records associated with the socket instead - RTR_DBG1( - "Couldn't undo all update operations from failed data synchronisation: Purging all ASPA records"); - aspa_table_src_remove(aspa_table, rtr_socket, true); - return RTR_ERROR; } + // Undo failed, cannot recover, remove all records associated with the socket instead + RTR_DBG1( + "Couldn't undo all update operations from failed data synchronisation: Purging all ASPA records"); + aspa_table_src_remove(aspa_table, rtr_socket, true); + return RTR_ERROR; } static int rtr_update_aspa_table(struct rtr_socket *rtr_socket, struct aspa_table *aspa_table, From 8ebdfed0dec2615ddbb837d8455ad71afd944eba Mon Sep 17 00:00:00 2001 From: tanneberger Date: Mon, 20 May 2024 16:06:34 +0200 Subject: [PATCH 23/55] rtrlib: reworking user interface adding rtr_mgr_setup_sockets function - adjusted tests and tools - added function rtr_mgr_setup_sockets with functionality that previously resided in rtr_mgr_init --- rtrlib/rtr_mgr.c | 139 ++++++++++++++++---------- rtrlib/rtr_mgr.h | 57 +++++++++-- tests/test_dynamic_groups.c | 6 +- tests/test_live_fetching.c | 15 ++- tests/test_live_validation.c | 22 +++- tests/unittests/test_packets_static.c | 4 +- tools/rpki-rov.c | 16 ++- tools/rtrclient.c | 27 +++-- 8 files changed, 209 insertions(+), 77 deletions(-) diff --git a/rtrlib/rtr_mgr.c b/rtrlib/rtr_mgr.c index 908ec1e4..50b593cd 100644 --- a/rtrlib/rtr_mgr.c +++ b/rtrlib/rtr_mgr.c @@ -295,19 +295,11 @@ int rtr_mgr_config_cmp_tommy(const void *a, const void *b) // TODO: Additional arguments trailing? 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 aspa_update_fp aspa_update_fp, const rtr_mgr_status_fp status_fp, - void *status_fp_data) + const unsigned int groups_len, 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 aspa_table *aspa_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; @@ -345,48 +337,55 @@ RTRLIB_EXPORT int rtr_mgr_init(struct rtr_mgr_config **config_out, struct rtr_mg 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->status_fp_data = status_fp_data; + config->status_fp = status_fp; + return RTR_SUCCESS; - aspa_table = lrtr_malloc(sizeof(*aspa_table)); - if (!aspa_table) - goto err; - aspa_table_init(aspa_table, aspa_update_fp); +err: + lrtr_free(config->groups); + lrtr_free(config); + config = NULL; + *config_out = NULL; - config->pfx_table = pfxt; - config->spki_table = spki_table; - config->aspa_table = aspa_table; + return err_code; +} +RTRLIB_EXPORT int rtr_mgr_setup_sockets(struct rtr_mgr_config *config, 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 + ) { + enum rtr_interval_mode iv_mode = RTR_INTERVAL_MODE_DEFAULT_MIN_MAX; + struct rtr_mgr_group_node *group_node = NULL; + struct rtr_mgr_group *cg = NULL; /* 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; + if (!config->groups) { + return RTR_ERROR; + } + 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; + if (!cg) { + return RTR_ERROR; + } 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; + if (rtr_mgr_init_sockets(cg, config, refresh_interval, expire_interval, retry_interval, iv_mode)) { + lrtr_free(cg); + return RTR_ERROR; + } group_node = lrtr_malloc(sizeof(struct rtr_mgr_group_node)); - if (!group_node) - goto err; + if (!group_node) { + lrtr_free(cg); + return RTR_ERROR; + } group_node->group = cg; tommy_list_insert_tail(&config->groups->list, &group_node->node, group_node); @@ -396,29 +395,61 @@ RTRLIB_EXPORT int rtr_mgr_init(struct rtr_mgr_config **config_out, struct rtr_mg */ 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); - if (aspa_table) - aspa_table_free(aspa_table, false); - lrtr_free(pfxt); - lrtr_free(spki_table); - lrtr_free(aspa_table); - - lrtr_free(cg); +RTRLIB_EXPORT int rtr_mgr_add_roa_support(struct rtr_mgr_config *config, const pfx_update_fp pfx_update_fp) +{ + if (config == NULL) { + return RTR_ERROR; + } - lrtr_free(config->groups); - lrtr_free(config); - config = NULL; - *config_out = NULL; + /* Init prefix table that we need to pass to the sockets */ + struct pfx_table *pfxt = lrtr_malloc(sizeof(*pfxt)); + if (!pfxt) { + return RTR_ERROR; + } - return err_code; + pfx_table_init(pfxt, pfx_update_fp); + config->pfx_table = pfxt; + + return RTR_SUCCESS; +} + +RTRLIB_EXPORT int rtr_mgr_add_aspa_support(struct rtr_mgr_config *config, const aspa_update_fp aspa_update_fp) +{ + if (config == NULL) { + return RTR_ERROR; + } + + /* Init aspa table that we need to pass to the sockets */ + struct aspa_table *aspa_table = lrtr_malloc(sizeof(*aspa_table)); + if (!aspa_table) { + return RTR_ERROR; + } + + aspa_table_init(aspa_table, aspa_update_fp); + config->aspa_table = aspa_table; + + return RTR_SUCCESS; +} + +RTRLIB_EXPORT int rtr_mgr_add_spki_support(struct rtr_mgr_config *config, const spki_update_fp spki_update_fp) +{ + if (config == NULL) { + return RTR_ERROR; + } + + /* Init spki table that we need to pass to the sockets */ + struct spki_table *spki_table = lrtr_malloc(sizeof(*spki_table)); + if (!spki_table) { + return ASPA_ERROR; + } + + spki_table_init(spki_table, spki_update_fp); + config->spki_table = spki_table; + + return ASPA_SUCCESS; } RTRLIB_EXPORT struct rtr_mgr_group *rtr_mgr_get_first_group(struct rtr_mgr_config *config) diff --git a/rtrlib/rtr_mgr.h b/rtrlib/rtr_mgr.h index c1d45a44..82bc2499 100644 --- a/rtrlib/rtr_mgr.h +++ b/rtrlib/rtr_mgr.h @@ -116,12 +116,6 @@ struct rtr_mgr_config { * 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] aspa_update_fp Pointer to aspa_update_fp callback, that is - executed for every added and removed aspa_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 @@ -133,9 +127,26 @@ struct rtr_mgr_config { * @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 aspa_update_fp aspa_update_fp, const rtr_mgr_status_fp status_fp, void *status_fp_data); + const rtr_mgr_status_fp status_fp, void *status_fp_data); + + + +int rtr_mgr_setup_sockets(struct rtr_mgr_config *config, 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 + ); + +/** + * @brief Sets up ROA support + * @param[in] config Pointer to the rtr_mgr_config where ROA support should be enabled. + * @param[in] update_fp Pointer to pfx_update_fp callback, that is executed for + every added and removed pfx_record. + * @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_add_roa_support(struct rtr_mgr_config *config, const pfx_update_fp pfx_update_fp); + /** * @brief Adds a new rtr_mgr_group to the linked list of a initialized config. @@ -266,6 +277,32 @@ 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); + + + +/** + * @brief Sets up ASPA support + * @param[in] config Pointer to the rtr_mgr_config where ROA support should be enabled. + * @param[in] aspa_update_fp Pointer to aspa_update_fp callback, that is + executed for every added and removed aspa_record. + * @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_add_aspa_support(struct rtr_mgr_config *config, const aspa_update_fp aspa_update_fp); + + +/** + * @brief Sets up BGPSEC support + * @param[in] config Pointer to the rtr_mgr_config where ROA support should be enabled. + * @param[in] spki_update_fp Pointer to spki_update_fp callback, that is + executed for every added and removed spki_record. + * @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_add_spki_support(struct rtr_mgr_config *config, const spki_update_fp spki_update_fp); + /* @} */ /** @@ -275,6 +312,7 @@ int rtr_mgr_for_each_group(struct rtr_mgr_config *config, void (*fp)(const struc * @{ */ #ifdef RTRLIB_BGPSEC_ENABLED + /** * @brief Validation function for AS path validation. * @param[in] data Data required for AS path validation. See @ref rtr_bgpsec. @@ -421,5 +459,6 @@ void rtr_mgr_bgpsec_nlri_free(struct rtr_bgpsec_nlri *nlri); void rtr_mgr_bgpsec_add_spki_record(struct rtr_mgr_config *config, struct spki_record *record); #endif + #endif /** @} */ diff --git a/tests/test_dynamic_groups.c b/tests/test_dynamic_groups.c index 984574f7..e30ef977 100644 --- a/tests/test_dynamic_groups.c +++ b/tests/test_dynamic_groups.c @@ -61,7 +61,11 @@ int main(void) struct rtr_mgr_config *conf; - rtr_mgr_init(&conf, groups, 1, 30, 600, 600, NULL, NULL, NULL, &connection_status_callback, NULL); + rtr_mgr_init(&conf, groups, 1, &connection_status_callback, NULL); + rtr_mgr_add_roa_support(conf, NULL); + rtr_mgr_add_aspa_support(conf, NULL); + rtr_mgr_add_spki_support(conf, NULL); + rtr_mgr_setup_sockets(conf, groups, 1, 50, 600, 600); //start the connection manager rtr_mgr_start(conf); diff --git a/tests/test_live_fetching.c b/tests/test_live_fetching.c index 5a622029..4ff65134 100644 --- a/tests/test_live_fetching.c +++ b/tests/test_live_fetching.c @@ -77,9 +77,22 @@ int main(void) struct rtr_mgr_config *conf; - if (rtr_mgr_init(&conf, groups, 1, 30, 600, 600, NULL, NULL, NULL, &connection_status_callback, NULL) < 0) + if (rtr_mgr_init(&conf, groups, 1, &connection_status_callback, NULL) < 0) return EXIT_FAILURE; + if (rtr_mgr_add_roa_support(conf, NULL) == RTR_ERROR) { + fprintf(stderr, "Failed initializing ROA support\n"); + } + + if (rtr_mgr_add_aspa_support(conf, NULL) == RTR_ERROR) { + fprintf(stderr, "Failed initializing ASPA support\n"); + } + + if (rtr_mgr_add_spki_support(conf, NULL) == RTR_ERROR) { + fprintf(stderr, "Failed initializing BGPSEC support\n"); + } + + rtr_mgr_setup_sockets(conf, groups, 1, 50, 600, 600); rtr_mgr_start(conf); int sleep_counter = 0; /* wait for connection, or timeout and exit eventually */ diff --git a/tests/test_live_validation.c b/tests/test_live_validation.c index 2f4a2800..2bb208e0 100644 --- a/tests/test_live_validation.c +++ b/tests/test_live_validation.c @@ -32,7 +32,7 @@ const struct test_validity_query queries[] = {{"93.175.146.0", 24, 12654, BGP_PF {"2001:7fb:fd03::", 48, 12654, BGP_PFXV_STATE_INVALID}, {"84.205.83.0", 24, 12654, BGP_PFXV_STATE_NOT_FOUND}, {"2001:7fb:ff03::", 48, 12654, BGP_PFXV_STATE_NOT_FOUND}, - {NULL, 0, 0, 0} }; + {NULL, 0, 0, 0}}; const int connection_timeout = 80; enum rtr_mgr_status connection_status = -1; @@ -85,9 +85,27 @@ int main(void) struct rtr_mgr_config *conf; - if (rtr_mgr_init(&conf, groups, 1, 30, 600, 600, NULL, NULL, NULL, &connection_status_callback, NULL) < 0) + if (rtr_mgr_init(&conf, groups, 1, &connection_status_callback, NULL) < 0) return EXIT_FAILURE; + + if (rtr_mgr_add_roa_support(conf, NULL) == RTR_ERROR) { + fprintf(stderr, "Failed initializing ROA support\n"); + return EXIT_FAILURE; + } + + if (rtr_mgr_add_aspa_support(conf, NULL) == RTR_ERROR) { + fprintf(stderr, "Failed initializing ASPA support\n"); + return EXIT_FAILURE; + } + + if (rtr_mgr_add_spki_support(conf, NULL) == RTR_ERROR) { + fprintf(stderr, "Failed initializing BGPSEC support\n"); + return EXIT_FAILURE; + } + + rtr_mgr_setup_sockets(conf, groups, 1, 50, 600, 600); + rtr_mgr_start(conf); int sleep_counter = 0; /* wait for connection, or timeout and exit eventually */ diff --git a/tests/unittests/test_packets_static.c b/tests/unittests/test_packets_static.c index 88eac9c5..be4f5409 100644 --- a/tests/unittests/test_packets_static.c +++ b/tests/unittests/test_packets_static.c @@ -501,8 +501,8 @@ int main(void) const struct CMUnitTest tests[] = { cmocka_unit_test(test_set_last_update), cmocka_unit_test(test_rtr_get_pdu_type), cmocka_unit_test(test_pdu_to_network_byte_order), cmocka_unit_test(test_pdu_to_host_byte_order), - cmocka_unit_test(test_rtr_pdu_check_size), cmocka_unit_test(test_rtr_send_error_pdu), - cmocka_unit_test(test_rtr_pdu_check_interval), cmocka_unit_test(test_set_interval_option), + cmocka_unit_test(test_rtr_pdu_check_size), cmocka_unit_test(test_rtr_send_error_pdu), + cmocka_unit_test(test_rtr_pdu_check_interval), cmocka_unit_test(test_set_interval_option), }; return cmocka_run_group_tests(tests, NULL, NULL); } diff --git a/tools/rpki-rov.c b/tools/rpki-rov.c index f24bcad4..e13d4f77 100644 --- a/tools/rpki-rov.c +++ b/tools/rpki-rov.c @@ -82,9 +82,23 @@ int main(int argc, char *argv[]) groups[0].sockets[0] = &rtr_tcp; groups[0].preference = 1; - if (rtr_mgr_init(&conf, groups, 1, 30, 600, 600, NULL, NULL, NULL, &connection_status_callback, NULL) < 0) + if (rtr_mgr_init(&conf, groups, 1, &connection_status_callback, NULL) < 0) return EXIT_FAILURE; + if (rtr_mgr_add_roa_support(conf, NULL) == RTR_ERROR) { + fprintf(stderr, "Failed initializing ROA support\n"); + } + + if (rtr_mgr_add_aspa_support(conf, NULL) == RTR_ERROR) { + fprintf(stderr, "Failed initializing ASPA support\n"); + } + + if (rtr_mgr_add_spki_support(conf, NULL) == RTR_ERROR) { + fprintf(stderr, "Failed initializing BGPSEC support\n"); + } + + rtr_mgr_setup_sockets(conf, groups, 1, 50, 600, 600); + rtr_mgr_start(conf); char input[256]; diff --git a/tools/rtrclient.c b/tools/rtrclient.c index a9b88180..ff0d0fa6 100644 --- a/tools/rtrclient.c +++ b/tools/rtrclient.c @@ -168,7 +168,7 @@ static const char *get_template(const char *name) fclose(template_file); return template; -read_error: + read_error: fclose(template_file); print_error_exit("Could not read template"); } @@ -261,25 +261,25 @@ static bool is_utf8(const char *str) if ((str[i] & UTF8_ONE_BYTE_MASK) == UTF8_ONE_BYTE_PREFIX) { i += 1; - // check if current byte is the start of a two byte utf8 char and validate subsequent bytes + // check if current byte is the start of a two byte utf8 char and validate subsequent bytes } else if ((str[i] & UTF8_TWO_BYTE_MASK) == UTF8_TWO_BYTE_PREFIX && i + 1 < len && (str[i + 1] & UTF8_SUBSEQUENT_BYTE_MASK) == UTF8_SUBSEQUENT_BYTE_PREFIX) { i += 2; - // check if current byte is the start of a three byte utf8 char and validate subsequent bytes + // check if current byte is the start of a three byte utf8 char and validate subsequent bytes } else if ((str[i] & UTF8_THREE_BYTE_MASK) == UTF8_THREE_BYTE_PREFIX && i + 2 < len && (str[i + 1] & UTF8_SUBSEQUENT_BYTE_MASK) == UTF8_SUBSEQUENT_BYTE_PREFIX && (str[i + 2] & UTF8_SUBSEQUENT_BYTE_MASK) == UTF8_SUBSEQUENT_BYTE_PREFIX) { i += 3; - // check if current byte is the start of a four byte utf8 char and validate subsequent bytes + // check if current byte is the start of a four byte utf8 char and validate subsequent bytes } else if ((str[i] & UTF8_FOUR_BYTE_MASK) == UTF8_FOUR_BYTE_PREFIX && i + 3 < len && (str[i + 1] & UTF8_SUBSEQUENT_BYTE_MASK) == UTF8_SUBSEQUENT_BYTE_PREFIX && (str[i + 2] & UTF8_SUBSEQUENT_BYTE_MASK) == UTF8_SUBSEQUENT_BYTE_PREFIX && (str[i + 3] & UTF8_SUBSEQUENT_BYTE_MASK) == UTF8_SUBSEQUENT_BYTE_PREFIX) { i += 4; - // if none of the conditions matched. The string contains at least one byte that is not valid utf8 + // if none of the conditions matched. The string contains at least one byte that is not valid utf8 } else { return false; } @@ -958,14 +958,27 @@ int main(int argc, char **argv) pfx_update_fp pfx_update_fp = activate_pfx_update_cb ? update_cb : NULL; aspa_update_fp aspa_update_fp = activate_aspa_update_cb ? update_aspa : NULL; - int ret = rtr_mgr_init(&conf, groups, 1, 30, 600, 600, pfx_update_fp, spki_update_fp, aspa_update_fp, status_fp, - NULL); + int ret = rtr_mgr_init(&conf, groups, 1, status_fp, NULL); if (ret == RTR_ERROR) fprintf(stderr, "Error in rtr_mgr_init!\n"); else if (ret == RTR_INVALID_PARAM) fprintf(stderr, "Invalid params passed to rtr_mgr_init\n"); + if (rtr_mgr_add_roa_support(conf, pfx_update_fp) == RTR_ERROR) { + fprintf(stderr, "Failed initializing ROA support\n"); + } + + if (rtr_mgr_add_aspa_support(conf, aspa_update_fp) == RTR_ERROR) { + fprintf(stderr, "Failed initializing ASPA support\n"); + } + + if (rtr_mgr_add_spki_support(conf, spki_update_fp) == RTR_ERROR) { + fprintf(stderr, "Failed initializing BGPSEC support\n"); + } + + rtr_mgr_setup_sockets(conf, groups, 1, 50, 600, 600); + if (!conf) return EXIT_FAILURE; From b9c69dafebd261fa8b6a3136f659c658594a9dba Mon Sep 17 00:00:00 2001 From: tanneberger Date: Mon, 20 May 2024 16:20:12 +0200 Subject: [PATCH 24/55] rtrlib: apply proper formatting --- rtrlib/aspa/aspa_array/aspa_array.c | 6 +++--- rtrlib/aspa/aspa_array/aspa_array.h | 1 - rtrlib/rtr/packets.c | 5 ++--- rtrlib/rtr_mgr.c | 11 ++++------- rtrlib/rtr_mgr.h | 12 ++---------- tests/test_live_fetching.c | 2 +- tests/test_live_validation.c | 1 - tools/rpki-rov.c | 2 +- tools/templates.h | 23 ++++++++++++++--------- 9 files changed, 27 insertions(+), 36 deletions(-) diff --git a/rtrlib/aspa/aspa_array/aspa_array.c b/rtrlib/aspa/aspa_array/aspa_array.c index 21b80326..ea6259a0 100644 --- a/rtrlib/aspa/aspa_array/aspa_array.c +++ b/rtrlib/aspa/aspa_array/aspa_array.c @@ -223,8 +223,8 @@ struct aspa_record *aspa_array_search(struct aspa_array *array, uint32_t custome return NULL; } - -enum aspa_status aspa_array_reserve(struct aspa_array *array, size_t size) { +enum aspa_status aspa_array_reserve(struct aspa_array *array, size_t size) +{ // the given array is null if (array == NULL) { return ASPA_ERROR; @@ -235,7 +235,7 @@ enum aspa_status aspa_array_reserve(struct aspa_array *array, size_t size) { return ASPA_SUCCESS; } - struct aspa_record* data = malloc(sizeof(struct aspa_record) * size); + struct aspa_record *data = malloc(sizeof(struct aspa_record) * size); // malloc failed if (data == NULL) { diff --git a/rtrlib/aspa/aspa_array/aspa_array.h b/rtrlib/aspa/aspa_array/aspa_array.h index 7a15f656..a9f22c6c 100644 --- a/rtrlib/aspa/aspa_array/aspa_array.h +++ b/rtrlib/aspa/aspa_array/aspa_array.h @@ -112,5 +112,4 @@ struct aspa_record *aspa_array_search(struct aspa_array *array, uint32_t custome */ enum aspa_status aspa_array_reserve(struct aspa_array *array, size_t size); - #endif // RTR_ASPA_ARRAY_H \ No newline at end of file diff --git a/rtrlib/rtr/packets.c b/rtrlib/rtr/packets.c index 0ec69430..999a57ce 100644 --- a/rtrlib/rtr/packets.c +++ b/rtrlib/rtr/packets.c @@ -1118,8 +1118,7 @@ static int rtr_undo_update_aspa_table(struct rtr_socket *rtr_socket, struct aspa return RTR_SUCCESS; } // Undo failed, cannot recover, remove all records associated with the socket instead - RTR_DBG1( - "Couldn't undo all update operations from failed data synchronisation: Purging all ASPA records"); + RTR_DBG1("Couldn't undo all update operations from failed data synchronisation: Purging all ASPA records"); aspa_table_src_remove(aspa_table, rtr_socket, true); return RTR_ERROR; } @@ -1267,7 +1266,7 @@ static int rtr_sync_update_tables(struct rtr_socket *rtr_socket, struct pfx_tabl RTR_DBG1("spki data added"); if (rtr_compute_update_aspa_table(rtr_socket, aspa_table, aspa_pdus, aspa_pdu_count, &aspa_update) == - RTR_ERROR) { + RTR_ERROR) { RTR_DBG1("error while computing ASPA update"); proceed = false; diff --git a/rtrlib/rtr_mgr.c b/rtrlib/rtr_mgr.c index 50b593cd..55e2320d 100644 --- a/rtrlib/rtr_mgr.c +++ b/rtrlib/rtr_mgr.c @@ -298,9 +298,7 @@ RTRLIB_EXPORT int rtr_mgr_init(struct rtr_mgr_config **config_out, struct rtr_mg const unsigned int groups_len, 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 rtr_mgr_config *config = NULL; - struct rtr_mgr_group_node *group_node; uint8_t last_preference = UINT8_MAX; *config_out = NULL; @@ -350,10 +348,10 @@ RTRLIB_EXPORT int rtr_mgr_init(struct rtr_mgr_config **config_out, struct rtr_mg return err_code; } -RTRLIB_EXPORT int rtr_mgr_setup_sockets(struct rtr_mgr_config *config, 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 - ) { +RTRLIB_EXPORT int rtr_mgr_setup_sockets(struct rtr_mgr_config *config, 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) +{ enum rtr_interval_mode iv_mode = RTR_INTERVAL_MODE_DEFAULT_MIN_MAX; struct rtr_mgr_group_node *group_node = NULL; struct rtr_mgr_group *cg = NULL; @@ -367,7 +365,6 @@ RTRLIB_EXPORT int rtr_mgr_setup_sockets(struct rtr_mgr_config *config, struct rt config->groups->list = NULL; for (unsigned int i = 0; i < groups_len; i++) { - cg = lrtr_malloc(sizeof(struct rtr_mgr_group)); if (!cg) { return RTR_ERROR; diff --git a/rtrlib/rtr_mgr.h b/rtrlib/rtr_mgr.h index 82bc2499..e195a608 100644 --- a/rtrlib/rtr_mgr.h +++ b/rtrlib/rtr_mgr.h @@ -129,12 +129,9 @@ struct rtr_mgr_config { int rtr_mgr_init(struct rtr_mgr_config **config_out, struct rtr_mgr_group groups[], const unsigned int groups_len, const rtr_mgr_status_fp status_fp, void *status_fp_data); - - int rtr_mgr_setup_sockets(struct rtr_mgr_config *config, 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 unsigned int refresh_interval, const unsigned int expire_interval, + const unsigned int retry_interval); /** * @brief Sets up ROA support @@ -147,7 +144,6 @@ int rtr_mgr_setup_sockets(struct rtr_mgr_config *config, struct rtr_mgr_group gr */ int rtr_mgr_add_roa_support(struct rtr_mgr_config *config, const pfx_update_fp pfx_update_fp); - /** * @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 @@ -278,8 +274,6 @@ 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); - - /** * @brief Sets up ASPA support * @param[in] config Pointer to the rtr_mgr_config where ROA support should be enabled. @@ -291,7 +285,6 @@ int rtr_mgr_for_each_group(struct rtr_mgr_config *config, void (*fp)(const struc */ int rtr_mgr_add_aspa_support(struct rtr_mgr_config *config, const aspa_update_fp aspa_update_fp); - /** * @brief Sets up BGPSEC support * @param[in] config Pointer to the rtr_mgr_config where ROA support should be enabled. @@ -459,6 +452,5 @@ void rtr_mgr_bgpsec_nlri_free(struct rtr_bgpsec_nlri *nlri); void rtr_mgr_bgpsec_add_spki_record(struct rtr_mgr_config *config, struct spki_record *record); #endif - #endif /** @} */ diff --git a/tests/test_live_fetching.c b/tests/test_live_fetching.c index 4ff65134..e2019fee 100644 --- a/tests/test_live_fetching.c +++ b/tests/test_live_fetching.c @@ -77,7 +77,7 @@ int main(void) struct rtr_mgr_config *conf; - if (rtr_mgr_init(&conf, groups, 1, &connection_status_callback, NULL) < 0) + if (rtr_mgr_init(&conf, groups, 1, &connection_status_callback, NULL) < 0) return EXIT_FAILURE; if (rtr_mgr_add_roa_support(conf, NULL) == RTR_ERROR) { diff --git a/tests/test_live_validation.c b/tests/test_live_validation.c index 2bb208e0..aaf47c83 100644 --- a/tests/test_live_validation.c +++ b/tests/test_live_validation.c @@ -88,7 +88,6 @@ int main(void) if (rtr_mgr_init(&conf, groups, 1, &connection_status_callback, NULL) < 0) return EXIT_FAILURE; - if (rtr_mgr_add_roa_support(conf, NULL) == RTR_ERROR) { fprintf(stderr, "Failed initializing ROA support\n"); return EXIT_FAILURE; diff --git a/tools/rpki-rov.c b/tools/rpki-rov.c index e13d4f77..3503114c 100644 --- a/tools/rpki-rov.c +++ b/tools/rpki-rov.c @@ -82,7 +82,7 @@ int main(int argc, char *argv[]) groups[0].sockets[0] = &rtr_tcp; groups[0].preference = 1; - if (rtr_mgr_init(&conf, groups, 1, &connection_status_callback, NULL) < 0) + if (rtr_mgr_init(&conf, groups, 1, &connection_status_callback, NULL) < 0) return EXIT_FAILURE; if (rtr_mgr_add_roa_support(conf, NULL) == RTR_ERROR) { diff --git a/tools/templates.h b/tools/templates.h index 4b03ad87..901c1f14 100644 --- a/tools/templates.h +++ b/tools/templates.h @@ -7,19 +7,24 @@ * Website: http://rtrlib.realmv6.org/ */ - #include - +#include struct pfx_output_template { const char *name; const char *template; }; - const struct pfx_output_template templates[] = { -{ .name = "default", .template = "\x7b\x7b\x23\x72\x6f\x61\x73\x7d\x7d\x0a\x7b\x7b\x70\x72\x65\x66\x69\x78\x7d\x7d\x2f\x7b\x7b\x6c\x65\x6e\x67\x74\x68\x7d\x7d\x2d\x7b\x7b\x6d\x61\x78\x6c\x65\x6e\x7d\x7d\x20\x41\x53\x20\x7b\x7b\x6f\x72\x69\x67\x69\x6e\x7d\x7d\x0a\x7b\x7b\x2f\x72\x6f\x61\x73\x7d\x7d\x0a "}, -{ .name = "csv", .template = "\x7b\x7b\x23\x72\x6f\x61\x73\x7d\x7d\x0a\x7b\x7b\x70\x72\x65\x66\x69\x78\x7d\x7d\x2c\x20\x7b\x7b\x6c\x65\x6e\x67\x74\x68\x7d\x7d\x2c\x20\x7b\x7b\x6d\x61\x78\x6c\x65\x6e\x7d\x7d\x2c\x20\x7b\x7b\x6f\x72\x69\x67\x69\x6e\x7d\x7d\x0a\x7b\x7b\x2f\x72\x6f\x61\x73\x7d\x7d\x0a "}, -{ .name = "csvwithheader", .template = "\x70\x72\x65\x66\x69\x78\x2c\x20\x6d\x69\x6e\x6c\x65\x6e\x2c\x20\x6d\x61\x78\x6c\x65\x6e\x2c\x20\x61\x73\x6e\x0a\x7b\x7b\x23\x72\x6f\x61\x73\x7d\x7d\x0a\x7b\x7b\x70\x72\x65\x66\x69\x78\x7d\x7d\x2c\x20\x7b\x7b\x6c\x65\x6e\x67\x74\x68\x7d\x7d\x2c\x20\x7b\x7b\x6d\x61\x78\x6c\x65\x6e\x7d\x7d\x2c\x20\x7b\x7b\x6f\x72\x69\x67\x69\x6e\x7d\x7d\x0a\x7b\x7b\x2f\x72\x6f\x61\x73\x7d\x7d\x0a "}, -{ .name = "json", .template = "\x5b\x0a\x7b\x7b\x23\x72\x6f\x61\x73\x7d\x7d\x0a\x09\x7b\x0a\x09\x09\x22\x70\x72\x65\x66\x69\x78\x22\x3a\x20\x22\x7b\x7b\x70\x72\x65\x66\x69\x78\x7d\x7d\x22\x2c\x0a\x09\x09\x22\x6c\x65\x6e\x67\x74\x68\x22\x3a\x20\x22\x7b\x7b\x6c\x65\x6e\x67\x74\x68\x7d\x7d\x22\x2c\x0a\x09\x09\x22\x6d\x61\x78\x6c\x65\x6e\x22\x3a\x20\x22\x7b\x7b\x6d\x61\x78\x6c\x65\x6e\x7d\x7d\x22\x2c\x0a\x09\x09\x22\x6f\x72\x69\x67\x69\x6e\x22\x3a\x20\x22\x7b\x7b\x6f\x72\x69\x67\x69\x6e\x7d\x7d\x22\x0a\x09\x7d\x7b\x7b\x5e\x6c\x61\x73\x74\x7d\x7d\x2c\x7b\x7b\x2f\x6c\x61\x73\x74\x7d\x7d\x0a\x7b\x7b\x2f\x72\x6f\x61\x73\x7d\x7d\x0a\x5d\x0a "}, -{NULL, NULL} -}; + {.name = "default", + .template = + "\x7b\x7b\x23\x72\x6f\x61\x73\x7d\x7d\x0a\x7b\x7b\x70\x72\x65\x66\x69\x78\x7d\x7d\x2f\x7b\x7b\x6c\x65\x6e\x67\x74\x68\x7d\x7d\x2d\x7b\x7b\x6d\x61\x78\x6c\x65\x6e\x7d\x7d\x20\x41\x53\x20\x7b\x7b\x6f\x72\x69\x67\x69\x6e\x7d\x7d\x0a\x7b\x7b\x2f\x72\x6f\x61\x73\x7d\x7d\x0a "}, + {.name = "csv", + .template = + "\x7b\x7b\x23\x72\x6f\x61\x73\x7d\x7d\x0a\x7b\x7b\x70\x72\x65\x66\x69\x78\x7d\x7d\x2c\x20\x7b\x7b\x6c\x65\x6e\x67\x74\x68\x7d\x7d\x2c\x20\x7b\x7b\x6d\x61\x78\x6c\x65\x6e\x7d\x7d\x2c\x20\x7b\x7b\x6f\x72\x69\x67\x69\x6e\x7d\x7d\x0a\x7b\x7b\x2f\x72\x6f\x61\x73\x7d\x7d\x0a "}, + {.name = "csvwithheader", + .template = + "\x70\x72\x65\x66\x69\x78\x2c\x20\x6d\x69\x6e\x6c\x65\x6e\x2c\x20\x6d\x61\x78\x6c\x65\x6e\x2c\x20\x61\x73\x6e\x0a\x7b\x7b\x23\x72\x6f\x61\x73\x7d\x7d\x0a\x7b\x7b\x70\x72\x65\x66\x69\x78\x7d\x7d\x2c\x20\x7b\x7b\x6c\x65\x6e\x67\x74\x68\x7d\x7d\x2c\x20\x7b\x7b\x6d\x61\x78\x6c\x65\x6e\x7d\x7d\x2c\x20\x7b\x7b\x6f\x72\x69\x67\x69\x6e\x7d\x7d\x0a\x7b\x7b\x2f\x72\x6f\x61\x73\x7d\x7d\x0a "}, + {.name = "json", + .template = + "\x5b\x0a\x7b\x7b\x23\x72\x6f\x61\x73\x7d\x7d\x0a\x09\x7b\x0a\x09\x09\x22\x70\x72\x65\x66\x69\x78\x22\x3a\x20\x22\x7b\x7b\x70\x72\x65\x66\x69\x78\x7d\x7d\x22\x2c\x0a\x09\x09\x22\x6c\x65\x6e\x67\x74\x68\x22\x3a\x20\x22\x7b\x7b\x6c\x65\x6e\x67\x74\x68\x7d\x7d\x22\x2c\x0a\x09\x09\x22\x6d\x61\x78\x6c\x65\x6e\x22\x3a\x20\x22\x7b\x7b\x6d\x61\x78\x6c\x65\x6e\x7d\x7d\x22\x2c\x0a\x09\x09\x22\x6f\x72\x69\x67\x69\x6e\x22\x3a\x20\x22\x7b\x7b\x6f\x72\x69\x67\x69\x6e\x7d\x7d\x22\x0a\x09\x7d\x7b\x7b\x5e\x6c\x61\x73\x74\x7d\x7d\x2c\x7b\x7b\x2f\x6c\x61\x73\x74\x7d\x7d\x0a\x7b\x7b\x2f\x72\x6f\x61\x73\x7d\x7d\x0a\x5d\x0a "}, + {NULL, NULL}}; From f3aa4096a259ba396e5b40cb2f6c526138b15e6a Mon Sep 17 00:00:00 2001 From: tanneberger Date: Mon, 20 May 2024 16:27:47 +0200 Subject: [PATCH 25/55] rtrlib: optimizing aspa_array - using lrtr_realloc instead of malloc & memcpy - decreasing the capacity of the array when possible --- rtrlib/aspa/aspa_array/aspa_array.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/rtrlib/aspa/aspa_array/aspa_array.c b/rtrlib/aspa/aspa_array/aspa_array.c index ea6259a0..0daa2a56 100644 --- a/rtrlib/aspa/aspa_array/aspa_array.c +++ b/rtrlib/aspa/aspa_array/aspa_array.c @@ -173,6 +173,13 @@ enum aspa_status aspa_array_remove(struct aspa_array *array, size_t index, bool } array->size -= 1; + + // decreasing capacity if possible + if (array->size * 2 < array->capacity) { + array->data = lrtr_realloc(array->data, array->size * 2); + array->capacity = array->size * 2; + } + return ASPA_SUCCESS; } @@ -235,20 +242,14 @@ enum aspa_status aspa_array_reserve(struct aspa_array *array, size_t size) return ASPA_SUCCESS; } - struct aspa_record *data = malloc(sizeof(struct aspa_record) * size); + struct aspa_record *data = lrtr_realloc(array->data, sizeof(struct aspa_record) * size); - // malloc failed + // realloc failed if (data == NULL) { return ASPA_ERROR; } - // coping the data from the old array into the new one - if (memcpy(data, array->data, array->size * sizeof(struct aspa_record)) == NULL) { - return ASPA_ERROR; - } - // updating the array - free(array->data); array->capacity = size; array->data = data; From 13015574a3c37a8910406bc2ec79103cd5537d31 Mon Sep 17 00:00:00 2001 From: tanneberger Date: Mon, 20 May 2024 17:21:24 +0200 Subject: [PATCH 26/55] rtrlib: extra checks for when user didn't initialize some tables - added null ptr checks in pfx_validate, aspa_verify and spki_validate - added warnings if the user tries to validate objects where there is no table --- rtrlib/aspa/aspa_private.h | 2 ++ rtrlib/aspa/aspa_verification.c | 10 ++++++++++ rtrlib/bgpsec/bgpsec.c | 5 +++++ rtrlib/pfx/trie/trie-pfx.c | 5 +++++ rtrlib/pfx/trie/trie_private.h | 2 ++ rtrlib/rtr/packets.c | 25 ++++++++++++++++++++++++- 6 files changed, 48 insertions(+), 1 deletion(-) diff --git a/rtrlib/aspa/aspa_private.h b/rtrlib/aspa/aspa_private.h index 2df71d94..9a72e9f2 100644 --- a/rtrlib/aspa/aspa_private.h +++ b/rtrlib/aspa/aspa_private.h @@ -99,6 +99,8 @@ #define ASPA_NOTIFY_NO_OPS 0 +#define ASPA_DBG1(a) lrtr_dbg("ASPA: " a) + // MARK: - Verification enum aspa_hop_result { ASPA_NO_ATTESTATION, ASPA_NOT_PROVIDER_PLUS, ASPA_PROVIDER_PLUS }; diff --git a/rtrlib/aspa/aspa_verification.c b/rtrlib/aspa/aspa_verification.c index c141d1d7..52e75824 100644 --- a/rtrlib/aspa/aspa_verification.c +++ b/rtrlib/aspa/aspa_verification.c @@ -70,6 +70,11 @@ enum aspa_hop_result aspa_check_hop(struct aspa_table *aspa_table, uint32_t cust static enum aspa_verification_result aspa_verify_as_path_upstream(struct aspa_table *aspa_table, uint32_t as_path[], size_t len) { + if (aspa_table == NULL) { + ASPA_DBG1("TRYING TO VALIDATE A AS_PATH BUT NO ASPA TABLE INITIALIZED"); + return ASPA_AS_PATH_INVALID; + } + // Optimized AS_PATH verification algorithm using zero based array // where the origin AS has index N - 1 and the latest AS in the AS_PATH // has index 0. @@ -145,6 +150,11 @@ static enum aspa_verification_result aspa_verify_as_path_upstream(struct aspa_ta static enum aspa_verification_result aspa_verify_as_path_downstream(struct aspa_table *aspa_table, uint32_t as_path[], size_t len) { + if (aspa_table == NULL) { + ASPA_DBG1("TRYING TO VALIDATE A AS_PATH BUT NO ASPA TABLE INITIALIZED"); + return ASPA_AS_PATH_INVALID; + } + // Optimized AS_PATH verification algorithm using zero based array // where the origin AS has index N - 1 and the latest AS in the AS_PATH // has index 0. diff --git a/rtrlib/bgpsec/bgpsec.c b/rtrlib/bgpsec/bgpsec.c index 55d88b76..32078f15 100644 --- a/rtrlib/bgpsec/bgpsec.c +++ b/rtrlib/bgpsec/bgpsec.c @@ -67,6 +67,11 @@ static const uint8_t algorithm_suites[] = {RTR_BGPSEC_ALGORITHM_SUITE_1}; int rtr_bgpsec_validate_as_path(const struct rtr_bgpsec *data, struct spki_table *table) { + if (table == NULL) { + BGPSEC_DBG1("TRYING TO VALIDATE A, BUT NO SPKI TABLE INITIALIZED"); + return RTR_BGPSEC_ERROR; + } + /* The AS path validation result. */ enum rtr_bgpsec_rtvals retval = 0; diff --git a/rtrlib/pfx/trie/trie-pfx.c b/rtrlib/pfx/trie/trie-pfx.c index ffb24164..53897e58 100644 --- a/rtrlib/pfx/trie/trie-pfx.c +++ b/rtrlib/pfx/trie/trie-pfx.c @@ -361,6 +361,11 @@ RTRLIB_EXPORT int pfx_table_validate_r(struct pfx_table *pfx_table, struct pfx_r // assert(reason_len == NULL || *reason_len == 0); // assert(reason == NULL || *reason == NULL); + if (pfx_table == NULL) { + PFX_DBG1("TRYING TO VALIDATE A PREFIX BUT NO TABLE INITIALIZED"); + return PFX_ERROR; + } + pthread_rwlock_rdlock(&(pfx_table->lock)); struct trie_node *root = pfx_table_get_root(pfx_table, prefix->ver); diff --git a/rtrlib/pfx/trie/trie_private.h b/rtrlib/pfx/trie/trie_private.h index 8003ced6..95e696b7 100644 --- a/rtrlib/pfx/trie/trie_private.h +++ b/rtrlib/pfx/trie/trie_private.h @@ -14,6 +14,8 @@ #include +#define PFX_DBG1(a) lrtr_dbg("PFX: " a) + /** * @brief trie_node * @param prefix diff --git a/rtrlib/rtr/packets.c b/rtrlib/rtr/packets.c index 999a57ce..046256eb 100644 --- a/rtrlib/rtr/packets.c +++ b/rtrlib/rtr/packets.c @@ -941,6 +941,13 @@ static int rtr_store_aspa_pdu(struct rtr_socket *rtr_socket, const struct pdu_as static int rtr_update_pfx_table(struct rtr_socket *rtr_socket, struct pfx_table *pfx_table, const void *pdu) { + // the prefix table was not initialized, hence we ignore incoming ROA Objects + if (pfx_table == NULL) { + const char txt[] = "IGNORING INCOMING ROA OBJECTS"; + RTR_DBG("%s", txt); + return RTR_SUCCESS; + } + const enum pdu_type type = rtr_get_pdu_type(pdu); assert(type == IPV4_PREFIX || type == IPV6_PREFIX); @@ -992,6 +999,16 @@ static int rtr_update_pfx_table(struct rtr_socket *rtr_socket, struct pfx_table static int rtr_update_spki_table(struct rtr_socket *rtr_socket, struct spki_table *spki_table, const void *pdu) { + assert(rtr_socket); + assert(pdu); + + // No spki table configured hence we ignore incoming BGPSec Keys + if (spki_table == NULL) { + const char txt[] = "IGNORING INCOMING SPKI OBJECTS"; + RTR_DBG("%s", txt); + return RTR_SUCCESS; + } + const enum pdu_type type = rtr_get_pdu_type(pdu); assert(type == ROUTER_KEY); @@ -1129,10 +1146,16 @@ static int rtr_update_aspa_table(struct rtr_socket *rtr_socket, struct aspa_tabl { // Fail hard in debug builds. assert(rtr_socket); - assert(aspa_table); assert(failed_operation); assert(ops); + // no aspa table was configured hence we ignore incoming aspa objects + if (aspa_table == NULL) { + const char txt[] = "IGNORING INCOMING ASPA OBJECTS"; + RTR_DBG("%s", txt); + retrun RTR_SUCESS; + } + if (!ops || !failed_operation || (pdu_count > 0 && !aspa_pdus)) return RTR_ERROR; From 295cb8e68965f1a66419c832953547fff96e74bc Mon Sep 17 00:00:00 2001 From: tanneberger Date: Wed, 5 Feb 2025 02:46:44 +0100 Subject: [PATCH 27/55] rtrlib: replace exponential up- and downscaling from aspa_array - updating README - up- and downscaling now uses an linear offset of 1000 --- CMakeLists.txt | 12 ++++-------- README.md | 7 ++++--- rtrlib/aspa/aspa_array/aspa_array.c | 15 ++++++++------- tests/test_aspa_array.c | 6 ++---- 4 files changed, 18 insertions(+), 22 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 55090066..3591a4f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -125,22 +125,18 @@ ADD_SUBDIRECTORY(tests) ENABLE_TESTING() ADD_TEST(test_pfx tests/test_pfx) ADD_TEST(test_trie tests/test_trie) -#ADD_TEST(test_pfx_locks tests/test_pfx_locks) - ADD_TEST(test_ht_spkitable tests/test_ht_spkitable) ADD_TEST(test_ht_spkitable_locks tests/test_ht_spkitable_locks) - ADD_TEST(test_live_validation tests/test_live_validation) - ADD_TEST(test_ipaddr tests/test_ipaddr) - ADD_TEST(test_getbits tests/test_getbits) - ADD_TEST(test_dynamic_groups tests/test_dynamic_groups) -list(APPEND CMAKE_CTEST_ARGUMENTS "--output-on-failure") - +ADD_TEST(test_aspa tests/test_aspa) +ADD_TEST(test_aspa_array tests/test_aspa_array) ADD_TEST(test_as_path_verification tests/test_as_path_verification) +list(APPEND CMAKE_CTEST_ARGUMENTS "--output-on-failure") + if(RTRLIB_BGPSEC_ENABLED) ADD_TEST(test_bgpsec tests/test_bgpsec) endif(RTRLIB_BGPSEC_ENABLED) diff --git a/README.md b/README.md index 03d410da..ed8970b9 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,10 @@ Introduction ------------ The RTRlib implements the client-side of the RPKI-RTR protocol ([RFC 6810](https://tools.ietf.org/html/rfc6810)), -([RFC 8210](https://tools.ietf.org/html/rfc8210)) and BGP Prefix Origin -Validation ([RFC 6811](https://tools.ietf.org/html/rfc6811)). This also enables -the maintenance of router keys. Router keys are required to deploy BGPSEC. +([RFC 8210](https://tools.ietf.org/html/rfc8210)), BGP Prefix Origin +Validation ([RFC 6811](https://tools.ietf.org/html/rfc6811)), and ASPA-based Route Leak +detection ([Rev. 18](https://datatracker.ietf.org/doc/draft-ietf-sidrops-aspa-verification/)). +This also enables the maintenance of router keys. Router keys are required to deploy BGPSEC. The software was successfully tested on Linux and FreeBSD. diff --git a/rtrlib/aspa/aspa_array/aspa_array.c b/rtrlib/aspa/aspa_array/aspa_array.c index 0daa2a56..cc40d21d 100644 --- a/rtrlib/aspa/aspa_array/aspa_array.c +++ b/rtrlib/aspa/aspa_array/aspa_array.c @@ -68,19 +68,19 @@ void aspa_array_free(struct aspa_array *array, bool free_provider_arrays) static enum aspa_status aspa_array_reallocate(struct aspa_array *array) { - // the factor by how much the capacity will increase: new_capacity = old_capacity * SIZE_INCREASE_EXPONENTIAL - const size_t SIZE_INCREASE_EXPONENTIAL = 2; + // the factor by how much the capacity will increase: new_capacity = old_capacity + SIZE_INCREASE_OFFSET + const size_t SIZE_INCREASE_OFFSET = 1000; // allocation the new chunk of memory struct aspa_record *tmp = - lrtr_realloc(array->data, sizeof(struct aspa_record) * array->capacity * SIZE_INCREASE_EXPONENTIAL); + lrtr_realloc(array->data, sizeof(struct aspa_record) * array->capacity + SIZE_INCREASE_OFFSET); // malloc failed so returning an error if (!tmp) return ASPA_ERROR; array->data = tmp; - array->capacity *= SIZE_INCREASE_EXPONENTIAL; + array->capacity += SIZE_INCREASE_OFFSET; return ASPA_SUCCESS; } @@ -175,9 +175,10 @@ enum aspa_status aspa_array_remove(struct aspa_array *array, size_t index, bool array->size -= 1; // decreasing capacity if possible - if (array->size * 2 < array->capacity) { - array->data = lrtr_realloc(array->data, array->size * 2); - array->capacity = array->size * 2; + const size_t SIZE_DECREASE_OFFSET = 1000; + if (array->size + SIZE_DECREASE_OFFSET < array->capacity) { + array->data = lrtr_realloc(array->data, array->size + SIZE_DECREASE_OFFSET); + array->capacity = array->size + SIZE_DECREASE_OFFSET; } return ASPA_SUCCESS; diff --git a/tests/test_aspa_array.c b/tests/test_aspa_array.c index dd51b237..5e9dd5d6 100644 --- a/tests/test_aspa_array.c +++ b/tests/test_aspa_array.c @@ -81,8 +81,7 @@ static void test_insert(void) assert(aspa_array_insert(array, 3, &record_3, true) == ASPA_SUCCESS); - // New pointer because reallocated - assert(old_pointer != array->data); + assert(old_pointer == array->data); assert(array->capacity >= 4); assert(array->size == 4); @@ -118,8 +117,7 @@ static void test_append(void) assert(aspa_array_append(array, &record_3, true) == ASPA_SUCCESS); - // new pointer because reallocated - assert(old_pointer != array->data); + assert(old_pointer == array->data); assert(array->capacity >= 4); assert(array->size == 4); From 15cd4b5339165ed2047261114a181fc0e97212a0 Mon Sep 17 00:00:00 2001 From: tanneberger Date: Fri, 21 Mar 2025 08:33:33 +0100 Subject: [PATCH 28/55] rtrlib: fix missing includes - the debug macro needs a function defined inside the rtrlib/lib/log_private.h header which was not included. --- rtrlib/aspa/aspa_private.h | 2 +- rtrlib/pfx/trie/trie_private.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/rtrlib/aspa/aspa_private.h b/rtrlib/aspa/aspa_private.h index 9a72e9f2..0c3e0096 100644 --- a/rtrlib/aspa/aspa_private.h +++ b/rtrlib/aspa/aspa_private.h @@ -91,8 +91,8 @@ #define RTR_ASPA_PRIVATE_H #include "aspa.h" - #include "rtrlib/rtr/rtr.h" +#include "rtrlib/lib/log_private.h" #include #include diff --git a/rtrlib/pfx/trie/trie_private.h b/rtrlib/pfx/trie/trie_private.h index 95e696b7..9c28be25 100644 --- a/rtrlib/pfx/trie/trie_private.h +++ b/rtrlib/pfx/trie/trie_private.h @@ -11,6 +11,7 @@ #define RTR_TRIE_PRIVATE #include "rtrlib/lib/ip_private.h" +#include "rtrlib/lib/log_private.h" #include From a64bc33c9fbe00310e141d831f14e73bdcb162d0 Mon Sep 17 00:00:00 2001 From: Brias Date: Sun, 22 Jun 2025 11:52:05 +0200 Subject: [PATCH 29/55] transport: fix bad copy-paste in `tr_ssh_init` (#299) - Checking the wrong pointer (`ssh_socket->config.client_privkey_path` instead of `ssh_socket->config.server_hostkey_path`) for `NULL` after copying the server host key path to the SSH socket struct could lead to undefined behavior or at least an unnecessary error if `ssh_socket->config.client_privkey_path` is set to `NULL`. This commit fixes the check so that the correct pointer is evaluated. --- rtrlib/transport/ssh/ssh_transport.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtrlib/transport/ssh/ssh_transport.c b/rtrlib/transport/ssh/ssh_transport.c index 222ea251..a4e4a4c8 100644 --- a/rtrlib/transport/ssh/ssh_transport.c +++ b/rtrlib/transport/ssh/ssh_transport.c @@ -341,7 +341,7 @@ RTRLIB_EXPORT int tr_ssh_init(const struct tr_ssh_config *config, struct tr_sock if (config->server_hostkey_path) { ssh_socket->config.server_hostkey_path = lrtr_strdup(config->server_hostkey_path); - if (!ssh_socket->config.client_privkey_path) + if (!ssh_socket->config.server_hostkey_path) goto error; } else { From 574cb640313d9648a92e2c1161107d8ab92e9238 Mon Sep 17 00:00:00 2001 From: Brias Date: Sun, 22 Jun 2025 12:32:58 +0200 Subject: [PATCH 30/55] Fix: Add missing memory allocation NULL-checks (#298) * transport: add missing NULL checks to memory allocation calls - Memory allocation by `lrtr_calloc` or `lrtr_malloc` could fail which led to undefined behavior / segmentation faults when dereferencing the result pointer while initializing an SSH or TCP socket. Now, the `tr_ssh_init` and `tr_tcp_init` functions prematurely return with an error code instead. - In addition, the `tr_ssh_init` did not free the already allocated memory when a password and a path to the private key are simultaneously or not at all configured but simply returned an `TR_ERROR` error code. Now, the allocated memory is freed before returning the `TR_ERROR` error code. * bgpsec: add missing NULL checks to allocation calls - The result of `lrtr_calloc` and `lrtr_malloc` was not checked in multiple locations and thus led to undefined behavior / segmentation faults when memory allocation failed due to dereferencing a NULL pointer. * aspa: add missing NULL checks to memory allocation calls - The result of `lrtr_malloc` and `lrtr_realloc` was not checked in multiple locations and thus led to undefined behavior / segmentation faults when memory allocation failed due to dereferencing a NULL pointer. - If the call to `lrtr_realloc` failed when reducing the ASPA array's capacity in `aspa_array_remove`, the existing data pointer was overridden by NULL which led to a memory leak. Now, if capacity reduction fails, the function keeps the array capacity unchanged and returns a success code, since the array element at the given index has been successfully removed, nevertheless. * bgpsec: handle memory allocation NULL-pointers in calling code - Functions like `init_stream` and `copy_stream` can return `NULL` upon memory allocation failure. Such a scenario was not handled in most of the calling code which would have resulted in undefined behavior / segmentation faults due to dereferencing a NULL-pointer. This commit adds the necessary NULL-checks and propagates NULL or an appropriate error code up the call-chain. --- rtrlib/aspa/aspa.c | 5 ++ rtrlib/aspa/aspa_array/aspa_array.c | 13 +++++- rtrlib/bgpsec/bgpsec.c | 59 ++++++++++++++++++------ rtrlib/bgpsec/bgpsec.h | 2 +- rtrlib/bgpsec/bgpsec_private.h | 5 +- rtrlib/bgpsec/bgpsec_utils.c | 30 ++++++++---- rtrlib/bgpsec/bgpsec_utils_private.h | 4 +- rtrlib/rtr_mgr.h | 3 +- rtrlib/transport/ssh/ssh_transport.c | 6 ++- rtrlib/transport/tcp/tcp_transport.c | 5 ++ tests/test_bgpsec.c | 3 ++ tests/unittests/test_bgpsec_signing.c | 1 + tests/unittests/test_bgpsec_utils.c | 3 ++ tests/unittests/test_bgpsec_validation.c | 1 + 14 files changed, 110 insertions(+), 30 deletions(-) diff --git a/rtrlib/aspa/aspa.c b/rtrlib/aspa/aspa.c index 28b77061..09da2068 100644 --- a/rtrlib/aspa/aspa.c +++ b/rtrlib/aspa/aspa.c @@ -32,6 +32,11 @@ static enum aspa_status aspa_table_notify_clients(struct aspa_table *aspa_table, size_t size = sizeof(uint32_t) * record->provider_count; rec.provider_asns = lrtr_malloc(size); + + if (rec.provider_asns == NULL) { + return ASPA_ERROR; + } + memcpy(rec.provider_asns, record->provider_asns, size); } else { rec.provider_asns = NULL; diff --git a/rtrlib/aspa/aspa_array/aspa_array.c b/rtrlib/aspa/aspa_array/aspa_array.c index cc40d21d..8d21b830 100644 --- a/rtrlib/aspa/aspa_array/aspa_array.c +++ b/rtrlib/aspa/aspa_array/aspa_array.c @@ -177,7 +177,18 @@ enum aspa_status aspa_array_remove(struct aspa_array *array, size_t index, bool // decreasing capacity if possible const size_t SIZE_DECREASE_OFFSET = 1000; if (array->size + SIZE_DECREASE_OFFSET < array->capacity) { - array->data = lrtr_realloc(array->data, array->size + SIZE_DECREASE_OFFSET); + struct aspa_record *tmp_data = lrtr_realloc(array->data, array->size + SIZE_DECREASE_OFFSET); + + if (tmp_data == NULL) { + /* + * Although reducing the array's capacity failed at this point, + * the element at the given index has been removed and thus the + * function's outcome can be considered successful. + */ + return ASPA_SUCCESS; + } + + array->data = tmp_data; array->capacity = array->size + SIZE_DECREASE_OFFSET; } diff --git a/rtrlib/bgpsec/bgpsec.c b/rtrlib/bgpsec/bgpsec.c index 32078f15..509c9110 100644 --- a/rtrlib/bgpsec/bgpsec.c +++ b/rtrlib/bgpsec/bgpsec.c @@ -82,7 +82,7 @@ int rtr_bgpsec_validate_as_path(const struct rtr_bgpsec *data, struct spki_table struct spki_record *tmp_key = NULL; /* A stream that holds the data that is hashed */ - struct stream *s = NULL; + struct stream *stream = NULL; /* Total size of required space for the stream */ unsigned int stream_size = 0; @@ -123,10 +123,15 @@ int rtr_bgpsec_validate_as_path(const struct rtr_bgpsec *data, struct spki_table /* Calculate the required stream size and initialize the stream */ stream_size = req_stream_size(data, VALIDATION); - s = init_stream(stream_size); + stream = init_stream(stream_size); + + if (stream == NULL) { + retval = RTR_BGPSEC_ERROR; + goto err; + } /* Align the byte sequence and store it in the stream */ - retval = align_byte_sequence(data, s, VALIDATION); + retval = align_byte_sequence(data, stream, VALIDATION); if (retval != RTR_BGPSEC_SUCCESS) goto err; @@ -175,7 +180,7 @@ int rtr_bgpsec_validate_as_path(const struct rtr_bgpsec *data, struct spki_table retval = RTR_BGPSEC_VALID; tmp_sig = data->sigs; - for (unsigned int offset = 0, next_offset = 0; offset <= get_stream_size(s) && retval == RTR_BGPSEC_VALID; + for (unsigned int offset = 0, next_offset = 0; offset <= get_stream_size(stream) && retval == RTR_BGPSEC_VALID; offset += next_offset) { if (tmp_sig->next) tmp_sig_len = tmp_sig->next->sig_len; @@ -191,11 +196,16 @@ int rtr_bgpsec_validate_as_path(const struct rtr_bgpsec *data, struct spki_table * The curr pointer points to the resulting "sub-stream". * Hacky, could be improved. */ - size_t len = get_stream_size(s) - offset; + size_t len = get_stream_size(stream) - offset; uint8_t *curr = lrtr_malloc(len); - read_stream_at(curr, s, offset, len); + if (!curr) { + retval = RTR_BGPSEC_ERROR; + goto err; + } + + read_stream_at(curr, stream, offset, len); retval = hash_byte_sequence(curr, len, data->alg, &hash_result); @@ -243,8 +253,8 @@ int rtr_bgpsec_validate_as_path(const struct rtr_bgpsec *data, struct spki_table lrtr_free(hash_result); if (tmp_key) lrtr_free(tmp_key); - if (s) - free_stream(s); + if (stream) + free_stream(stream); if (retval == RTR_BGPSEC_VALID) BGPSEC_DBG1("Validation result for the whole BGPsec_PATH: valid"); @@ -267,7 +277,7 @@ int rtr_bgpsec_generate_signature(const struct rtr_bgpsec *data, uint8_t *privat int req_sig_size = 0; /* A stream that holds the data that is hashed */ - struct stream *s = NULL; + struct stream *stream = NULL; /* Total size of required space for the stream */ unsigned int stream_size = 0; @@ -329,18 +339,23 @@ int rtr_bgpsec_generate_signature(const struct rtr_bgpsec *data, uint8_t *privat /* Calculate the required stream size and initialize the stream */ stream_size = req_stream_size(data, SIGNING); - s = init_stream(stream_size); + stream = init_stream(stream_size); + + if (stream == NULL) { + retval = RTR_BGPSEC_ERROR; + goto err; + } /* Align the bytes to prepare them for hashing. */ - retval = align_byte_sequence(data, s, SIGNING); + retval = align_byte_sequence(data, stream, SIGNING); if (retval != RTR_BGPSEC_SUCCESS) goto err; /* Hash the aligned bytes. */ - uint8_t *curr = get_stream_start(s); + uint8_t *curr = get_stream_start(stream); - retval = hash_byte_sequence(curr, get_stream_size(s), data->alg, &hash_result); + retval = hash_byte_sequence(curr, get_stream_size(stream), data->alg, &hash_result); if (retval != RTR_BGPSEC_SUCCESS) goto err; @@ -353,8 +368,8 @@ int rtr_bgpsec_generate_signature(const struct rtr_bgpsec *data, uint8_t *privat lrtr_free(hash_result); if (*new_signature && (retval == RTR_BGPSEC_SIGNING_ERROR)) rtr_bgpsec_free_signatures(*new_signature); - if (s) - free_stream(s); + if (stream) + free_stream(stream); if (priv_key) { EC_KEY_free(priv_key); priv_key = NULL; @@ -502,6 +517,10 @@ struct rtr_secure_path_seg *rtr_bgpsec_new_secure_path_seg(uint8_t pcount, uint8 { struct rtr_secure_path_seg *seg = lrtr_malloc(sizeof(struct rtr_secure_path_seg)); + if (!seg) { + return NULL; + } + seg->pcount = pcount; seg->flags = flags; seg->asn = asn; @@ -569,7 +588,17 @@ struct rtr_bgpsec_nlri *rtr_bgpsec_nlri_new(int nlri_len) { struct rtr_bgpsec_nlri *nlri = lrtr_malloc(sizeof(struct rtr_bgpsec_nlri)); + if (!nlri) { + return NULL; + } + nlri->nlri = lrtr_malloc(nlri_len); + + if (!nlri->nlri) { + lrtr_free(nlri); + return NULL; + } + return nlri; } diff --git a/rtrlib/bgpsec/bgpsec.h b/rtrlib/bgpsec/bgpsec.h index 1356ee60..d18948d7 100644 --- a/rtrlib/bgpsec/bgpsec.h +++ b/rtrlib/bgpsec/bgpsec.h @@ -43,7 +43,7 @@ enum rtr_bgpsec_rtvals { RTR_BGPSEC_VALID = 1, /** An operation was successful. */ RTR_BGPSEC_SUCCESS = 0, - /** An operation was not sucessful. */ + /** An operation was not successful. */ RTR_BGPSEC_ERROR = -1, /** The public key could not be loaded. */ RTR_BGPSEC_LOAD_PUB_KEY_ERROR = -2, diff --git a/rtrlib/bgpsec/bgpsec_private.h b/rtrlib/bgpsec/bgpsec_private.h index 82750057..fbee76bc 100644 --- a/rtrlib/bgpsec/bgpsec_private.h +++ b/rtrlib/bgpsec/bgpsec_private.h @@ -76,6 +76,8 @@ void rtr_bgpsec_free_signatures(struct rtr_signature_seg *seg); * @param[in] flags The flags field. * @param[in] asn The ASN of the segment. * @return A pointer to an initialized rtr_secure_path_seg struct + * or NULL if an error occurred, e.g. the memory allocation + * failed. */ struct rtr_secure_path_seg *rtr_bgpsec_new_secure_path_seg(uint8_t pcount, uint8_t flags, uint32_t asn); @@ -123,7 +125,8 @@ struct rtr_bgpsec *rtr_bgpsec_new(uint8_t alg, uint8_t safi, uint16_t afi, uint3 /** * @brief Allocate memory for a rtr_bgpsec_nlri struct. - * @return A pointer to an allocated rtr_bgpsec_nlri struct. + * @return A pointer to an allocated rtr_bgpsec_nlri struct + * or NULL if the memory allocation failed. */ struct rtr_bgpsec_nlri *rtr_bgpsec_nlri_new(int nlri_len); diff --git a/rtrlib/bgpsec/bgpsec_utils.c b/rtrlib/bgpsec/bgpsec_utils.c index b4b41c24..3cf0921a 100644 --- a/rtrlib/bgpsec/bgpsec_utils.c +++ b/rtrlib/bgpsec/bgpsec_utils.c @@ -25,14 +25,24 @@ struct stream { struct stream *init_stream(uint16_t size) { - struct stream *s = lrtr_calloc(sizeof(struct stream), 1); - - s->stream = lrtr_calloc(size, 1); - s->start = s->stream; - s->size = size; - s->w_head = 0; - s->r_head = 0; - return s; + struct stream *stream = lrtr_calloc(sizeof(struct stream), 1); + + if (stream == NULL) { + return NULL; + } + + stream->stream = lrtr_calloc(size, 1); + + if (stream->stream == NULL) { + lrtr_free(stream); + return NULL; + } + + stream->start = stream->stream; + stream->size = size; + stream->w_head = 0; + stream->r_head = 0; + return stream; } /* cppcheck-suppress unusedFunction */ @@ -40,6 +50,10 @@ struct stream *copy_stream(struct stream *s) { struct stream *cpy = init_stream(s->size); + if (cpy == NULL) { + return NULL; + } + memcpy(cpy->stream, s->stream, s->size); cpy->w_head = s->w_head; cpy->r_head = s->r_head; diff --git a/rtrlib/bgpsec/bgpsec_utils_private.h b/rtrlib/bgpsec/bgpsec_utils_private.h index 45561c85..8b8a7356 100644 --- a/rtrlib/bgpsec/bgpsec_utils_private.h +++ b/rtrlib/bgpsec/bgpsec_utils_private.h @@ -43,10 +43,10 @@ enum align_type { /* Forward declaration of stream to make it opaque. */ struct stream; -/* Initialize a stream of size bytes */ +/* Initialize and return a stream of size bytes or NULL if the memory allocation failed */ struct stream *init_stream(uint16_t size); -/* Copy a stream s and return the copy */ +/* Copy a stream s and return the copy or NULL if the memory allocation failed */ struct stream *copy_stream(struct stream *s); /* Free stream s */ diff --git a/rtrlib/rtr_mgr.h b/rtrlib/rtr_mgr.h index e195a608..595acb10 100644 --- a/rtrlib/rtr_mgr.h +++ b/rtrlib/rtr_mgr.h @@ -363,7 +363,8 @@ void rtr_mgr_bgpsec_free_signatures(struct rtr_signature_seg *seg); * @param[in] pcount The pcount field. * @param[in] flags The flags field. * @param[in] asn The ASN of the segment. - * @return A pointer to an initialized rtr_secure_path_seg struct + * @return A pointer to an initialized rtr_secure_path_seg struct or + * NULL if an error occurred, e.g. the memory allocation failed. */ struct rtr_secure_path_seg *rtr_mgr_bgpsec_new_secure_path_seg(uint8_t pcount, uint8_t flags, uint32_t asn); diff --git a/rtrlib/transport/ssh/ssh_transport.c b/rtrlib/transport/ssh/ssh_transport.c index a4e4a4c8..243e01ef 100644 --- a/rtrlib/transport/ssh/ssh_transport.c +++ b/rtrlib/transport/ssh/ssh_transport.c @@ -305,6 +305,10 @@ RTRLIB_EXPORT int tr_ssh_init(const struct tr_ssh_config *config, struct tr_sock socket->socket = lrtr_calloc(1, sizeof(struct tr_ssh_socket)); struct tr_ssh_socket *ssh_socket = socket->socket; + if (ssh_socket == NULL) { + return TR_ERROR; + } + ssh_socket->channel = NULL; ssh_socket->session = NULL; ssh_socket->config.host = lrtr_strdup(config->host); @@ -317,7 +321,7 @@ RTRLIB_EXPORT int tr_ssh_init(const struct tr_ssh_config *config, struct tr_sock goto error; if ((config->password && config->client_privkey_path) || (!config->password && !config->client_privkey_path)) - return TR_ERROR; + goto error; if (config->bindaddr) { ssh_socket->config.bindaddr = lrtr_strdup(config->bindaddr); diff --git a/rtrlib/transport/tcp/tcp_transport.c b/rtrlib/transport/tcp/tcp_transport.c index 38804fe5..593efbea 100644 --- a/rtrlib/transport/tcp/tcp_transport.c +++ b/rtrlib/transport/tcp/tcp_transport.c @@ -339,6 +339,11 @@ RTRLIB_EXPORT int tr_tcp_init(const struct tr_tcp_config *config, struct tr_sock socket->ident_fp = &tr_tcp_ident; socket->socket = lrtr_malloc(sizeof(struct tr_tcp_socket)); + + if (socket->socket == NULL) { + return TR_ERROR; + } + struct tr_tcp_socket *tcp_socket = socket->socket; tcp_socket->socket = -1; diff --git a/tests/test_bgpsec.c b/tests/test_bgpsec.c index 9cd56e73..9ec32e10 100644 --- a/tests/test_bgpsec.c +++ b/tests/test_bgpsec.c @@ -115,6 +115,7 @@ static void validate_bgpsec_path_test(void) uint32_t my_as = 65537; pfx = rtr_mgr_bgpsec_nlri_new(3); + assert(pfx != NULL); pfx->nlri_len = 24; pfx->afi = 1; /* LRTR_IPV4 */ pfx_int = htonl(3221225984); /* 192.0.2.0 */ @@ -240,6 +241,7 @@ static void generate_signature_test(void) uint32_t target_as = 65538; pfx = rtr_mgr_bgpsec_nlri_new(3); + assert(pfx != NULL); pfx->nlri_len = 24; pfx->afi = 1; /* LRTR_IPV4 */ pfx_int = htonl(3221225984); /* 192.0.2.0 */ @@ -335,6 +337,7 @@ static void originate_and_validate_test(void) uint32_t target_as = 65536; pfx = rtr_mgr_bgpsec_nlri_new(3); + assert(pfx != NULL); pfx->nlri_len = 24; pfx->afi = 1; /* LRTR_IPV4 */ pfx_int = htonl(3221225984); /* 192.0.2.0 */ diff --git a/tests/unittests/test_bgpsec_signing.c b/tests/unittests/test_bgpsec_signing.c index 664d7010..4cd86141 100644 --- a/tests/unittests/test_bgpsec_signing.c +++ b/tests/unittests/test_bgpsec_signing.c @@ -26,6 +26,7 @@ struct rtr_bgpsec *setup_bgpsec(void) int pfx_int = 0; pfx = rtr_bgpsec_nlri_new(3); + assert(pfx != NULL); pfx->nlri_len = 24; pfx->afi = 1; /* LRTR_IPV4 */ pfx_int = htonl(3221225984); /* 192.0.2.0 */ diff --git a/tests/unittests/test_bgpsec_utils.c b/tests/unittests/test_bgpsec_utils.c index 43b67952..ac1d3181 100644 --- a/tests/unittests/test_bgpsec_utils.c +++ b/tests/unittests/test_bgpsec_utils.c @@ -28,6 +28,7 @@ struct rtr_bgpsec *setup_bgpsec(void) long pfx_int = 0; pfx = rtr_bgpsec_nlri_new(3); + assert(pfx != NULL); pfx->nlri_len = 24; pfx->afi = BGPSEC_IPV4; pfx_int = 3221225984; /* 192.0.2.0 */ @@ -97,6 +98,7 @@ static void test_bgpsec_streams(void **state) struct stream *s_cpy = copy_stream(s); + assert(s_cpy != NULL); assert_int_equal(s->size, s_cpy->size); assert_int_equal(s->r_head, s_cpy->r_head); assert_int_equal(s->w_head, s_cpy->w_head); @@ -169,6 +171,7 @@ static void test_bgpsec_constructors(void **state) long pfx_int = 0; pfx = rtr_bgpsec_nlri_new(3); + assert(pfx != NULL); pfx->nlri_len = 24; pfx->afi = BGPSEC_IPV4; pfx_int = 3221225984; /* 192.0.2.0 */ diff --git a/tests/unittests/test_bgpsec_validation.c b/tests/unittests/test_bgpsec_validation.c index 0b4a67b7..56e3b416 100644 --- a/tests/unittests/test_bgpsec_validation.c +++ b/tests/unittests/test_bgpsec_validation.c @@ -27,6 +27,7 @@ struct rtr_bgpsec *setup_bgpsec(void) int pfx_int = 0; pfx = rtr_bgpsec_nlri_new(3); + assert(pfx != NULL); pfx->nlri_len = 24; pfx->afi = 1; /* LRTR_IPV4 */ pfx_int = htonl(3221225984); /* 192.0.2.0 */ From 6319429306fe9415cc30f431583395e8b326785d Mon Sep 17 00:00:00 2001 From: tanneberger Date: Sat, 19 Jul 2025 15:08:39 +0200 Subject: [PATCH 31/55] doxygen: updating the doxygen example - the example inside the doxygen folder still used the old api --- doxygen/examples/rtr_mgr.c | 12 +++++++++++- rtrlib/aspa/aspa_private.h | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/doxygen/examples/rtr_mgr.c b/doxygen/examples/rtr_mgr.c index 30d05b9b..83293fa3 100644 --- a/doxygen/examples/rtr_mgr.c +++ b/doxygen/examples/rtr_mgr.c @@ -5,6 +5,16 @@ #include #include +static void connection_status_callback(const struct rtr_mgr_group *group __attribute__((unused)), + enum rtr_mgr_status status, + const struct rtr_socket *socket __attribute__((unused)), + void *data __attribute__((unused))) +{ + if (status == RTR_MGR_ERROR) { + printf("connection status callback received error\n"); + } +} + int main() { //create a SSH transport socket @@ -66,7 +76,7 @@ int main() //initialize all rtr_sockets in the server pool with the same settings struct rtr_mgr_config *conf; - rtr_mgr_init(&conf, groups, 2, 30, 600, 600, NULL, NULL, NULL, NULL); + rtr_mgr_init(&conf, groups, 1, &connection_status_callback, NULL); //start the connection manager rtr_mgr_start(conf); diff --git a/rtrlib/aspa/aspa_private.h b/rtrlib/aspa/aspa_private.h index 0c3e0096..ce1d2e06 100644 --- a/rtrlib/aspa/aspa_private.h +++ b/rtrlib/aspa/aspa_private.h @@ -91,8 +91,8 @@ #define RTR_ASPA_PRIVATE_H #include "aspa.h" -#include "rtrlib/rtr/rtr.h" #include "rtrlib/lib/log_private.h" +#include "rtrlib/rtr/rtr.h" #include #include @@ -203,7 +203,7 @@ enum aspa_status aspa_table_update_swap_in_compute(struct aspa_table *aspa_table struct aspa_update **update); /** - * @brief Applys the given update, as previously computed by `aspa_table_update_swap_in_compute`, + * @brief Applies the given update, as previously computed by `aspa_table_update_swap_in_compute`, * releases memory allocated while computing the update and unlocks update lock. The update is consumed. * * @param update The update that will be applied. From e8a9fa1d5cff3370a6605c3945159e2b3a3f6b72 Mon Sep 17 00:00:00 2001 From: tanneberger Date: Sat, 19 Jul 2025 19:19:17 +0200 Subject: [PATCH 32/55] doxygen: update styling - add doxygen/assets folder for new css/html files - updating Doxygen.in to accompany new style files --- doxygen/Doxyfile.in | 2300 +------------- .../assets/doxygen-awesome-darkmode-toggle.js | 157 + .../doxygen-awesome-fragment-copy-button.js | 85 + .../assets/doxygen-awesome-interactive-toc.js | 91 + .../assets/doxygen-awesome-paragraph-link.js | 51 + ...n-awesome-sidebar-only-darkmode-toggle.css | 40 + .../assets/doxygen-awesome-sidebar-only.css | 116 + doxygen/assets/doxygen-awesome-tabs.js | 90 + doxygen/assets/doxygen-awesome.css | 2681 +++++++++++++++++ doxygen/graphics/components.png | Bin 19657 -> 502858 bytes 10 files changed, 3417 insertions(+), 2194 deletions(-) create mode 100644 doxygen/assets/doxygen-awesome-darkmode-toggle.js create mode 100644 doxygen/assets/doxygen-awesome-fragment-copy-button.js create mode 100644 doxygen/assets/doxygen-awesome-interactive-toc.js create mode 100644 doxygen/assets/doxygen-awesome-paragraph-link.js create mode 100644 doxygen/assets/doxygen-awesome-sidebar-only-darkmode-toggle.css create mode 100644 doxygen/assets/doxygen-awesome-sidebar-only.css create mode 100644 doxygen/assets/doxygen-awesome-tabs.js create mode 100644 doxygen/assets/doxygen-awesome.css diff --git a/doxygen/Doxyfile.in b/doxygen/Doxyfile.in index a9bef9ea..473d6c3f 100644 --- a/doxygen/Doxyfile.in +++ b/doxygen/Doxyfile.in @@ -1,2332 +1,244 @@ -# Doxyfile 1.8.8 +PROJECT_NAME = RTRlib +PROJECT_NUMBER = -# This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project. -# -# All text after a double hash (##) is considered a comment and is placed in -# front of the TAG it is preceding. -# -# All text after a single hash (#) is considered a comment and will be ignored. -# The format is: -# TAG = value [value, ...] -# For lists, items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (\" \"). +OUTPUT_DIRECTORY = docs/ -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- +INPUT = @CMAKE_CURRENT_SOURCE_DIR@/rtrlib/ @CMAKE_CURRENT_SOURCE_DIR@/doxygen/ doxygen/mainpage.dox -# This tag specifies the encoding used for all characters in the config file -# that follow. The default is UTF-8 which is also the encoding used for all text -# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv -# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv -# for the list of possible encodings. -# The default value is: UTF-8. +USE_MDFILE_AS_MAINPAGE = @CMAKE_CURRENT_SOURCE_DIR@/doxygen/mainpage.dox +FILE_PATTERNS = *.h *.md +RECURSIVE = YES -DOXYFILE_ENCODING = UTF-8 +HTML_EXTRA_STYLESHEET = @CMAKE_CURRENT_SOURCE_DIR@/doxygen/assets/doxygen-awesome.css \ + @CMAKE_CURRENT_SOURCE_DIR@/doxygen/assets/doxygen-awesome-sidebar-only.css \ + @CMAKE_CURRENT_SOURCE_DIR@/doxygen/assets/doxygen-awesome-sidebar-only-darkmode-toggle.css \ -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by -# double-quotes, unless you are using Doxywizard) that should identify the -# project for which the documentation is generated. This name is used in the -# title of most generated pages and in a few other places. -# The default value is: My Project. +HTML_EXTRA_FILES = @CMAKE_CURRENT_SOURCE_DIR@/doxygen/assets/doxygen-awesome-darkmode-toggle.js \ + @CMAKE_CURRENT_SOURCE_DIR@/doxygen/assets/doxygen-awesome-tabs.js \ + @CMAKE_CURRENT_SOURCE_DIR@/doxygen/assets/doxygen-awesome-interactive-toc.js \ + @CMAKE_CURRENT_SOURCE_DIR@/doxygen/assets/doxygen-awesome-paragraph-link.js \ + @CMAKE_CURRENT_SOURCE_DIR@/doxygen/assets/doxygen-awesome-fragment-copy-button.js -PROJECT_NAME = RTRlib - -# The PROJECT_NUMBER tag can be used to enter a project or revision number. This -# could be handy for archiving the generated documentation or if some version -# control system is used. - -PROJECT_NUMBER = - -# Using the PROJECT_BRIEF tag one can provide an optional one line description -# for a project that appears at the top of each page and should give viewer a -# quick idea about the purpose of the project. Keep the description short. - -PROJECT_BRIEF = - -# With the PROJECT_LOGO tag one can specify an logo or icon that is included in -# the documentation. The maximum height of the logo should not exceed 55 pixels -# and the maximum width should not exceed 200 pixels. Doxygen will copy the logo -# to the output directory. - -PROJECT_LOGO = - -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path -# into which the generated documentation will be written. If a relative path is -# entered, it will be relative to the location where doxygen was started. If -# left blank the current directory will be used. - -OUTPUT_DIRECTORY = docs/ - -# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 4096 sub- -# directories (in 2 levels) under the output directory of each output format and -# will distribute the generated files over these directories. Enabling this -# option can be useful when feeding doxygen a huge amount of source files, where -# putting all generated files in the same directory would otherwise causes -# performance problems for the file system. -# The default value is: NO. +IMAGE_PATH = @CMAKE_CURRENT_SOURCE_DIR@/doxygen/graphics/ CREATE_SUBDIRS = NO - -# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII -# characters to appear in the names of generated files. If set to NO, non-ASCII -# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode -# U+3044. -# The default value is: NO. - +CREATE_SUBDIRS_LEVEL = 8 ALLOW_UNICODE_NAMES = NO - -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, -# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), -# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, -# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), -# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, -# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, -# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, -# Ukrainian and Vietnamese. -# The default value is: English. - OUTPUT_LANGUAGE = English - -# If the BRIEF_MEMBER_DESC tag is set to YES doxygen will include brief member -# descriptions after the members that are listed in the file and class -# documentation (similar to Javadoc). Set to NO to disable this. -# The default value is: YES. - -BRIEF_MEMBER_DESC = NO - -# If the REPEAT_BRIEF tag is set to YES doxygen will prepend the brief -# description of a member or function before the detailed description -# -# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the -# brief descriptions will be completely suppressed. -# The default value is: YES. - -REPEAT_BRIEF = NO - -# This tag implements a quasi-intelligent brief description abbreviator that is -# used to form the text in various listings. Each string in this list, if found -# as the leading text of the brief description, will be stripped from the text -# and the result, after processing the whole list, is used as the annotated -# text. Otherwise, the brief description is used as-is. If left blank, the -# following values are used ($name is automatically replaced with the name of -# the entity):The $name class, The $name widget, The $name file, is, provides, -# specifies, contains, represents, a, an and the. - -ABBREVIATE_BRIEF = - -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# doxygen will generate a detailed section even if there is only a brief -# description. -# The default value is: NO. - -ALWAYS_DETAILED_SEC = NO - -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all -# inherited members of a class in the documentation of that class as if those -# members were ordinary class members. Constructors, destructors and assignment -# operators of the base classes will not be shown. -# The default value is: NO. - -INLINE_INHERITED_MEMB = NO - -# If the FULL_PATH_NAMES tag is set to YES doxygen will prepend the full path -# before files name in the file list and in the header files. If set to NO the -# shortest path that makes the file name unique will be used -# The default value is: YES. - +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = YES +ALWAYS_DETAILED_SEC = YES +INLINE_INHERITED_MEMB = YES FULL_PATH_NAMES = YES - -# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. -# Stripping is only done if one of the specified strings matches the left-hand -# part of the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the path to -# strip. -# -# Note that you can specify absolute paths here, but also relative paths, which -# will be relative from the directory where doxygen is started. -# This tag requires that the tag FULL_PATH_NAMES is set to YES. - -STRIP_FROM_PATH = - -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the -# path mentioned in the documentation of a class, which tells the reader which -# header file to include in order to use a class. If left blank only the name of -# the header file containing the class definition is used. Otherwise one should -# specify the list of include paths that are normally passed to the compiler -# using the -I flag. - STRIP_FROM_INC_PATH = - -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but -# less readable) file names. This can be useful is your file systems doesn't -# support long names like on DOS, Mac, or CD-ROM. -# The default value is: NO. - SHORT_NAMES = NO - -# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the -# first line (until the first dot) of a Javadoc-style comment as the brief -# description. If set to NO, the Javadoc-style will behave just like regular Qt- -# style comments (thus requiring an explicit @brief command for a brief -# description.) -# The default value is: NO. - -JAVADOC_AUTOBRIEF = NO - -# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first -# line (until the first dot) of a Qt-style comment as the brief description. If -# set to NO, the Qt-style will behave just like regular Qt-style comments (thus -# requiring an explicit \brief command for a brief description.) -# The default value is: NO. - -QT_AUTOBRIEF = NO - -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a -# multi-line C++ special comment block (i.e. a block of //! or /// comments) as -# a brief description. This used to be the default behavior. The new default is -# to treat a multi-line C++ comment block as a detailed description. Set this -# tag to YES if you prefer the old behavior instead. -# -# Note that setting this tag to YES also means that rational rose comments are -# not recognized any more. -# The default value is: NO. - +JAVADOC_AUTOBRIEF = YES +JAVADOC_BANNER = NO +QT_AUTOBRIEF = YES MULTILINE_CPP_IS_BRIEF = NO - -# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the -# documentation from any documented member that it re-implements. -# The default value is: YES. - +PYTHON_DOCSTRING = YES INHERIT_DOCS = YES - -# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce a -# new page for each member. If set to NO, the documentation of a member will be -# part of the file/class/namespace that contains it. -# The default value is: NO. - SEPARATE_MEMBER_PAGES = NO - -# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen -# uses this value to replace tabs by spaces in code fragments. -# Minimum value: 1, maximum value: 16, default value: 4. - -TAB_SIZE = 4 - -# This tag can be used to specify a number of aliases that act as commands in -# the documentation. An alias has the form: -# name=value -# For example adding -# "sideeffect=@par Side Effects:\n" -# will allow you to put the command \sideeffect (or @sideeffect) in the -# documentation, which will result in a user-defined paragraph with heading -# "Side Effects:". You can put \n's in the value part of an alias to insert -# newlines. - -ALIASES = - -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding "class=itcl::class" -# will allow you to use the command class in the itcl::class meaning. - -TCL_SUBST = - -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources -# only. Doxygen will then generate output that is more tailored for C. For -# instance, some of the names that are used will be different. The list of all -# members will be omitted, etc. -# The default value is: NO. - +TAB_SIZE = 8 OPTIMIZE_OUTPUT_FOR_C = YES - -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or -# Python sources only. Doxygen will then generate output that is more tailored -# for that language. For instance, namespaces will be presented as packages, -# qualified scopes will look different, etc. -# The default value is: NO. - -OPTIMIZE_OUTPUT_JAVA = NO - -# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran -# sources. Doxygen will then generate output that is tailored for Fortran. -# The default value is: NO. - -OPTIMIZE_FOR_FORTRAN = NO - -# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL -# sources. Doxygen will then generate output that is tailored for VHDL. -# The default value is: NO. - -OPTIMIZE_OUTPUT_VHDL = NO - -# Doxygen selects the parser to use depending on the extension of the files it -# parses. With this tag you can assign which parser to use for a given -# extension. Doxygen has a built-in mapping, but you can override or extend it -# using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, Javascript, -# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: -# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: -# Fortran. In the later case the parser tries to guess whether the code is fixed -# or free formatted code, this is the default for Fortran type files), VHDL. For -# instance to make doxygen treat .inc files as Fortran files (default is PHP), -# and .f files as C (default is Fortran), use: inc=Fortran f=C. -# -# Note For files without extension you can use no_extension as a placeholder. -# -# Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. - +OPTIMIZE_OUTPUT_SLICE = NO EXTENSION_MAPPING = - -# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments -# according to the Markdown format, which allows for more readable -# documentation. See http://daringfireball.net/projects/markdown/ for details. -# The output of markdown processing is further processed by doxygen, so you can -# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in -# case of backward compatibilities issues. -# The default value is: YES. - MARKDOWN_SUPPORT = YES - -# When enabled doxygen tries to link words that correspond to documented -# classes, or namespaces to their corresponding documentation. Such a link can -# be prevented in individual cases by by putting a % sign in front of the word -# or globally by setting AUTOLINK_SUPPORT to NO. -# The default value is: YES. - +TOC_INCLUDE_HEADINGS = 5 +MARKDOWN_ID_STYLE = DOXYGEN AUTOLINK_SUPPORT = YES - -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want -# to include (a tag file for) the STL sources as input, then you should set this -# tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); -# versus func(std::string) {}). This also make the inheritance and collaboration -# diagrams that involve STL classes more complete and accurate. -# The default value is: NO. - BUILTIN_STL_SUPPORT = NO - -# If you use Microsoft's C++/CLI language, you should set this option to YES to -# enable parsing support. -# The default value is: NO. - -CPP_CLI_SUPPORT = NO - -# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: -# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen -# will parse them like normal C++ but will assume all classes use public instead -# of private inheritance when no explicit protection keyword is present. -# The default value is: NO. - +CPP_CLI_SUPPORT = YES SIP_SUPPORT = NO - -# For Microsoft's IDL there are propget and propput attributes to indicate -# getter and setter methods for a property. Setting this option to YES will make -# doxygen to replace the get and set methods by a property in the documentation. -# This will only work if the methods are indeed getting or setting a simple -# type. If this is not the case, or you want to show the methods anyway, you -# should set this option to NO. -# The default value is: YES. - -IDL_PROPERTY_SUPPORT = NO - -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES, then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default -# all members of a group must be documented explicitly. -# The default value is: NO. - +IDL_PROPERTY_SUPPORT = YES DISTRIBUTE_GROUP_DOC = NO - -# Set the SUBGROUPING tag to YES to allow class member groups of the same type -# (for instance a group of public functions) to be put as a subgroup of that -# type (e.g. under the Public Functions section). Set it to NO to prevent -# subgrouping. Alternatively, this can be done per class using the -# \nosubgrouping command. -# The default value is: YES. - +GROUP_NESTED_COMPOUNDS = NO SUBGROUPING = YES - -# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions -# are shown inside the group in which they are included (e.g. using \ingroup) -# instead of on a separate page (for HTML and Man pages) or section (for LaTeX -# and RTF). -# -# Note that this feature does not work in combination with -# SEPARATE_MEMBER_PAGES. -# The default value is: NO. - INLINE_GROUPED_CLASSES = NO - -# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions -# with only public data fields or simple typedef fields will be shown inline in -# the documentation of the scope in which they are defined (i.e. file, -# namespace, or group documentation), provided this scope is documented. If set -# to NO, structs, classes, and unions are shown on a separate page (for HTML and -# Man pages) or section (for LaTeX and RTF). -# The default value is: NO. - INLINE_SIMPLE_STRUCTS = NO - -# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or -# enum is documented as struct, union, or enum with the name of the typedef. So -# typedef struct TypeS {} TypeT, will appear in the documentation as a struct -# with name TypeT. When disabled the typedef will appear as a member of a file, -# namespace, or class. And the struct will be named TypeS. This can typically be -# useful for C code in case the coding convention dictates that all compound -# types are typedef'ed and only the typedef is referenced, never the tag name. -# The default value is: NO. - -TYPEDEF_HIDES_STRUCT = YES - -# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This -# cache is used to resolve symbols given their name and scope. Since this can be -# an expensive process and often the same symbol appears multiple times in the -# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small -# doxygen will become slower. If the cache is too large, memory is wasted. The -# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range -# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 -# symbols. At the end of a run doxygen will report the cache usage and suggest -# the optimal cache size from a speed point of view. -# Minimum value: 0, maximum value: 9, default value: 0. - -LOOKUP_CACHE_SIZE = 0 - -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- - -# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in -# documentation are documented, even if no documentation was available. Private -# class members and static file members will be hidden unless the -# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. -# Note: This will also disable the warnings about undocumented members that are -# normally produced when WARNINGS is set to YES. -# The default value is: NO. - -EXTRACT_ALL = NO - -# If the EXTRACT_PRIVATE tag is set to YES all private members of a class will -# be included in the documentation. -# The default value is: NO. - +TYPEDEF_HIDES_STRUCT = NO +LOOKUP_CACHE_SIZE = 9 +NUM_PROC_THREADS = 1 +TIMESTAMP = YES +EXTRACT_ALL = YES EXTRACT_PRIVATE = NO - -# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal -# scope will be included in the documentation. -# The default value is: NO. - -EXTRACT_PACKAGE = NO - -# If the EXTRACT_STATIC tag is set to YES all static members of a file will be -# included in the documentation. -# The default value is: NO. - -EXTRACT_STATIC = NO - -# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) defined -# locally in source files will be included in the documentation. If set to NO -# only classes defined in header files are included. Does not have any effect -# for Java sources. -# The default value is: YES. - -EXTRACT_LOCAL_CLASSES = NO - -# This flag is only useful for Objective-C code. When set to YES local methods, -# which are defined in the implementation section but not in the interface are -# included in the documentation. If set to NO only methods in the interface are -# included. -# The default value is: NO. - -EXTRACT_LOCAL_METHODS = NO - -# If this flag is set to YES, the members of anonymous namespaces will be -# extracted and appear in the documentation as a namespace called -# 'anonymous_namespace{file}', where file will be replaced with the base name of -# the file that contains the anonymous namespace. By default anonymous namespace -# are hidden. -# The default value is: NO. - +EXTRACT_PRIV_VIRTUAL = NO +EXTRACT_PACKAGE = YES +EXTRACT_STATIC = YES +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = YES EXTRACT_ANON_NSPACES = NO - -# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all -# undocumented members inside documented classes or files. If set to NO these -# members will be included in the various overviews, but no documentation -# section is generated. This option has no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - +RESOLVE_UNNAMED_PARAMS = YES HIDE_UNDOC_MEMBERS = NO - -# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. If set -# to NO these classes will be included in the various overviews. This option has -# no effect if EXTRACT_ALL is enabled. -# The default value is: NO. - -HIDE_UNDOC_CLASSES = YES - -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# (class|struct|union) declarations. If set to NO these declarations will be -# included in the documentation. -# The default value is: NO. - +HIDE_UNDOC_CLASSES = NO HIDE_FRIEND_COMPOUNDS = NO - -# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any -# documentation blocks found inside the body of a function. If set to NO these -# blocks will be appended to the function's detailed documentation block. -# The default value is: NO. - HIDE_IN_BODY_DOCS = NO - -# The INTERNAL_DOCS tag determines if documentation that is typed after a -# \internal command is included. If the tag is set to NO then the documentation -# will be excluded. Set it to YES to include the internal documentation. -# The default value is: NO. - INTERNAL_DOCS = NO - -# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file -# names in lower-case letters. If set to YES upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# and Mac users are advised to set this option to NO. -# The default value is: system dependent. - -CASE_SENSE_NAMES = NO - -# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with -# their full class and namespace scopes in the documentation. If set to YES the -# scope will be hidden. -# The default value is: NO. - +CASE_SENSE_NAMES = YES HIDE_SCOPE_NAMES = NO - -# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of -# the files that are included by a file in the documentation of that file. -# The default value is: YES. - +HIDE_COMPOUND_REFERENCE= NO +SHOW_HEADERFILE = YES SHOW_INCLUDE_FILES = YES - -# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each -# grouped member an include statement to the documentation, telling the reader -# which file to include in order to use the member. -# The default value is: NO. - -SHOW_GROUPED_MEMB_INC = NO - -# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include -# files with double quotes in the documentation rather than with sharp brackets. -# The default value is: NO. - -FORCE_LOCAL_INCLUDES = YES - -# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the -# documentation for inline members. -# The default value is: YES. - +SHOW_GROUPED_MEMB_INC = YES +FORCE_LOCAL_INCLUDES = NO INLINE_INFO = YES - -# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the -# (detailed) documentation of file and class members alphabetically by member -# name. If set to NO the members will appear in declaration order. -# The default value is: YES. - SORT_MEMBER_DOCS = YES - -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief -# descriptions of file, namespace and class members alphabetically by member -# name. If set to NO the members will appear in declaration order. Note that -# this will also influence the order of the classes in the class list. -# The default value is: NO. - -SORT_BRIEF_DOCS = YES - -# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the -# (brief and detailed) documentation of class members so that constructors and -# destructors are listed first. If set to NO the constructors will appear in the -# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. -# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief -# member documentation. -# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting -# detailed member documentation. -# The default value is: NO. - +SORT_BRIEF_DOCS = NO SORT_MEMBERS_CTORS_1ST = NO - -# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy -# of group names into alphabetical order. If set to NO the group names will -# appear in their defined order. -# The default value is: NO. - -SORT_GROUP_NAMES = NO - -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by -# fully-qualified names, including namespaces. If set to NO, the class list will -# be sorted only by class name, not including the namespace part. -# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the alphabetical -# list. -# The default value is: NO. - -SORT_BY_SCOPE_NAME = NO - -# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper -# type resolution of all parameters of a function it will reject a match between -# the prototype and the implementation of a member function even if there is -# only one candidate or it is obvious which candidate to choose by doing a -# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still -# accept a match between prototype and implementation in such cases. -# The default value is: NO. - -STRICT_PROTO_MATCHING = NO - -# The GENERATE_TODOLIST tag can be used to enable ( YES) or disable ( NO) the -# todo list. This list is created by putting \todo commands in the -# documentation. -# The default value is: YES. - -GENERATE_TODOLIST = YES - -# The GENERATE_TESTLIST tag can be used to enable ( YES) or disable ( NO) the -# test list. This list is created by putting \test commands in the -# documentation. -# The default value is: YES. - -GENERATE_TESTLIST = YES - -# The GENERATE_BUGLIST tag can be used to enable ( YES) or disable ( NO) the bug -# list. This list is created by putting \bug commands in the documentation. -# The default value is: YES. - -GENERATE_BUGLIST = YES - -# The GENERATE_DEPRECATEDLIST tag can be used to enable ( YES) or disable ( NO) -# the deprecated list. This list is created by putting \deprecated commands in -# the documentation. -# The default value is: YES. - +SORT_GROUP_NAMES = YES +SORT_BY_SCOPE_NAME = YES +STRICT_PROTO_MATCHING = YES +GENERATE_TODOLIST = NO +GENERATE_TESTLIST = NO +GENERATE_BUGLIST = NO GENERATE_DEPRECATEDLIST= YES - -# The ENABLED_SECTIONS tag can be used to enable conditional documentation -# sections, marked by \if ... \endif and \cond -# ... \endcond blocks. - ENABLED_SECTIONS = - -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the -# initial value of a variable or macro / define can have for it to appear in the -# documentation. If the initializer consists of more lines than specified here -# it will be hidden. Use a value of 0 to hide initializers completely. The -# appearance of the value of individual variables and macros / defines can be -# controlled using \showinitializer or \hideinitializer command in the -# documentation regardless of this setting. -# Minimum value: 0, maximum value: 10000, default value: 30. - -MAX_INITIALIZER_LINES = 30 - -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at -# the bottom of the documentation of classes and structs. If set to YES the list -# will mention the files that were used to generate the documentation. -# The default value is: YES. - -SHOW_USED_FILES = NO - -# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This -# will remove the Files entry from the Quick Index and from the Folder Tree View -# (if specified). -# The default value is: YES. - -SHOW_FILES = NO - -# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces -# page. This will remove the Namespaces entry from the Quick Index and from the -# Folder Tree View (if specified). -# The default value is: YES. - -SHOW_NAMESPACES = NO - -# The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from -# the version control system). Doxygen will invoke the program by executing (via -# popen()) the command command input-file, where command is the value of the -# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided -# by doxygen. Whatever the program writes to standard output is used as the file -# version. For an example see the documentation. - +MAX_INITIALIZER_LINES = 300 +SHOW_USED_FILES = YES +SHOW_FILES = YES +SHOW_NAMESPACES = YES FILE_VERSION_FILTER = - -# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed -# by doxygen. The layout file controls the global structure of the generated -# output files in an output format independent way. To create the layout file -# that represents doxygen's defaults, run doxygen with the -l option. You can -# optionally specify a file name after the option, if omitted DoxygenLayout.xml -# will be used as the name of the layout file. -# -# Note that if you run doxygen from a directory containing a file called -# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE -# tag is left empty. - LAYOUT_FILE = - -# The CITE_BIB_FILES tag can be used to specify one or more bib files containing -# the reference definitions. This must be a list of .bib files. The .bib -# extension is automatically appended if omitted. This requires the bibtex tool -# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. -# For LaTeX the style of the bibliography can be controlled using -# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the -# search path. See also \cite for info how to create references. - -CITE_BIB_FILES = - -#--------------------------------------------------------------------------- -# Configuration options related to warning and progress messages -#--------------------------------------------------------------------------- - -# The QUIET tag can be used to turn on/off the messages that are generated to -# standard output by doxygen. If QUIET is set to YES this implies that the -# messages are off. -# The default value is: NO. - QUIET = YES - -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated to standard error ( stderr) by doxygen. If WARNINGS is set to YES -# this implies that the warnings are on. -# -# Tip: Turn warnings on while writing the documentation. -# The default value is: YES. - WARNINGS = YES - -# If the WARN_IF_UNDOCUMENTED tag is set to YES, then doxygen will generate -# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag -# will automatically be disabled. -# The default value is: YES. - -WARN_IF_UNDOCUMENTED = NO - -# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some parameters -# in a documented function, or documenting parameters that don't exist or using -# markup commands wrongly. -# The default value is: YES. - +WARN_IF_UNDOCUMENTED = YES WARN_IF_DOC_ERROR = YES - -# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that -# are documented, but have no documentation for their parameters or return -# value. If set to NO doxygen will only warn about wrong or incomplete parameter -# documentation, but not about the absence of documentation. -# The default value is: NO. - -WARN_NO_PARAMDOC = YES - -# The WARN_FORMAT tag determines the format of the warning messages that doxygen -# can produce. The string should contain the $file, $line, and $text tags, which -# will be replaced by the file and line number from which the warning originated -# and the warning text. Optionally the format may contain $version, which will -# be replaced by the version of the file (if it could be obtained via -# FILE_VERSION_FILTER) -# The default value is: $file:$line: $text. - +WARN_IF_INCOMPLETE_DOC = YES +WARN_NO_PARAMDOC = NO +WARN_IF_UNDOC_ENUM_VAL = NO +WARN_AS_ERROR = NO WARN_FORMAT = "$file:$line: $text" - -# The WARN_LOGFILE tag can be used to specify a file to which warning and error -# messages should be written. If left blank the output is written to standard -# error (stderr). - +WARN_LINE_FORMAT = "at line $line of file $file" WARN_LOGFILE = - -#--------------------------------------------------------------------------- -# Configuration options related to the input files -#--------------------------------------------------------------------------- - -# The INPUT tag is used to specify the files and/or directories that contain -# documented source files. You may enter file names like myfile.cpp or -# directories like /usr/src/myproject. Separate the files or directories with -# spaces. -# Note: If this tag is empty the current directory is searched. - -INPUT = @CMAKE_CURRENT_SOURCE_DIR@/rtrlib/ @CMAKE_CURRENT_SOURCE_DIR@/doxygen/ - -# This tag can be used to specify the character encoding of the source files -# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses -# libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: http://www.gnu.org/software/libiconv) for the list of -# possible encodings. -# The default value is: UTF-8. - INPUT_ENCODING = UTF-8 - -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and -# *.h) to filter out the source-files in the directories. If left blank the -# following patterns are tested:*.c, *.cc, *.cxx, *.cpp, *.c++, *.java, *.ii, -# *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, -# *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, -# *.md, *.mm, *.dox, *.py, *.f90, *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, -# *.qsf, *.as and *.js. - -FILE_PATTERNS = *.h \ - *.dox - -# The RECURSIVE tag can be used to specify whether or not subdirectories should -# be searched for input files as well. -# The default value is: NO. - -RECURSIVE = YES - -# The EXCLUDE tag can be used to specify files and/or directories that should be -# excluded from the INPUT source files. This way you can easily exclude a -# subdirectory from a directory tree whose root is specified with the INPUT tag. -# -# Note that relative paths are relative to the directory from which doxygen is -# run. - -EXCLUDE = - -# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or -# directories that are symbolic links (a Unix file system feature) are excluded -# from the input. -# The default value is: NO. - -EXCLUDE_SYMLINKS = NO - -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories for example use the pattern */test/* - -EXCLUDE_PATTERNS = */rtrlib/spki/hashtable/* @DOCS_EXCLUDE_PATTERN@ - -# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names -# (namespaces, classes, functions, etc.) that should be excluded from the -# output. The symbol name can be a fully qualified name, a word, or if the -# wildcard * is used, a substring. Examples: ANamespace, AClass, -# AClass::ANamespace, ANamespace::*Test -# -# Note that the wildcards are matched against the file with absolute path, so to -# exclude all test directories use the pattern */test/* - -EXCLUDE_SYMBOLS = - -# The EXAMPLE_PATH tag can be used to specify one or more files or directories -# that contain example code fragments that are included (see the \include -# command). - -EXAMPLE_PATH = @CMAKE_CURRENT_SOURCE_DIR@/doxygen/examples/ - -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and -# *.h) to filter out the source-files in the directories. If left blank all -# files are included. - +INPUT_FILE_ENCODING = +EXAMPLE_PATH = EXAMPLE_PATTERNS = - -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude commands -# irrespective of the value of the RECURSIVE tag. -# The default value is: NO. - -EXAMPLE_RECURSIVE = NO - -# The IMAGE_PATH tag can be used to specify one or more files or directories -# that contain images that are to be included in the documentation (see the -# \image command). - -IMAGE_PATH = @CMAKE_CURRENT_SOURCE_DIR@/doxygen/graphics/ - -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command: -# -# -# -# where is the value of the INPUT_FILTER tag, and is the -# name of an input file. Doxygen will then use the output that the filter -# program writes to standard output. If FILTER_PATTERNS is specified, this tag -# will be ignored. -# -# Note that the filter must not add or remove lines; it is applied before the -# code is scanned, but not when the output code is generated. If lines are added -# or removed, the anchors will not be placed correctly. - +EXAMPLE_RECURSIVE = YES INPUT_FILTER = - -# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. The filters are a list of the form: pattern=filter -# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how -# filters are used. If the FILTER_PATTERNS tag is empty or if none of the -# patterns match the file name, INPUT_FILTER is applied. - FILTER_PATTERNS = - -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER ) will also be used to filter the input files that are used for -# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). -# The default value is: NO. - FILTER_SOURCE_FILES = NO - -# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file -# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and -# it is also possible to disable source filtering for a specific pattern using -# *.ext= (so without naming a filter). -# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. - FILTER_SOURCE_PATTERNS = - -# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that -# is part of the input, its contents will be placed on the main page -# (index.html). This can be useful if you have a project on for instance GitHub -# and want to reuse the introduction page also for the doxygen output. - -USE_MDFILE_AS_MAINPAGE = - -#--------------------------------------------------------------------------- -# Configuration options related to source browsing -#--------------------------------------------------------------------------- - -# If the SOURCE_BROWSER tag is set to YES then a list of source files will be -# generated. Documented entities will be cross-referenced with these sources. -# -# Note: To get rid of all source code in the generated output, make sure that -# also VERBATIM_HEADERS is set to NO. -# The default value is: NO. - SOURCE_BROWSER = NO - -# Setting the INLINE_SOURCES tag to YES will include the body of functions, -# classes and enums directly into the documentation. -# The default value is: NO. - INLINE_SOURCES = NO - -# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any -# special comment blocks from generated source code fragments. Normal C, C++ and -# Fortran comments will always remain visible. -# The default value is: YES. - STRIP_CODE_COMMENTS = YES - -# If the REFERENCED_BY_RELATION tag is set to YES then for each documented -# function all documented functions referencing it will be listed. -# The default value is: NO. - -REFERENCED_BY_RELATION = YES - -# If the REFERENCES_RELATION tag is set to YES then for each documented function -# all documented entities called/used by that function will be listed. -# The default value is: NO. - -REFERENCES_RELATION = YES - -# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set -# to YES, then the hyperlinks from functions in REFERENCES_RELATION and -# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will -# link to the documentation. -# The default value is: YES. - +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO REFERENCES_LINK_SOURCE = YES - -# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the -# source code will show a tooltip with additional information such as prototype, -# brief description and links to the definition and documentation. Since this -# will make the HTML file larger and loading of large files a bit slower, you -# can opt to disable this feature. -# The default value is: YES. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - SOURCE_TOOLTIPS = YES - -# If the USE_HTAGS tag is set to YES then the references to source code will -# point to the HTML generated by the htags(1) tool instead of doxygen built-in -# source browser. The htags tool is part of GNU's global source tagging system -# (see http://www.gnu.org/software/global/global.html). You will need version -# 4.8.6 or higher. -# -# To use it do the following: -# - Install the latest version of global -# - Enable SOURCE_BROWSER and USE_HTAGS in the config file -# - Make sure the INPUT points to the root of the source tree -# - Run doxygen as normal -# -# Doxygen will invoke htags (and that will in turn invoke gtags), so these -# tools must be available from the command line (i.e. in the search path). -# -# The result: instead of the source browser generated by doxygen, the links to -# source code will now point to the output of htags. -# The default value is: NO. -# This tag requires that the tag SOURCE_BROWSER is set to YES. - USE_HTAGS = NO - -# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a -# verbatim copy of the header file for each class for which an include is -# specified. Set to NO to disable this. -# See also: Section \class. -# The default value is: YES. - VERBATIM_HEADERS = YES - -#--------------------------------------------------------------------------- -# Configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- - -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all -# compounds will be generated. Enable this if the project contains a lot of -# classes, structs, unions or interfaces. -# The default value is: YES. - ALPHABETICAL_INDEX = YES - -# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in -# which the alphabetical index list will be split. -# Minimum value: 1, maximum value: 20, default value: 5. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -COLS_IN_ALPHA_INDEX = 5 - -# In case all classes in a project start with a common prefix, all classes will -# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag -# can be used to specify a prefix (or a list of prefixes) that should be ignored -# while generating the index headers. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - IGNORE_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the HTML output -#--------------------------------------------------------------------------- - -# If the GENERATE_HTML tag is set to YES doxygen will generate HTML output -# The default value is: YES. - GENERATE_HTML = YES - -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a -# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of -# it. -# The default directory is: html. -# This tag requires that the tag GENERATE_HTML is set to YES. - HTML_OUTPUT = html - -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each -# generated HTML page (for example: .htm, .php, .asp). -# The default value is: .html. -# This tag requires that the tag GENERATE_HTML is set to YES. - HTML_FILE_EXTENSION = .html - -# The HTML_HEADER tag can be used to specify a user-defined HTML header file for -# each generated HTML page. If the tag is left blank doxygen will generate a -# standard header. -# -# To get valid HTML the header file that includes any scripts and style sheets -# that doxygen needs, which is dependent on the configuration options used (e.g. -# the setting GENERATE_TREEVIEW). It is highly recommended to start with a -# default header using -# doxygen -w html new_header.html new_footer.html new_stylesheet.css -# YourConfigFile -# and then modify the file new_header.html. See also section "Doxygen usage" -# for information on how to generate the default header that doxygen normally -# uses. -# Note: The header is subject to change so you typically have to regenerate the -# default header when upgrading to a newer version of doxygen. For a description -# of the possible markers and block names see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_HEADER = - -# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each -# generated HTML page. If the tag is left blank doxygen will generate a standard -# footer. See HTML_HEADER for more information on how to generate a default -# footer and what special commands can be used inside the footer. See also -# section "Doxygen usage" for information on how to generate the default footer -# that doxygen normally uses. -# This tag requires that the tag GENERATE_HTML is set to YES. - HTML_FOOTER = - -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style -# sheet that is used by each HTML page. It can be used to fine-tune the look of -# the HTML output. If left blank doxygen will generate a default style sheet. -# See also section "Doxygen usage" for information on how to generate the style -# sheet that doxygen normally uses. -# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as -# it is more robust and this tag (HTML_STYLESHEET) will in the future become -# obsolete. -# This tag requires that the tag GENERATE_HTML is set to YES. - HTML_STYLESHEET = - -# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined -# cascading style sheets that are included after the standard style sheets -# created by doxygen. Using this option one can overrule certain style aspects. -# This is preferred over using HTML_STYLESHEET since it does not replace the -# standard style sheet and is therefor more robust against future updates. -# Doxygen will copy the style sheet files to the output directory. -# Note: The order of the extra stylesheet files is of importance (e.g. the last -# stylesheet in the list overrules the setting of the previous ones in the -# list). For an example see the documentation. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_STYLESHEET = - -# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or -# other source files which should be copied to the HTML output directory. Note -# that these files will be copied to the base HTML output directory. Use the -# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these -# files. In the HTML_STYLESHEET file, use the file name only. Also note that the -# files will be copied as-is; there are no commands or markers available. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_EXTRA_FILES = - -# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen -# will adjust the colors in the stylesheet and background images according to -# this color. Hue is specified as an angle on a colorwheel, see -# http://en.wikipedia.org/wiki/Hue for more information. For instance the value -# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 -# purple, and 360 is red again. -# Minimum value: 0, maximum value: 359, default value: 220. -# This tag requires that the tag GENERATE_HTML is set to YES. - +HTML_COLORSTYLE = LIGHT HTML_COLORSTYLE_HUE = 220 - -# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors -# in the HTML output. For a value of 0 the output will use grayscales only. A -# value of 255 will produce the most vivid colors. -# Minimum value: 0, maximum value: 255, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - HTML_COLORSTYLE_SAT = 100 - -# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the -# luminance component of the colors in the HTML output. Values below 100 -# gradually make the output lighter, whereas values above 100 make the output -# darker. The value divided by 100 is the actual gamma applied, so 80 represents -# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not -# change the gamma. -# Minimum value: 40, maximum value: 240, default value: 80. -# This tag requires that the tag GENERATE_HTML is set to YES. - HTML_COLORSTYLE_GAMMA = 80 - -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to NO can help when comparing the output of multiple runs. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = YES - -# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML -# documentation will contain sections that can be hidden and shown after the -# page has loaded. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_DYNAMIC_SECTIONS = NO - -# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries -# shown in the various tree structured indices initially; the user can expand -# and collapse entries dynamically later on. Doxygen will expand the tree to -# such a level that at most the specified number of entries are visible (unless -# a fully collapsed tree already exceeds this amount). So setting the number of -# entries 1 will produce a full collapsed tree by default. 0 is a special value -# representing an infinite number of entries and will result in a full expanded -# tree by default. -# Minimum value: 0, maximum value: 9999, default value: 100. -# This tag requires that the tag GENERATE_HTML is set to YES. - +HTML_DYNAMIC_MENUS = YES +HTML_DYNAMIC_SECTIONS = YES +HTML_CODE_FOLDING = YES +HTML_COPY_CLIPBOARD = YES +HTML_PROJECT_COOKIE = HTML_INDEX_NUM_ENTRIES = 100 - -# If the GENERATE_DOCSET tag is set to YES, additional index files will be -# generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: http://developer.apple.com/tools/xcode/), introduced with -# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a -# Makefile in the HTML output directory. Running make will produce the docset in -# that directory and running make install will install the docset in -# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at -# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html -# for more information. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_DOCSET = NO - -# This tag determines the name of the docset feed. A documentation feed provides -# an umbrella under which multiple documentation sets from a single provider -# (such as a company or product suite) can be grouped. -# The default value is: Doxygen generated docs. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - +GENERATE_DOCSET = YES DOCSET_FEEDNAME = "Doxygen generated docs" - -# This tag specifies a string that should uniquely identify the documentation -# set bundle. This should be a reverse domain-name style string, e.g. -# com.mycompany.MyDocSet. Doxygen will append .docset to the name. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - +DOCSET_FEEDURL = DOCSET_BUNDLE_ID = org.doxygen.Project - -# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify -# the documentation publisher. This should be a reverse domain-name style -# string, e.g. com.mycompany.MyDocSet.documentation. -# The default value is: org.doxygen.Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - DOCSET_PUBLISHER_ID = org.doxygen.Publisher - -# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. -# The default value is: Publisher. -# This tag requires that the tag GENERATE_DOCSET is set to YES. - DOCSET_PUBLISHER_NAME = Publisher - -# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three -# additional HTML index files: index.hhp, index.hhc, and index.hhk. The -# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on -# Windows. -# -# The HTML Help Workshop contains a compiler that can convert all HTML output -# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML -# files are now used as the Windows 98 help format, and will replace the old -# Windows help format (.hlp) on all Windows platforms in the future. Compressed -# HTML files also contain an index, a table of contents, and you can search for -# words in the documentation. The HTML workshop also contains a viewer for -# compressed HTML files. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - GENERATE_HTMLHELP = NO - -# The CHM_FILE tag can be used to specify the file name of the resulting .chm -# file. You can add a path in front of the file if the result should not be -# written to the html output directory. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -CHM_FILE = - -# The HHC_LOCATION tag can be used to specify the location (absolute path -# including file name) of the HTML help compiler ( hhc.exe). If non-empty -# doxygen will try to run the HTML help compiler on the generated index.hhp. -# The file has to be specified with full path. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - +CHM_FILE = NO HHC_LOCATION = - -# The GENERATE_CHI flag controls if a separate .chi index file is generated ( -# YES) or that it should be included in the master .chm file ( NO). -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - GENERATE_CHI = NO - -# The CHM_INDEX_ENCODING is used to encode HtmlHelp index ( hhk), content ( hhc) -# and project file content. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - CHM_INDEX_ENCODING = - -# The BINARY_TOC flag controls whether a binary table of contents is generated ( -# YES) or a normal table of contents ( NO) in the .chm file. Furthermore it -# enables the Previous and Next buttons. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - -BINARY_TOC = NO - -# The TOC_EXPAND flag can be set to YES to add extra items for group members to -# the table of contents of the HTML help documentation and to the tree view. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTMLHELP is set to YES. - +BINARY_TOC = YES TOC_EXPAND = NO - -# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and -# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that -# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help -# (.qch) of the generated HTML documentation. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - +SITEMAP_URL = GENERATE_QHP = NO - -# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify -# the file name of the resulting .qch file. The path specified is relative to -# the HTML output folder. -# This tag requires that the tag GENERATE_QHP is set to YES. - QCH_FILE = - -# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help -# Project output. For more information please see Qt Help Project / Namespace -# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_QHP is set to YES. - QHP_NAMESPACE = org.doxygen.Project - -# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt -# Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- -# folders). -# The default value is: doc. -# This tag requires that the tag GENERATE_QHP is set to YES. - QHP_VIRTUAL_FOLDER = doc - -# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom -# filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - QHP_CUST_FILTER_NAME = - -# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the -# custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- -# filters). -# This tag requires that the tag GENERATE_QHP is set to YES. - QHP_CUST_FILTER_ATTRS = - -# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this -# project's filter section matches. Qt Help Project / Filter Attributes (see: -# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). -# This tag requires that the tag GENERATE_QHP is set to YES. - QHP_SECT_FILTER_ATTRS = - -# The QHG_LOCATION tag can be used to specify the location of Qt's -# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the -# generated .qhp file. -# This tag requires that the tag GENERATE_QHP is set to YES. - QHG_LOCATION = - -# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be -# generated, together with the HTML files, they form an Eclipse help plugin. To -# install this plugin and make it available under the help contents menu in -# Eclipse, the contents of the directory containing the HTML and XML files needs -# to be copied into the plugins directory of eclipse. The name of the directory -# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. -# After copying Eclipse needs to be restarted before the help appears. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - GENERATE_ECLIPSEHELP = NO - -# A unique identifier for the Eclipse help plugin. When installing the plugin -# the directory name containing the HTML and XML files should also have this -# name. Each documentation set should have its own identifier. -# The default value is: org.doxygen.Project. -# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. - ECLIPSE_DOC_ID = org.doxygen.Project - -# If you want full control over the layout of the generated HTML pages it might -# be necessary to disable the index and replace it with your own. The -# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top -# of each HTML page. A value of NO enables the index and the value YES disables -# it. Since the tabs in the index contain the same information as the navigation -# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - DISABLE_INDEX = NO - -# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index -# structure should be generated to display hierarchical information. If the tag -# value is set to YES, a side panel will be generated containing a tree-like -# index structure (just like the one that is generated for HTML Help). For this -# to work a browser that supports JavaScript, DHTML, CSS and frames is required -# (i.e. any modern browser). Windows users are probably better off using the -# HTML help feature. Via custom stylesheets (see HTML_EXTRA_STYLESHEET) one can -# further fine-tune the look of the index. As an example, the default style -# sheet generated by doxygen has an example that shows how to put an image at -# the root of the tree instead of the PROJECT_NAME. Since the tree basically has -# the same information as the tab index, you could consider setting -# DISABLE_INDEX to YES when enabling this option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -GENERATE_TREEVIEW = NO - -# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that -# doxygen will group on one line in the generated HTML documentation. -# -# Note that a value of 0 will completely suppress the enum values from appearing -# in the overview section. -# Minimum value: 0, maximum value: 20, default value: 4. -# This tag requires that the tag GENERATE_HTML is set to YES. - +GENERATE_TREEVIEW = YES +FULL_SIDEBAR = NO ENUM_VALUES_PER_LINE = 4 - -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used -# to set the initial width (in pixels) of the frame in which the tree is shown. -# Minimum value: 0, maximum value: 1500, default value: 250. -# This tag requires that the tag GENERATE_HTML is set to YES. - -TREEVIEW_WIDTH = 250 - -# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open links to -# external symbols imported via tag files in a separate window. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - +TREEVIEW_WIDTH = 300 EXT_LINKS_IN_WINDOW = NO - -# Use this tag to change the font size of LaTeX formulas included as images in -# the HTML documentation. When you change the font size after a successful -# doxygen run you need to manually remove any form_*.png images from the HTML -# output directory to force them to be regenerated. -# Minimum value: 8, maximum value: 50, default value: 10. -# This tag requires that the tag GENERATE_HTML is set to YES. - +OBFUSCATE_EMAILS = NO +HTML_FORMULA_FORMAT = png FORMULA_FONTSIZE = 10 - -# Use the FORMULA_TRANPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are not -# supported properly for IE 6.0, but are supported on all modern browsers. -# -# Note that when changing this option you need to delete any form_*.png files in -# the HTML output directory before the changes have effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_TRANSPARENT = YES - -# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# http://www.mathjax.org) which uses client side Javascript for the rendering -# instead of using prerendered bitmaps. Use this if you do not have LaTeX -# installed or if you want to formulas look prettier in the HTML output. When -# enabled you may also need to install MathJax separately and configure the path -# to it using the MATHJAX_RELPATH option. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - +FORMULA_MACROFILE = USE_MATHJAX = NO - -# When MathJax is enabled you can set the default output format to be used for -# the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/latest/output.html) for more details. -# Possible values are: HTML-CSS (which is slower, but has the best -# compatibility), NativeMML (i.e. MathML) and SVG. -# The default value is: HTML-CSS. -# This tag requires that the tag USE_MATHJAX is set to YES. - +MATHJAX_VERSION = MathJax_2 MATHJAX_FORMAT = HTML-CSS - -# When MathJax is enabled you need to specify the location relative to the HTML -# output directory using the MATHJAX_RELPATH option. The destination directory -# should contain the MathJax.js script. For instance, if the mathjax directory -# is located at the same level as the HTML output directory, then -# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax -# Content Delivery Network so you can quickly see the result without installing -# MathJax. However, it is strongly recommended to install a local copy of -# MathJax from http://www.mathjax.org before deployment. -# The default value is: http://cdn.mathjax.org/mathjax/latest. -# This tag requires that the tag USE_MATHJAX is set to YES. - -MATHJAX_RELPATH = http://www.mathjax.org/mathjax - -# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax -# extension names that should be enabled during MathJax rendering. For example -# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols -# This tag requires that the tag USE_MATHJAX is set to YES. - +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest MATHJAX_EXTENSIONS = - -# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces -# of code that will be used on startup of the MathJax code. See the MathJax site -# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an -# example see the documentation. -# This tag requires that the tag USE_MATHJAX is set to YES. - MATHJAX_CODEFILE = - -# When the SEARCHENGINE tag is enabled doxygen will generate a search box for -# the HTML output. The underlying search engine uses javascript and DHTML and -# should work on any modern browser. Note that when using HTML help -# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) -# there is already a search function so this one should typically be disabled. -# For large projects the javascript based search engine can be slow, then -# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to -# search using the keyboard; to jump to the search box use + S -# (what the is depends on the OS and browser, but it is typically -# , /