diff options
Diffstat (limited to 'src')
256 files changed, 38418 insertions, 13757 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt deleted file mode 100644 index 54fdd8ab..00000000 --- a/src/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -add_subdirectory(lib) -add_subdirectory(ipcpd) -add_subdirectory(irmd) -add_subdirectory(tools) diff --git a/src/ipcpd/CMakeLists.txt b/src/ipcpd/CMakeLists.txt index 1ce1bc0d..609da54a 100644 --- a/src/ipcpd/CMakeLists.txt +++ b/src/ipcpd/CMakeLists.txt @@ -1,54 +1,30 @@ -set(IPCP_QOS_CUBE_BE_PRIO 50 CACHE STRING - "Priority for best effort QoS cube (0-99)") -set(IPCP_QOS_CUBE_VIDEO_PRIO 90 CACHE STRING - "Priority for video QoS cube (0-99)") -set(IPCP_QOS_CUBE_VOICE_PRIO 99 CACHE STRING - "Priority for voice QoS cube (0-99)") -set(IPCP_MIN_THREADS 4 CACHE STRING - "Minimum number of worker threads in the IPCP") -set(IPCP_ADD_THREADS 4 CACHE STRING - "Number of extra threads to start when an IPCP faces thread starvation") -set(IPCP_SCHED_THR_MUL 2 CACHE STRING - "Number of scheduler threads per QoS cube") -set(DISABLE_CORE_LOCK TRUE CACHE BOOL - "Disable locking performance threads to a core") -set(IPCP_CONN_WAIT_DIR TRUE CACHE BOOL - "Check the running state of the directory when adding a dt connection") -set(DHT_ENROLL_SLACK 50 CACHE STRING - "DHT enrollment waiting time (0-999, ms)") -if (CMAKE_SYSTEM_NAME STREQUAL "Linux") - set(IPCP_LINUX_TIMERSLACK_NS 1000 CACHE STRING - "Slack value for high resolution timers on Linux systems.") -endif () - -if ((IPCP_QOS_CUBE_BE_PRIO LESS 0) OR (IPCP_QOS_CUBE_BE_PRIO GREATER 99)) - message(FATAL_ERROR "Invalid priority for best effort QoS cube") -endif () - -if ((IPCP_QOS_CUBE_VIDEO_PRIO LESS 0) OR (IPCP_QOS_CUBE_VIDEO_PRIO GREATER 99)) - message(FATAL_ERROR "Invalid priority for video QoS cube") -endif () - -if ((IPCP_QOS_CUBE_VOICE_PRIO LESS 0) OR (IPCP_QOS_CUBE_VOICE_PRIO GREATER 99)) - message(FATAL_ERROR "Invalid priority for voice QoS cube") -endif () - -if ((DHT_ENROLL_SLACK LESS 0) OR (DHT_ENROLL_SLACK GREATER 999)) - message(FATAL_ERROR "Invalid DHT slack value") -endif () - +# IPCP (IPC Process) daemons build configuration +# Configuration options and validation are in cmake/config/ipcp/*.cmake +# Common sources shared by all IPCPs (absolute paths for subdirectories) set(IPCP_SOURCES - # Add source files here ${CMAKE_CURRENT_SOURCE_DIR}/ipcp.c ${CMAKE_CURRENT_SOURCE_DIR}/shim-data.c - ) +) -add_subdirectory(local) -add_subdirectory(eth) -add_subdirectory(udp) -add_subdirectory(unicast) -add_subdirectory(broadcast) +set(COMMON_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/common/enroll.c +) + +set(IPCP_INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_BINARY_DIR}/include +) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/config.h" @ONLY) + +add_subdirectory(local) +add_subdirectory(broadcast) +add_subdirectory(unicast) +if(HAVE_ETH) + add_subdirectory(eth) +endif() +add_subdirectory(udp) diff --git a/src/ipcpd/broadcast/CMakeLists.txt b/src/ipcpd/broadcast/CMakeLists.txt index afcc8696..433d9979 100644 --- a/src/ipcpd/broadcast/CMakeLists.txt +++ b/src/ipcpd/broadcast/CMakeLists.txt @@ -1,34 +1,22 @@ -get_filename_component(CURRENT_SOURCE_PARENT_DIR - ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) -get_filename_component(CURRENT_BINARY_PARENT_DIR - ${CMAKE_CURRENT_BINARY_DIR} DIRECTORY) +# Broadcast IPCP build configuration -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) +set(IPCP_BROADCAST_TARGET ipcpd-broadcast) -include_directories(${CURRENT_SOURCE_PARENT_DIR}) -include_directories(${CURRENT_BINARY_PARENT_DIR}) - -include_directories(${CMAKE_SOURCE_DIR}/include) -include_directories(${CMAKE_BINARY_DIR}/include) - -set(IPCP_BROADCAST_TARGET ipcpd-broadcast CACHE INTERNAL "") - -set(SOURCE_FILES - # Add source files here +set(BROADCAST_SOURCES connmgr.c dt.c - enroll.c main.c - ) +) + +add_executable(${IPCP_BROADCAST_TARGET} + ${BROADCAST_SOURCES} + ${IPCP_SOURCES} + ${COMMON_SOURCES} +) -add_executable(ipcpd-broadcast ${SOURCE_FILES} ${IPCP_SOURCES} - ${LAYER_CONFIG_PROTO_SRCS}) -target_link_libraries(ipcpd-broadcast LINK_PUBLIC ouroboros-dev) +target_include_directories(${IPCP_BROADCAST_TARGET} PRIVATE ${IPCP_INCLUDE_DIRS}) +target_link_libraries(${IPCP_BROADCAST_TARGET} PRIVATE ouroboros-dev) -include(AddCompileFlags) -if (CMAKE_BUILD_TYPE MATCHES "Debug*") - add_compile_flags(ipcpd-broadcast -DCONFIG_OUROBOROS_DEBUG) -endif () +ouroboros_target_debug_definitions(${IPCP_BROADCAST_TARGET}) -install(TARGETS ipcpd-broadcast RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}) +install(TARGETS ${IPCP_BROADCAST_TARGET} RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}) diff --git a/src/ipcpd/broadcast/connmgr.c b/src/ipcpd/broadcast/connmgr.c index b65f48b0..f297175d 100644 --- a/src/ipcpd/broadcast/connmgr.c +++ b/src/ipcpd/broadcast/connmgr.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Handles connections between components * diff --git a/src/ipcpd/broadcast/dt.c b/src/ipcpd/broadcast/dt.c index 00476027..938c9085 100644 --- a/src/ipcpd/broadcast/dt.c +++ b/src/ipcpd/broadcast/dt.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Forward loop for broadcast * @@ -78,15 +78,16 @@ static int dt_add_nb(int fd) list_for_each(p, &fwd.nbs) { struct nb * el = list_entry(p, struct nb, next); if (el->fd == fd) { - log_dbg("Already know neighbor."); pthread_rwlock_unlock(&fwd.nbs_lock); - return -EPERM; + log_warn("Already know neighbor on fd %d.", fd); + return 0; } } nb = malloc(sizeof(*nb)); if (nb == NULL) { pthread_rwlock_unlock(&fwd.nbs_lock); + log_err("Failed to malloc neighbor struct."); return -ENOMEM; } @@ -96,10 +97,10 @@ static int dt_add_nb(int fd) ++fwd.nbs_len; - log_dbg("Neighbor %d added.", fd); - pthread_rwlock_unlock(&fwd.nbs_lock); + log_dbg("Neighbor %d added.", fd); + return 0; } @@ -124,6 +125,8 @@ static int dt_del_nb(int fd) pthread_rwlock_unlock(&fwd.nbs_lock); + log_err("Neighbor not found on fd %d.", fd); + return -EPERM; } @@ -191,7 +194,7 @@ static void * dt_reader(void * o) while (true) { ret = fevent(fwd.set, fq, NULL); if (ret < 0) { - log_warn("Event error: %d.", ret); + log_warn("Event warning: %d.", ret); continue; } @@ -226,13 +229,13 @@ static void handle_event(void * self, switch (event) { case NOTIFY_DT_CONN_ADD: - if (dt_add_nb(c->flow_info.fd)) - log_dbg("Failed to add neighbor."); + if (dt_add_nb(c->flow_info.fd) < 0) + log_err("Failed to add neighbor."); fset_add(fwd.set, c->flow_info.fd); break; case NOTIFY_DT_CONN_DEL: - if (dt_del_nb(c->flow_info.fd)) - log_dbg("Failed to delete neighbor."); + if (dt_del_nb(c->flow_info.fd) < 0) + log_err("Failed to delete neighbor."); fset_del(fwd.set, c->flow_info.fd); break; default: diff --git a/src/ipcpd/broadcast/dt.h b/src/ipcpd/broadcast/dt.h index 4331bd3e..8d3b83f8 100644 --- a/src/ipcpd/broadcast/dt.h +++ b/src/ipcpd/broadcast/dt.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Forward loop for broadcast * diff --git a/src/ipcpd/broadcast/main.c b/src/ipcpd/broadcast/main.c index 522d1391..151b38c8 100644 --- a/src/ipcpd/broadcast/main.c +++ b/src/ipcpd/broadcast/main.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Broadcast IPC Process * @@ -31,14 +31,15 @@ #define OUROBOROS_PREFIX "broadcast-ipcp" #define THIS_TYPE IPCP_BROADCAST -#include <ouroboros/errno.h> -#include <ouroboros/hash.h> #include <ouroboros/dev.h> +#include <ouroboros/errno.h> #include <ouroboros/ipcp-dev.h> #include <ouroboros/logs.h> #include <ouroboros/notifier.h> +#include <ouroboros/np1_flow.h> +#include <ouroboros/random.h> #include <ouroboros/rib.h> -#include <ouroboros/time_utils.h> +#include <ouroboros/time.h> #include "common/connmgr.h" #include "common/enroll.h" @@ -52,54 +53,33 @@ #include <assert.h> #include <inttypes.h> -struct ipcp ipcpi; - -static int initialize_components(const struct ipcp_config * conf) +static int initialize_components(void) { - ipcpi.layer_name = strdup(conf->layer_info.layer_name); - if (ipcpi.layer_name == NULL) { - log_err("Failed to set layer name."); - goto fail_layer_name; - } - - ipcpi.dir_hash_algo = conf->layer_info.dir_hash_algo; - assert(ipcp_dir_hash_len() != 0); - if (dt_init()) { + if (dt_init() < 0) { log_err("Failed to initialize forwarding component."); - goto fail_dt; + return -1; } ipcp_set_state(IPCP_INIT); return 0; - - fail_dt: - free(ipcpi.layer_name); - fail_layer_name: - return -1; } static void finalize_components(void) { dt_fini(); - - free(ipcpi.layer_name); } static int start_components(void) { - assert(ipcp_get_state() == IPCP_INIT); - - ipcp_set_state(IPCP_OPERATIONAL); - - if (enroll_start()) { + if (enroll_start() < 0) { log_err("Failed to start enrollment."); goto fail_enroll_start; } - if (connmgr_start()) { + if (connmgr_start() < 0) { log_err("Failed to start AP connection manager."); goto fail_connmgr_start; } @@ -115,52 +95,56 @@ static int start_components(void) static void stop_components(void) { - assert(ipcp_get_state() == IPCP_OPERATIONAL || - ipcp_get_state() == IPCP_SHUTDOWN); - connmgr_stop(); enroll_stop(); - - ipcp_set_state(IPCP_INIT); } static int broadcast_ipcp_enroll(const char * dst, struct layer_info * info) { + struct ipcp_config * conf; struct conn conn; + uint8_t id[ENROLL_ID_LEN]; - if (connmgr_alloc(COMPID_ENROLL, dst, NULL, &conn)) { - log_err("Failed to get connection."); - goto fail_er_flow; + if (random_buffer(id, ENROLL_ID_LEN) < 0) { + log_err("Failed to generate enrollment ID."); + goto fail_id; + } + + log_info_id(id, "Requesting enrollment."); + + if (connmgr_alloc(COMPID_ENROLL, dst, NULL, &conn) < 0) { + log_err_id(id, "Failed to get connection."); + goto fail_id; } /* Get boot state from peer. */ - if (enroll_boot(&conn)) { - log_err("Failed to get boot information."); + if (enroll_boot(&conn, id) < 0) { + log_err_id(id, "Failed to get boot information."); goto fail_enroll_boot; } - if (initialize_components(enroll_get_conf())) { - log_err("Failed to initialize IPCP components."); + conf = enroll_get_conf(); + *info = conf->layer_info; + + if (initialize_components() < 0) { + log_err_id(id, "Failed to initialize components."); goto fail_enroll_boot; } - if (start_components()) { - log_err("Failed to start components."); + if (start_components() < 0) { + log_err_id(id, "Failed to start components."); goto fail_start_comp; } - if (enroll_done(&conn, 0)) - log_warn("Failed to confirm enrollment with peer."); - - if (connmgr_dealloc(COMPID_ENROLL, &conn)) - log_warn("Failed to deallocate enrollment flow."); + if (enroll_ack(&conn, id, 0) < 0) + log_err_id(id, "Failed to confirm enrollment."); - log_info("Enrolled with %s.", dst); + if (connmgr_dealloc(COMPID_ENROLL, &conn) < 0) + log_warn_id(id, "Failed to dealloc enrollment flow."); - info->dir_hash_algo = ipcpi.dir_hash_algo; - strcpy(info->layer_name, ipcpi.layer_name); + log_info_id(id, "Enrolled with %s.", dst); return 0; @@ -168,18 +152,19 @@ static int broadcast_ipcp_enroll(const char * dst, finalize_components(); fail_enroll_boot: connmgr_dealloc(COMPID_ENROLL, &conn); - fail_er_flow: + fail_id: return -1; } -static int broadcast_ipcp_bootstrap(const struct ipcp_config * conf) +static int broadcast_ipcp_bootstrap(struct ipcp_config * conf) { assert(conf); assert(conf->type == THIS_TYPE); + assert(conf->layer_info.dir_hash_algo == DIR_HASH_SHA3_256); enroll_bootstrap(conf); - if (initialize_components(conf)) { + if (initialize_components()) { log_err("Failed to init IPCP components."); goto fail_init; } @@ -189,8 +174,6 @@ static int broadcast_ipcp_bootstrap(const struct ipcp_config * conf) goto fail_start; } - log_dbg("Bootstrapped in layer %s.", conf->layer_info.layer_name); - return 0; fail_start: @@ -203,40 +186,63 @@ static int name_check(const uint8_t * dst) { uint8_t * buf; size_t len; - int ret; + int err; + char layer[LAYER_NAME_SIZE + 1]; - len = hash_len(ipcpi.dir_hash_algo); + len = ipcp_dir_hash_len(); buf = malloc(len); - if (buf == NULL) - return -ENOMEM; + if (buf == NULL) { + log_err("Failed to malloc buffer."); + err = -ENOMEM; + goto fail_buf; + } + + err = ipcp_get_layer_name(layer); + if (err < 0) { + log_err("Failed to get layer name."); + goto fail_layer; + } - str_hash(ipcpi.dir_hash_algo, buf, ipcpi.layer_name); + str_hash(HASH_SHA3_256, buf, layer); - ret = memcmp(buf, dst, len); + if (memcmp(buf, dst, len) < 0) { + log_err("Hash mismatch for layer %s.", layer); + err = -ENAME; + goto fail_layer; + } free(buf); - return ret; + return 0; + + fail_layer: + free(buf); + fail_buf: + return err; } static int broadcast_ipcp_join(int fd, - const uint8_t * dst, - qosspec_t qs) + const uint8_t * dst) { + int err; struct conn conn; - - (void) qs; + time_t mpl = IPCP_BROADCAST_MPL; + buffer_t data = BUF_INIT; memset(&conn, 0, sizeof(conn)); conn.flow_info.fd = fd; + conn.flow_info.qs = qos_np1; - if (name_check(dst) != 0) - return -1; + err = name_check(dst); + if (err < 0) { + log_err("Failed to check name."); + return err; + } notifier_event(NOTIFY_DT_CONN_ADD, &conn); - ipcp_flow_alloc_reply(fd, 0, NULL, 0); + ipcp_flow_alloc_reply(fd, 0, mpl, &data); return 0; } @@ -251,12 +257,11 @@ int broadcast_ipcp_dealloc(int fd) notifier_event(NOTIFY_DT_CONN_DEL, &conn); - flow_dealloc(fd); + ipcp_flow_dealloc(fd); return 0; } - static struct ipcp_ops broadcast_ops = { .ipcp_bootstrap = broadcast_ipcp_bootstrap, .ipcp_enroll = broadcast_ipcp_enroll, @@ -275,7 +280,7 @@ int main(int argc, char * argv[]) { if (ipcp_init(argc, argv, &broadcast_ops, THIS_TYPE) < 0) { - log_err("Failed to init IPCP."); + log_err("Failed to initialize IPCP."); goto fail_init; } @@ -294,24 +299,20 @@ int main(int argc, goto fail_enroll_init; } - if (ipcp_boot() < 0) { + if (ipcp_start() < 0) { log_err("Failed to boot IPCP."); - goto fail_boot; - } - - if (ipcp_create_r(0)) { - log_err("Failed to notify IRMd we are initialized."); - ipcp_set_state(IPCP_NULL); - goto fail_create_r; + goto fail_start; } - ipcp_shutdown(); + ipcp_sigwait(); if (ipcp_get_state() == IPCP_SHUTDOWN) { stop_components(); finalize_components(); } + ipcp_stop(); + enroll_fini(); connmgr_fini(); @@ -322,9 +323,7 @@ int main(int argc, exit(EXIT_SUCCESS); - fail_create_r: - ipcp_shutdown(); - fail_boot: + fail_start: enroll_fini(); fail_enroll_init: connmgr_fini(); @@ -333,6 +332,5 @@ int main(int argc, fail_notifier_init: ipcp_fini(); fail_init: - ipcp_create_r(-1); exit(EXIT_FAILURE); } diff --git a/src/ipcpd/common/comp.h b/src/ipcpd/common/comp.h index 95e59b24..f3790d9c 100644 --- a/src/ipcpd/common/comp.h +++ b/src/ipcpd/common/comp.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Components for the unicast/broadcast IPC process * @@ -23,7 +23,7 @@ #ifndef OUROBOROS_IPCPD_COMMON_COMP_H #define OUROBOROS_IPCPD_COMMON_COMP_H -#include <ouroboros/cacep.h> +#include <ouroboros/cep.h> #define DST_MAX_STRLEN 64 diff --git a/src/ipcpd/common/connmgr.c b/src/ipcpd/common/connmgr.c index 53a66992..1bb8c932 100644 --- a/src/ipcpd/common/connmgr.c +++ b/src/ipcpd/common/connmgr.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Handles connections between components * @@ -22,9 +22,10 @@ #define OUROBOROS_PREFIX "connection-manager" +#include <ouroboros/cep.h> #include <ouroboros/dev.h> -#include <ouroboros/cacep.h> #include <ouroboros/errno.h> +#include <ouroboros/fccntl.h> #include <ouroboros/list.h> #include <ouroboros/logs.h> #include <ouroboros/notifier.h> @@ -33,15 +34,9 @@ #include "connmgr.h" #include "ipcp.h" -#include <string.h> -#include <stdlib.h> #include <assert.h> - -enum connmgr_state { - CONNMGR_NULL = 0, - CONNMGR_INIT, - CONNMGR_RUNNING -}; +#include <stdlib.h> +#include <string.h> struct conn_el { struct list_head next; @@ -60,7 +55,6 @@ struct comp { struct { struct comp comps[COMPID_MAX]; - enum connmgr_state state; pthread_t acceptor; } connmgr; @@ -128,10 +122,12 @@ static int add_comp_conn(enum comp_id id, static void * flow_acceptor(void * o) { - int fd; - qosspec_t qs; - struct conn_info rcv_info; - struct conn_info fail_info; + int fd; + qosspec_t qs; + struct conn_info rcv_info; + struct conn_info fail_info; + struct timespec timeo = TIMESPEC_INIT_MS(CONNMGR_RCV_TIMEOUT); + int err; (void) o; @@ -143,38 +139,50 @@ static void * flow_acceptor(void * o) fd = flow_accept(&qs, NULL); if (fd < 0) { if (fd != -EIRMD) - log_warn("Flow accept failed: %d", fd); + log_err("Flow accept failed: %d", fd); continue; } - if (cacep_rcv(fd, &rcv_info)) { - log_dbg("Error establishing application connection."); + log_info("Handling incoming flow %d.",fd); + + fccntl(fd, FLOWSRCVTIMEO, &timeo); + + err = cep_rcv(fd, &rcv_info); + if (err < 0) { + log_err("Error receiving OCEP info: %d.", err); flow_dealloc(fd); continue; } + log_info("Request to connect to %s.", rcv_info.comp_name); + id = get_id_by_name(rcv_info.comp_name); if (id < 0) { - log_dbg("Connection request for unknown component %s.", + log_err("Connection request for unknown component %s.", rcv_info.comp_name); - cacep_snd(fd, &fail_info); + cep_snd(fd, &fail_info); flow_dealloc(fd); continue; } - assert(id < COMPID_MAX); - - if (cacep_snd(fd, &connmgr.comps[id].info)) { - log_dbg("Failed to respond to request."); + err = cep_snd(fd, &connmgr.comps[id].info); + if (err < 0) { + log_err("Failed responding to OCEP request: %d.", err); flow_dealloc(fd); continue; } - if (add_comp_conn(id, fd, qs, &rcv_info)) { - log_dbg("Failed to add new connection."); + fccntl(fd, FLOWSRCVTIMEO, NULL); + + err = add_comp_conn(id, fd, qs, &rcv_info); + if (err < 0) { + log_err("Failed to add new connection: %d.", err); flow_dealloc(fd); continue; } + + log_info("Finished handling incoming flow %d for %s.", + fd, rcv_info.comp_name); } return (void *) 0; @@ -213,10 +221,10 @@ static void handle_event(void * self, int connmgr_init(void) { - connmgr.state = CONNMGR_INIT; - - if (notifier_reg(handle_event, NULL)) + if (notifier_reg(handle_event, NULL)) { + log_err("Failed to register notifier."); return -1; + } return 0; } @@ -225,29 +233,26 @@ void connmgr_fini(void) { int i; - notifier_unreg(handle_event); - - if (connmgr.state == CONNMGR_RUNNING) - pthread_join(connmgr.acceptor, NULL); - for (i = 0; i < COMPID_MAX; ++i) connmgr_comp_fini(i); + + notifier_unreg(handle_event); } int connmgr_start(void) { - if (pthread_create(&connmgr.acceptor, NULL, flow_acceptor, NULL)) + if (pthread_create(&connmgr.acceptor, NULL, flow_acceptor, NULL)) { + log_err("Failed to create pthread: %s.", strerror(errno)); return -1; - - connmgr.state = CONNMGR_RUNNING; + } return 0; } void connmgr_stop(void) { - if (connmgr.state == CONNMGR_RUNNING) - pthread_cancel(connmgr.acceptor); + pthread_cancel(connmgr.acceptor); + pthread_join(connmgr.acceptor, NULL); } int connmgr_comp_init(enum comp_id id, @@ -259,12 +264,14 @@ int connmgr_comp_init(enum comp_id id, comp = connmgr.comps + id; - if (pthread_mutex_init(&comp->lock, NULL)) - return -1; + if (pthread_mutex_init(&comp->lock, NULL)) { + log_err("Failed to initialize mutex: %s.", strerror(errno)); + goto fail_mutex; + } if (pthread_cond_init(&comp->cond, NULL)) { - pthread_mutex_destroy(&comp->lock); - return -1; + log_err("Failed to initialize condvar: %s.", strerror(errno)); + goto fail_cond; } list_head_init(&comp->conns); @@ -273,6 +280,11 @@ int connmgr_comp_init(enum comp_id id, memcpy(&connmgr.comps[id].info, info, sizeof(connmgr.comps[id].info)); return 0; + + fail_cond: + pthread_mutex_destroy(&comp->lock); + fail_mutex: + return -1; } void connmgr_comp_fini(enum comp_id id) @@ -316,26 +328,32 @@ int connmgr_ipcp_connect(const char * dst, { struct conn_el * ce; int id; + int ret; assert(dst); assert(component); ce = malloc(sizeof(*ce)); if (ce == NULL) { - log_dbg("Out of memory."); - return -1; + log_err("Out of memory."); + goto fail_malloc; } id = get_id_by_name(component); if (id < 0) { - log_dbg("No such component: %s", component); - free(ce); - return -1; + log_err("No such component: %s", component); + goto fail_id; } - if (connmgr_alloc(id, dst, &qs, &ce->conn)) { - free(ce); - return -1; + pthread_cleanup_push(free, ce); + + ret = connmgr_alloc(id, dst, &qs, &ce->conn); + + pthread_cleanup_pop(false); + + if (ret < 0) { + log_err("Failed to allocate flow."); + goto fail_id; } if (strlen(dst) > DST_MAX_STRLEN) { @@ -353,6 +371,11 @@ int connmgr_ipcp_connect(const char * dst, pthread_mutex_unlock(&connmgr.comps[id].lock); return 0; + + fail_id: + free(ce); + fail_malloc: + return -1; } int connmgr_ipcp_disconnect(const char * dst, @@ -366,8 +389,10 @@ int connmgr_ipcp_disconnect(const char * dst, assert(component); id = get_id_by_name(component); - if (id < 0) + if (id < 0) { + log_err("No such component: %s.", component); return -1; + } pthread_mutex_lock(&connmgr.comps[id].lock); @@ -393,62 +418,63 @@ int connmgr_alloc(enum comp_id id, qosspec_t * qs, struct conn * conn) { + struct comp * comp; + int fd; + struct timespec timeo = TIMESPEC_INIT_MS(CONNMGR_RCV_TIMEOUT); + assert(id >= 0 && id < COMPID_MAX); assert(dst); - conn->flow_info.fd = flow_alloc(dst, qs, NULL); - if (conn->flow_info.fd < 0) { - log_dbg("Failed to allocate flow to %s.", dst); - return -1; + comp = connmgr.comps + id; + + fd = flow_alloc(dst, qs, NULL); + if (fd < 0) { + log_err("Failed to allocate flow to %s.", dst); + goto fail_alloc; } + conn->flow_info.fd = fd; + if (qs != NULL) conn->flow_info.qs = *qs; else memset(&conn->flow_info.qs, 0, sizeof(conn->flow_info.qs)); - log_dbg("Sending cacep info for protocol %s to fd %d.", - connmgr.comps[id].info.protocol, conn->flow_info.fd); + log_dbg("Sending OCEP info for protocol %s to fd %d.", + comp->info.protocol, conn->flow_info.fd); - if (cacep_snd(conn->flow_info.fd, &connmgr.comps[id].info)) { - log_dbg("Failed to create application connection."); - flow_dealloc(conn->flow_info.fd); - return -1; + fccntl(fd, FLOWSRCVTIMEO, &timeo); + + if (cep_snd(fd, &comp->info)) { + log_err("Failed to send OCEP info."); + goto fail_cep; } - if (cacep_rcv(conn->flow_info.fd, &conn->conn_info)) { - log_dbg("Failed to connect to application."); - flow_dealloc(conn->flow_info.fd); - return -1; + if (cep_rcv(fd, &conn->conn_info)) { + log_err("Failed to receive OCEP info."); + goto fail_cep; } - if (strcmp(connmgr.comps[id].info.protocol, conn->conn_info.protocol)) { - log_dbg("Unknown protocol (requested %s, got %s).", - connmgr.comps[id].info.protocol, - conn->conn_info.protocol); - flow_dealloc(conn->flow_info.fd); - return -1; + if (strcmp(comp->info.protocol, conn->conn_info.protocol)) { + log_err("Unknown protocol (requested %s, got %s).", + comp->info.protocol, conn->conn_info.protocol); + goto fail_cep; } - if (connmgr.comps[id].info.pref_version != - conn->conn_info.pref_version) { - log_dbg("Unknown protocol version."); - flow_dealloc(conn->flow_info.fd); - return -1; + if (comp->info.pref_version != conn->conn_info.pref_version) { + log_err("Unknown protocol version %d.", + conn->conn_info.pref_version); + goto fail_cep; } - if (connmgr.comps[id].info.pref_syntax != conn->conn_info.pref_syntax) { - log_dbg("Unknown protocol syntax."); - flow_dealloc(conn->flow_info.fd); - return -1; + if (comp->info.pref_syntax != conn->conn_info.pref_syntax) { + log_err("Unknown protocol syntax."); + goto fail_cep; } switch (id) { case COMPID_DT: notifier_event(NOTIFY_DT_CONN_ADD, conn); -#if defined(BUILD_IPCP_UNICAST) && defined(IPCP_CONN_WAIT_DIR) - dir_wait_running(); -#endif break; case COMPID_MGMT: notifier_event(NOTIFY_MGMT_CONN_ADD, conn); @@ -458,6 +484,11 @@ int connmgr_alloc(enum comp_id id, } return 0; + + fail_cep: + flow_dealloc(conn->flow_info.fd); + fail_alloc: + return -1; } int connmgr_dealloc(enum comp_id id, @@ -467,7 +498,7 @@ int connmgr_dealloc(enum comp_id id, case COMPID_DT: notifier_event(NOTIFY_DT_CONN_DEL, conn); break; -#if defined(BUILD_IPCP_UNICAST) && defined(IPCP_CONN_WAIT_DIR) +#if defined(BUILD_IPCP_UNICAST) case COMPID_MGMT: notifier_event(NOTIFY_MGMT_CONN_DEL, conn); break; @@ -503,6 +534,7 @@ int connmgr_wait(enum comp_id id, el = list_first_entry((&comp->pending), struct conn_el, next); if (el == NULL) { pthread_mutex_unlock(&comp->lock); + log_err("Failed to get connection element."); return -1; } diff --git a/src/ipcpd/common/connmgr.h b/src/ipcpd/common/connmgr.h index 5f7b557f..0710dbbf 100644 --- a/src/ipcpd/common/connmgr.h +++ b/src/ipcpd/common/connmgr.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Handles the different AP connections * @@ -23,7 +23,7 @@ #ifndef OUROBOROS_IPCPD_COMMON_CONNMGR_H #define OUROBOROS_IPCPD_COMMON_CONNMGR_H -#include <ouroboros/cacep.h> +#include <ouroboros/cep.h> #include <ouroboros/qos.h> #include "comp.h" diff --git a/src/ipcpd/common/enroll.c b/src/ipcpd/common/enroll.c index 090067d8..8e5384a5 100644 --- a/src/ipcpd/common/enroll.c +++ b/src/ipcpd/common/enroll.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Enrollment Task * @@ -28,13 +28,11 @@ #define OUROBOROS_PREFIX "enrollment" -#include <ouroboros/endian.h> -#include <ouroboros/errno.h> -#include <ouroboros/time_utils.h> #include <ouroboros/dev.h> -#include <ouroboros/logs.h> #include <ouroboros/errno.h> -#include <ouroboros/sockets.h> +#include <ouroboros/logs.h> +#include <ouroboros/serdes-oep.h> +#include <ouroboros/time.h> #include "common/connmgr.h" #include "common/enroll.h" @@ -45,281 +43,232 @@ #include <string.h> #include <pthread.h> -#include "ipcp_config.pb-c.h" -typedef EnrollMsg enroll_msg_t; +#ifdef __APPLE__ +#define llabs labs +#endif #define ENROLL_COMP "Enrollment" #define ENROLL_PROTO "OEP" /* Ouroboros enrollment protocol */ #define ENROLL_WARN_TIME_OFFSET 20 #define ENROLL_BUF_LEN 1024 -enum enroll_state { - ENROLL_NULL = 0, - ENROLL_INIT, - ENROLL_RUNNING -}; struct { struct ipcp_config conf; - enum enroll_state state; + pthread_t listener; } enroll; -static int send_rcv_enroll_msg(int fd) +static void * enroll_handle(void * o) { - enroll_msg_t req = ENROLL_MSG__INIT; - enroll_msg_t * reply; - uint8_t buf[ENROLL_BUF_LEN]; - ssize_t len; - ssize_t delta_t; - struct timespec t0; - struct timespec rtt; - - req.code = ENROLL_CODE__ENROLL_REQ; - - len = enroll_msg__get_packed_size(&req); - if (len < 0) { - log_dbg("Failed pack request message."); - return -1; - } - - enroll_msg__pack(&req, buf); - - clock_gettime(CLOCK_REALTIME, &t0); + struct enroll_req req; + struct enroll_resp resp; + struct enroll_ack ack; + struct conn conn; + uint8_t __buf[ENROLL_BUF_LEN]; + buffer_t buf; + ssize_t len; - if (flow_write(fd, buf, len) < 0) { - log_dbg("Failed to send request message."); - return -1; - } - - len = flow_read(fd, buf, ENROLL_BUF_LEN); - if (len < 0) { - log_dbg("No enrollment reply received."); - return -1; - } - - log_dbg("Received enrollment info (%zd bytes).", len); + (void) o; - reply = enroll_msg__unpack(NULL, len, buf); - if (reply == NULL) { - log_dbg("No enrollment response."); - return -1; - } + buf.data = __buf; + buf.len = sizeof(__buf); - if (reply->code != ENROLL_CODE__ENROLL_BOOT) { - log_dbg("Failed to unpack enrollment response."); - enroll_msg__free_unpacked(reply, NULL); - return -1; - } + resp.response = 0; + resp.conf = enroll.conf; - if (!(reply->has_t_sec && reply->has_t_nsec)) { - log_dbg("No time in response message."); - enroll_msg__free_unpacked(reply, NULL); - return -1; - } - - clock_gettime(CLOCK_REALTIME, &rtt); + while (true) { + buffer_t msg; + int fd; - delta_t = ts_diff_ms(&t0, &rtt); + if (connmgr_wait(COMPID_ENROLL, &conn)) { + log_err("Failed to get next connection."); + continue; + } - rtt.tv_sec = reply->t_sec; - rtt.tv_nsec = reply->t_nsec; - - if (labs(ts_diff_ms(&t0, &rtt)) - delta_t > ENROLL_WARN_TIME_OFFSET) - log_warn("Clock offset above threshold."); - - strcpy(enroll.conf.layer_info.layer_name, - reply->conf->layer_info->layer_name); - enroll.conf.type = reply->conf->ipcp_type; -#ifdef BUILD_IPCP_UNICAST - enroll.conf.addr_size = reply->conf->addr_size; - enroll.conf.eid_size = reply->conf->eid_size; - enroll.conf.max_ttl = reply->conf->max_ttl; - enroll.conf.addr_auth_type = reply->conf->addr_auth_type; - enroll.conf.routing_type = reply->conf->routing_type; - enroll.conf.cong_avoid = reply->conf->cong_avoid; -#endif - enroll.conf.layer_info.dir_hash_algo - = reply->conf->layer_info->dir_hash_algo; - enroll_msg__free_unpacked(reply, NULL); + fd = conn.flow_info.fd; - return 0; -} + log_info("Incoming enrollment connection on flow %d.", fd); -static ssize_t enroll_pack(uint8_t ** buf) -{ - enroll_msg_t msg = ENROLL_MSG__INIT; - ipcp_config_msg_t config = IPCP_CONFIG_MSG__INIT; - layer_info_msg_t layer_info = LAYER_INFO_MSG__INIT; - struct timespec now; - ssize_t len; + len = flow_read(fd, buf.data, buf.len); + if (len < 0) { + log_err("Failed to read from flow %d.", fd); + goto finish_flow; + } - clock_gettime(CLOCK_REALTIME, &now); - - msg.code = ENROLL_CODE__ENROLL_BOOT; - msg.has_t_sec = true; - msg.t_sec = now.tv_sec; - msg.has_t_nsec = true; - msg.t_nsec = now.tv_nsec; - msg.conf = &config; - - config.ipcp_type = enroll.conf.type; -#ifdef BUILD_IPCP_UNICAST - config.has_addr_size = true; - config.addr_size = enroll.conf.addr_size; - config.has_eid_size = true; - config.eid_size = enroll.conf.eid_size; - config.has_max_ttl = true; - config.max_ttl = enroll.conf.max_ttl; - config.has_addr_auth_type = true; - config.addr_auth_type = enroll.conf.addr_auth_type; - config.has_routing_type = true; - config.routing_type = enroll.conf.routing_type; - config.has_cong_avoid = true; - config.cong_avoid = enroll.conf.cong_avoid; -#endif - config.layer_info = &layer_info; + msg.data = buf.data; + msg.len = (size_t) len; - layer_info.layer_name = (char *) enroll.conf.layer_info.layer_name; - layer_info.dir_hash_algo = enroll.conf.layer_info.dir_hash_algo; + if (enroll_req_des(&req, msg) < 0) { + log_err("Failed to unpack request message."); + goto finish_flow; + } - len = enroll_msg__get_packed_size(&msg); + log_info_id(req.id, "Handling incoming enrollment."); - *buf = malloc(len); - if (*buf == NULL) - return -1; + ack.result = -100; - enroll_msg__pack(&msg, *buf); + clock_gettime(CLOCK_REALTIME, &resp.t); - return len; -} + memcpy(resp.id, req.id, ENROLL_ID_LEN); -static void * enroll_handle(void * o) -{ - struct conn conn; - uint8_t buf[ENROLL_BUF_LEN]; - uint8_t * reply; - ssize_t len; - enroll_msg_t * msg; + len = enroll_resp_ser(&resp, buf); + if (len < 0) { + log_err_id(req.id, "Failed to pack reply."); + goto finish_enroll; + } - (void) o; + log_dbg_id(req.id, "Sending enrollment info (%zd bytes).", len); - while (true) { - if (connmgr_wait(COMPID_ENROLL, &conn)) { - log_err("Failed to get next connection."); - continue; + if (flow_write(conn.flow_info.fd, buf.data, len) < 0) { + log_err_id(req.id, "Failed te send response."); + goto finish_enroll; } - len = flow_read(conn.flow_info.fd, buf, ENROLL_BUF_LEN); + len = flow_read(conn.flow_info.fd, buf.data, buf.len); if (len < 0) { - log_err("Failed to read from flow."); - connmgr_dealloc(COMPID_ENROLL, &conn); - continue; + log_err_id(req.id, "Failed to read from flow."); + goto finish_enroll; } - msg = enroll_msg__unpack(NULL, len, buf); - if (msg == NULL) { - log_err("Failed to unpack message."); - connmgr_dealloc(COMPID_ENROLL, &conn); - continue; - } + msg.data = buf.data; + msg.len = (size_t) len; - if (msg->code != ENROLL_CODE__ENROLL_REQ) { - log_err("Wrong message type."); - connmgr_dealloc(COMPID_ENROLL, &conn); - enroll_msg__free_unpacked(msg, NULL); - continue; + if (enroll_ack_des(&ack, msg) < 0) { + log_err_id(req.id, "Failed to unpack ack."); + goto finish_enroll; } - log_dbg("Enrolling a new neighbor."); - - enroll_msg__free_unpacked(msg, NULL); - - len = enroll_pack(&reply); - if (reply == NULL) { - log_err("Failed to pack enrollment message."); - connmgr_dealloc(COMPID_ENROLL, &conn); - continue; + if (memcmp(req.id, ack.id, ENROLL_ID_LEN) != 0) + log_warn_id(req.id, "Enrollment ID mismatch."); + + finish_enroll: + switch(ack.result) { + case 0: + log_info_id(req.id, "Enrollment completed."); + break; + case -100: + log_warn_id(req.id, "Enrollment failed."); + break; + default: + log_warn_id(req.id, "Enrollment failed at remote."); } + finish_flow: + connmgr_dealloc(COMPID_ENROLL, &conn); - log_dbg("Sending enrollment info (%zd bytes).", len); + log_info("Enrollment flow %d closed.", fd); + } - if (flow_write(conn.flow_info.fd, reply, len) < 0) { - log_err("Failed respond to enrollment request."); - connmgr_dealloc(COMPID_ENROLL, &conn); - free(reply); - continue; - } + return 0; +} - free(reply); +int enroll_boot(struct conn * conn, + const uint8_t * id) +{ + uint8_t __buf[ENROLL_BUF_LEN]; + buffer_t buf; + buffer_t msg; + ssize_t len; + ssize_t delta_t; + struct timespec t0; + struct timespec rtt; + int fd; + int ret; + struct enroll_req req; + struct enroll_resp resp; + + fd = conn->flow_info.fd; + + buf.data = __buf; + buf.len = sizeof(__buf); + + memcpy(req.id, id, ENROLL_ID_LEN); + + len = enroll_req_ser(&req, buf); + if (len < 0) { + log_err_id(id, "Failed to pack request message."); + return -1; + } - len = flow_read(conn.flow_info.fd, buf, ENROLL_BUF_LEN); - if (len < 0) { - log_err("Failed to read from flow."); - connmgr_dealloc(COMPID_ENROLL, &conn); - continue; - } + clock_gettime(CLOCK_REALTIME, &t0); - msg = enroll_msg__unpack(NULL, len, buf); - if (msg == NULL) { - log_err("Failed to unpack message."); - connmgr_dealloc(COMPID_ENROLL, &conn); - continue; - } + if (flow_write(fd, buf.data, len) < 0) { + log_err_id(id, "Failed to send request message."); + return -1; + } - if (msg->code != ENROLL_CODE__ENROLL_DONE || !msg->has_result) { - log_err("Wrong message type."); - enroll_msg__free_unpacked(msg, NULL); - connmgr_dealloc(COMPID_ENROLL, &conn); - continue; - } + len = flow_read(fd, buf.data, buf.len); + if (len < 0) { + log_err_id(id, "No reply received."); + return -1; + } - if (msg->result == 0) - log_dbg("Neighbor enrollment successful."); - else - log_dbg("Neigbor reported failed enrollment."); + log_dbg_id(id, "Received configuration info (%zd bytes).", len); - enroll_msg__free_unpacked(msg, NULL); + msg.data = buf.data; + msg.len = len; - connmgr_dealloc(COMPID_ENROLL, &conn); + ret = enroll_resp_des(&resp, msg); + if (ret < 0) { + log_err_id(id, "Failed to unpack response message."); + return -1; } - return 0; -} + if (memcmp(resp.id, id, ENROLL_ID_LEN) != 0) { + log_err_id(id, "Enrollment ID mismatch."); + return -1; + } -int enroll_boot(struct conn * conn) -{ - log_dbg("Getting boot information."); + if (resp.response < 0) { + log_warn_id(id, "Remote denied request: %d.", resp.response); + return -1; + } - if (send_rcv_enroll_msg(conn->flow_info.fd)) { - log_err("Failed to enroll."); + if (resp.conf.type != ipcp_get_type()) { + log_err_id(id, "Wrong type in enrollment response %d (%d).", + resp.conf.type, ipcp_get_type()); return -1; } + enroll.conf = resp.conf; + + clock_gettime(CLOCK_REALTIME, &rtt); + + delta_t = ts_diff_ms(&t0, &rtt); + + rtt.tv_sec = resp.t.tv_sec; + rtt.tv_nsec = resp.t.tv_nsec; + + if (llabs(ts_diff_ms(&t0, &rtt)) - delta_t > ENROLL_WARN_TIME_OFFSET) + log_warn_id(id, "Clock offset above threshold."); + return 0; } -int enroll_done(struct conn * conn, - int result) +int enroll_ack(struct conn * conn, + const uint8_t * id, + const int result) { - enroll_msg_t msg = ENROLL_MSG__INIT; - uint8_t buf[ENROLL_BUF_LEN]; - ssize_t len; + struct enroll_ack ack; + uint8_t __buf[ENROLL_BUF_LEN]; + buffer_t buf; + ssize_t len; - msg.code = ENROLL_CODE__ENROLL_DONE; - msg.has_result = true; - msg.result = result; + buf.data = __buf; + buf.len = sizeof(__buf); - len = enroll_msg__get_packed_size(&msg); + ack.result = result; + + memcpy(ack.id, id, ENROLL_ID_LEN); + + len = enroll_ack_ser(&ack, buf); if (len < 0) { - log_dbg("Failed pack request message."); + log_err_id(id, "Failed to pack acknowledgement."); return -1; } - enroll_msg__pack(&msg, buf); - - if (flow_write(conn->flow_info.fd, buf, len) < 0) { - log_dbg("Failed to send acknowledgment."); + if (flow_write(conn->flow_info.fd, buf.data, len) < 0) { + log_err_id(id, "Failed to send acknowledgment."); return -1; } @@ -355,16 +304,11 @@ int enroll_init(void) return -1; } - enroll.state = ENROLL_INIT; - return 0; } void enroll_fini(void) { - if (enroll.state == ENROLL_RUNNING) - pthread_join(enroll.listener, NULL); - connmgr_comp_fini(COMPID_ENROLL); } @@ -373,13 +317,11 @@ int enroll_start(void) if (pthread_create(&enroll.listener, NULL, enroll_handle, NULL)) return -1; - enroll.state = ENROLL_RUNNING; - return 0; } void enroll_stop(void) { - if (enroll.state == ENROLL_RUNNING) - pthread_cancel(enroll.listener); + pthread_cancel(enroll.listener); + pthread_join(enroll.listener, NULL); } diff --git a/src/ipcpd/common/enroll.h b/src/ipcpd/common/enroll.h index fb866416..f26c31a3 100644 --- a/src/ipcpd/common/enroll.h +++ b/src/ipcpd/common/enroll.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Enrollment Task * @@ -37,10 +37,12 @@ void enroll_stop(void); void enroll_bootstrap(const struct ipcp_config * conf); -int enroll_boot(struct conn * conn); +int enroll_boot(struct conn * conn, + const uint8_t * id); -int enroll_done(struct conn * conn, - int result); +int enroll_ack(struct conn * conn, + const uint8_t * id, + const int result); struct ipcp_config * enroll_get_conf(void); diff --git a/src/ipcpd/config.h.in b/src/ipcpd/config.h.in index 0bf3ad69..806de2b6 100644 --- a/src/ipcpd/config.h.in +++ b/src/ipcpd/config.h.in @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * IPC process configuration * @@ -29,11 +29,9 @@ #define SOCKET_TIMEOUT @SOCKET_TIMEOUT@ #define CONNECT_TIMEOUT @CONNECT_TIMEOUT@ -#define SHM_BUFFER_SIZE @SHM_BUFFER_SIZE@ -#define SHM_RDRB_BLOCK_SIZE @SHM_RDRB_BLOCK_SIZE@ +#define SSM_POOL_BLOCK_SIZE @SSM_POOL_BLOCK_SIZE@ #define DU_BUFF_HEADSPACE @DU_BUFF_HEADSPACE@ #define DU_BUFF_TAILSPACE @DU_BUFF_TAILSPACE@ -#cmakedefine SHM_RDRB_MULTI_BLOCK #define IPCP_MIN_THREADS @IPCP_MIN_THREADS@ #define IPCP_ADD_THREADS @IPCP_ADD_THREADS@ @@ -47,11 +45,18 @@ #define QOS_PRIO_VOICE @IPCP_QOS_CUBE_VOICE_PRIO@ #define IPCP_SCHED_THR_MUL @IPCP_SCHED_THR_MUL@ #define PFT_SIZE @PFT_SIZE@ -#define DHT_ENROLL_SLACK @DHT_ENROLL_SLACK@ +#define IPCP_UNICAST_MPL @IPCP_UNICAST_MPL@ +#define CONNMGR_RCV_TIMEOUT @CONNMGR_RCV_TIMEOUT@ -#cmakedefine IPCP_CONN_WAIT_DIR #cmakedefine DISABLE_CORE_LOCK +#cmakedefine BUILD_CONTAINER #cmakedefine IPCP_FLOW_STATS +#cmakedefine IPCP_DEBUG_LOCAL +#ifdef CONFIG_OUROBOROS_DEBUG +#cmakedefine DEBUG_PROTO_DHT +#cmakedefine DEBUG_PROTO_OEP +#cmakedefine DEBUG_PROTO_LS +#endif /* udp */ #cmakedefine HAVE_DDNS @@ -59,12 +64,22 @@ #define NSLOOKUP_EXEC "@NSLOOKUP_EXECUTABLE@" #define IPCP_UDP_RD_THR @IPCP_UDP_RD_THR@ #define IPCP_UDP_WR_THR @IPCP_UDP_WR_THR@ +#define IPCP_UDP_MPL @IPCP_UDP_MPL@ -/* eth-llc */ +/* eth */ #cmakedefine HAVE_NETMAP #cmakedefine HAVE_BPF #cmakedefine HAVE_RAW_SOCKETS #cmakedefine IPCP_ETH_QDISC_BYPASS -#define IPCP_ETH_RD_THR @IPCP_ETH_RD_THR@ -#define IPCP_ETH_WR_THR @IPCP_ETH_WR_THR@ -#define IPCP_ETH_LO_MTU @IPCP_ETH_LO_MTU@ +#define IPCP_ETH_RD_THR @IPCP_ETH_RD_THR@ +#define IPCP_ETH_WR_THR @IPCP_ETH_WR_THR@ +#define IPCP_ETH_LO_MTU @IPCP_ETH_LO_MTU@ +#define IPCP_ETH_MGMT_FRAME_SIZE @IPCP_ETH_MGMT_FRAME_SIZE@ +#define IPCP_ETH_MPL @IPCP_ETH_MPL@ + +/* local */ +#define IPCP_LOCAL_MPL @IPCP_LOCAL_MPL@ + +/* broadcast */ +/* local */ +#define IPCP_BROADCAST_MPL @IPCP_BROADCAST_MPL@ diff --git a/src/ipcpd/eth/CMakeLists.txt b/src/ipcpd/eth/CMakeLists.txt index d7105b4f..e6cc8224 100644 --- a/src/ipcpd/eth/CMakeLists.txt +++ b/src/ipcpd/eth/CMakeLists.txt @@ -1,132 +1,24 @@ -get_filename_component(CURRENT_SOURCE_PARENT_DIR - ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) -get_filename_component(CURRENT_BINARY_PARENT_DIR - ${CMAKE_CURRENT_BINARY_DIR} DIRECTORY) - -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) - -include_directories(${CURRENT_SOURCE_PARENT_DIR}) -include_directories(${CURRENT_BINARY_PARENT_DIR}) - -include_directories(${CMAKE_SOURCE_DIR}/include) -include_directories(${CMAKE_BINARY_DIR}/include) - -find_path(NETMAP_C_INCLUDE_DIR - net/netmap_user.h - HINTS /usr/include /usr/local/include) - -mark_as_advanced(NETMAP_C_INCLUDE_DIR) - -# Check for raw sockets -if (CMAKE_SYSTEM_NAME STREQUAL "Linux") - set(DISABLE_RAW_SOCKETS FALSE CACHE BOOL - "Disable raw socket support for Ethernet IPCPs") - if (NOT DISABLE_RAW_SOCKETS) - message(STATUS "Raw socket support for Ethernet IPCPs enabled") - set(HAVE_RAW_SOCKETS TRUE PARENT_SCOPE) - set(HAVE_RAW_SOCKETS TRUE) - set(HAVE_ETH TRUE) - else () - message(STATUS "Raw socket support for Ethernet IPCPs disabled by user") - unset(HAVE_RAW_SOCKETS PARENT_SCOPE) - unset(HAVE_RAW_SOCKETS) - endif () -endif () - -# Check for BPF -if (NOT CMAKE_SYSTEM_NAME STREQUAL "Linux") - find_path(BPF_C_INCLUDE_DIR - net/bpf.h - HINTS /usr/include /usr/local/include) - - mark_as_advanced(BPF_C_INCLUDE_DIR) - - if (BPF_C_INCLUDE_DIR) - set(DISABLE_BPF FALSE CACHE BOOL - "Disable Berkeley Packet Filter support for Ethernet IPCPs") - if (NOT DISABLE_BPF) - message(STATUS "Berkeley Packet Filter support " - "for Ethernet IPCPs enabled") - set(HAVE_BPF TRUE PARENT_SCOPE) - set(HAVE_BPF TRUE) - set(HAVE_ETH TRUE) - else () - message(STATUS "Berkeley Packet Filter support " - "for Ethernet IPCPs disabled by user") - unset(HAVE_BPF PARENT_SCOPE) - unset(HAVE_BPF) - endif () - endif () -endif () - -# Check for netmap exclusively -if (NOT HAVE_RAW_SOCKETS AND NOT HAVE_BPF AND NETMAP_C_INCLUDE_DIR) - set(DISABLE_NETMAP FALSE CACHE BOOL - "Disable netmap support for ETH IPCPs") - if (NOT DISABLE_NETMAP) - message(STATUS "Netmap support for Ethernet IPCPs enabled") - set(HAVE_NETMAP TRUE PARENT_SCOPE) - set(HAVE_ETH TRUE) - else () - message(STATUS "Netmap support for Ethernet IPCPs disabled by user") - unset(HAVE_NETMAP PARENT_SCOPE) - endif () -endif () - -if (HAVE_ETH) - message(STATUS "Supported raw packet API found, building eth-llc and eth-dix") - - set(IPCP_ETH_RD_THR 1 CACHE STRING - "Number of reader threads in Ethernet IPCP") - set(IPCP_ETH_WR_THR 1 CACHE STRING - "Number of writer threads in Ethernet IPCP") - set(IPCP_ETH_QDISC_BYPASS false CACHE BOOL - "Bypass the Qdisc in the kernel when using raw sockets") - set(IPCP_ETH_LO_MTU 1500 CACHE STRING - "Restrict Ethernet MTU over loopback interfaces") - - set(ETH_LLC_SOURCES - # Add source files here - ${CMAKE_CURRENT_SOURCE_DIR}/llc.c - ) - - set(ETH_DIX_SOURCES - # Add source files here - ${CMAKE_CURRENT_SOURCE_DIR}/dix.c - ) - - set(IPCP_ETH_LLC_TARGET ipcpd-eth-llc CACHE INTERNAL "") - set(IPCP_ETH_DIX_TARGET ipcpd-eth-dix CACHE INTERNAL "") - - add_executable(ipcpd-eth-llc ${ETH_LLC_SOURCES} ${IPCP_SOURCES}) - add_executable(ipcpd-eth-dix ${ETH_DIX_SOURCES} ${IPCP_SOURCES}) - - if (HAVE_BPF AND NOT APPLE) - target_include_directories(ipcpd-eth-llc PUBLIC ${BPF_C_INCLUDE_DIR}) - target_include_directories(ipcpd-eth-dix PUBLIC ${BPF_C_INCLUDE_DIR}) - endif () - - if (HAVE_NETMAP AND NOT APPLE) - set_target_properties(ipcpd-eth-llc PROPERTIES - COMPILE_FLAGS "${CMAKE_C_FLAGS} -std=c99") - set_target_properties(ipcpd-eth-dix PROPERTIES - COMPILE_FLAGS "${CMAKE_C_FLAGS} -std=c99") - target_include_directories(ipcpd-eth-llc PUBLIC - ${NETMAP_C_INCLUDE_DIR}) - target_include_directories(ipcpd-eth-dix PUBLIC - ${NETMAP_C_INCLUDE_DIR}) - endif () - - target_link_libraries(ipcpd-eth-llc LINK_PUBLIC ouroboros-dev) - target_link_libraries(ipcpd-eth-dix LINK_PUBLIC ouroboros-dev) - - include(AddCompileFlags) - if (CMAKE_BUILD_TYPE MATCHES "Debug*") - add_compile_flags(ipcpd-eth-llc -DCONFIG_OUROBOROS_DEBUG) - add_compile_flags(ipcpd-eth-dix -DCONFIG_OUROBOROS_DEBUG) - endif () - - install(TARGETS ipcpd-eth-llc ipcpd-eth-dix RUNTIME DESTINATION - ${CMAKE_INSTALL_SBINDIR}) -endif () +# Ethernet IPCPs build configuration (LLC and DIX) +# HAVE_ETH detection is in cmake/dependencies.cmake + +set(IPCP_ETH_LLC_TARGET ipcpd-eth-llc) +set(IPCP_ETH_DIX_TARGET ipcpd-eth-dix) + +add_executable(${IPCP_ETH_LLC_TARGET} llc.c ${IPCP_SOURCES}) +add_executable(${IPCP_ETH_DIX_TARGET} dix.c ${IPCP_SOURCES}) + +foreach(target ${IPCP_ETH_LLC_TARGET} ${IPCP_ETH_DIX_TARGET}) + target_include_directories(${target} PRIVATE ${IPCP_INCLUDE_DIRS}) + if(HAVE_BPF AND NOT APPLE) + target_include_directories(${target} PRIVATE ${BPF_C_INCLUDE_DIR}) + endif() + if(HAVE_NETMAP AND NOT APPLE) + target_compile_options(${target} PRIVATE -std=c99) + target_include_directories(${target} PRIVATE ${NETMAP_C_INCLUDE_DIR}) + endif() + target_link_libraries(${target} PRIVATE ouroboros-dev) + ouroboros_target_debug_definitions(${target}) +endforeach() + +install(TARGETS ${IPCP_ETH_LLC_TARGET} ${IPCP_ETH_DIX_TARGET} + RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}) diff --git a/src/ipcpd/eth/dix.c b/src/ipcpd/eth/dix.c index ac117e37..37b9896d 100644 --- a/src/ipcpd/eth/dix.c +++ b/src/ipcpd/eth/dix.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * IPC processes over Ethernet - DIX * diff --git a/src/ipcpd/eth/eth.c b/src/ipcpd/eth/eth.c index 932034d5..9d281297 100644 --- a/src/ipcpd/eth/eth.c +++ b/src/ipcpd/eth/eth.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * IPC processes over Ethernet * @@ -37,6 +37,7 @@ #include "config.h" +#include <ouroboros/endian.h> #include <ouroboros/hash.h> #include <ouroboros/errno.h> #include <ouroboros/list.h> @@ -46,11 +47,12 @@ #include <ouroboros/ipcp-dev.h> #include <ouroboros/fqueue.h> #include <ouroboros/logs.h> -#include <ouroboros/time_utils.h> +#include <ouroboros/time.h> #include <ouroboros/fccntl.h> #include <ouroboros/pthread.h> #include "ipcp.h" +#include "np1.h" #include "shim-data.h" #include <signal.h> @@ -87,31 +89,31 @@ #include <sys/mman.h> #if defined(HAVE_NETMAP) -#define NETMAP_WITH_LIBS -#include <net/netmap_user.h> + #define NETMAP_WITH_LIBS + #include <net/netmap_user.h> #elif defined(HAVE_BPF) -#define BPF_DEV_MAX 256 -#define BPF_BLEN sysconf(_SC_PAGESIZE) -#include <net/bpf.h> + #define BPF_DEV_MAX 256 + #define BPF_BLEN sysconf(_SC_PAGESIZE) + #include <net/bpf.h> #endif -#ifdef __linux__ +#define MAC_FMT "%02x:%02x:%02x:%02x:%02x:%02x" +#define MAC_VAL(a) \ + (uint8_t)(a)[0], (uint8_t)(a)[1], (uint8_t)(a)[2], \ + (uint8_t)(a)[3], (uint8_t)(a)[4], (uint8_t)(a)[5] + + #ifndef ETH_MAX_MTU /* In if_ether.h as of Linux 4.10. */ -#define ETH_MAX_MTU 0xFFFFU + #define ETH_MAX_MTU 0xFFFFU #endif /* ETH_MAX_MTU */ #ifdef BUILD_ETH_DIX -#define ETH_MTU eth_data.mtu -#define ETH_MTU_MAX ETH_MAX_MTU + #define ETH_MTU eth_data.mtu + #define ETH_MTU_MAX ETH_MAX_MTU #else -#define ETH_MTU eth_data.mtu -#define ETH_MTU_MAX 1500 + #define ETH_MTU eth_data.mtu + #define ETH_MTU_MAX 1500 #endif /* BUILD_ETH_DIX */ -#else /* __linux__ */ -#define ETH_MTU 1500 -#define ETH_MTU_MAX ETH_MTU -#endif /* __linux__ */ -#define MAC_SIZE 6 #define ETH_TYPE_LENGTH_SIZE sizeof(uint16_t) #define ETH_HEADER_SIZE (2 * MAC_SIZE + ETH_TYPE_LENGTH_SIZE) @@ -135,18 +137,15 @@ #define ETH_FRAME_SIZE (ETH_HEADER_SIZE + ETH_MTU_MAX) #endif -#define ALLOC_TIMEO 10 /* ms */ #define NAME_QUERY_TIMEO 2000 /* ms */ #define MGMT_TIMEO 100 /* ms */ -#define MGMT_FRAME_SIZE 2048 +#define MGMT_FRAME_SIZE IPCP_ETH_MGMT_FRAME_SIZE #define FLOW_REQ 0 #define FLOW_REPLY 1 #define NAME_QUERY_REQ 2 #define NAME_QUERY_REPLY 3 -struct ipcp ipcpi; - struct mgmt_msg { #if defined(BUILD_ETH_DIX) uint16_t seid; @@ -164,13 +163,13 @@ struct mgmt_msg { uint32_t ber; uint32_t max_gap; uint32_t delay; - uint16_t cypher_s; + uint32_t timeout; + int32_t response; uint8_t in_order; #if defined (BUILD_ETH_DIX) uint8_t code; uint8_t availability; #endif - int8_t response; } __attribute__((packed)); struct eth_frame { @@ -208,8 +207,9 @@ struct mgmt_frame { struct { struct shim_data * shim_data; -#ifdef __linux__ + int mtu; +#ifdef __linux__ int if_idx; #endif #if defined(HAVE_NETMAP) @@ -454,16 +454,15 @@ static int eth_ipcp_send_frame(const uint8_t * dst_addr, return 0; } -static int eth_ipcp_alloc(const uint8_t * dst_addr, +static int eth_ipcp_alloc(const uint8_t * dst_addr, #if defined(BUILD_ETH_DIX) - uint16_t eid, + uint16_t eid, #elif defined(BUILD_ETH_LLC) - uint8_t ssap, + uint8_t ssap, #endif - const uint8_t * hash, - qosspec_t qs, - const void * data, - size_t dlen) + const uint8_t * hash, + qosspec_t qs, + const buffer_t * data) { uint8_t * buf; struct mgmt_msg * msg; @@ -472,10 +471,12 @@ static int eth_ipcp_alloc(const uint8_t * dst_addr, len = sizeof(*msg) + ipcp_dir_hash_len(); - buf = malloc(len + ETH_HEADER_TOT_SIZE + dlen); + buf = malloc(len + ETH_HEADER_TOT_SIZE + data->len); if (buf == NULL) return -1; + memset(buf, 0, len + ETH_HEADER_TOT_SIZE + data->len); + msg = (struct mgmt_msg *) (buf + ETH_HEADER_TOT_SIZE); msg->code = FLOW_REQ; #if defined(BUILD_ETH_DIX) @@ -491,10 +492,11 @@ static int eth_ipcp_alloc(const uint8_t * dst_addr, msg->ber = hton32(qs.ber); msg->in_order = qs.in_order; msg->max_gap = hton32(qs.max_gap); - msg->cypher_s = hton16(qs.cypher_s); + msg->timeout = hton32(qs.timeout); memcpy(msg + 1, hash, ipcp_dir_hash_len()); - memcpy(buf + len + ETH_HEADER_TOT_SIZE, data, dlen); + if (data->len > 0) + memcpy(buf + len + ETH_HEADER_TOT_SIZE, data->data, data->len); ret = eth_ipcp_send_frame(dst_addr, #if defined(BUILD_ETH_DIX) @@ -503,31 +505,32 @@ static int eth_ipcp_alloc(const uint8_t * dst_addr, reverse_bits(MGMT_SAP), reverse_bits(MGMT_SAP), #endif - buf, len + dlen); + buf, len + data->len); free(buf); return ret; } -static int eth_ipcp_alloc_resp(uint8_t * dst_addr, +static int eth_ipcp_alloc_resp(uint8_t * dst_addr, #if defined(BUILD_ETH_DIX) - uint16_t seid, - uint16_t deid, + uint16_t seid, + uint16_t deid, #elif defined(BUILD_ETH_LLC) - uint8_t ssap, - uint8_t dsap, + uint8_t ssap, + uint8_t dsap, #endif - int response, - const void * data, - size_t len) + int response, + const buffer_t * data) { struct mgmt_msg * msg; uint8_t * buf; - buf = malloc(sizeof(*msg) + ETH_HEADER_TOT_SIZE + len); + buf = malloc(sizeof(*msg) + ETH_HEADER_TOT_SIZE + data->len); if (buf == NULL) return -1; + memset(buf, 0, sizeof(*msg) + ETH_HEADER_TOT_SIZE + data->len); + msg = (struct mgmt_msg *) (buf + ETH_HEADER_TOT_SIZE); msg->code = FLOW_REPLY; @@ -538,9 +541,10 @@ static int eth_ipcp_alloc_resp(uint8_t * dst_addr, msg->ssap = ssap; msg->dsap = dsap; #endif - msg->response = response; + msg->response = hton32(response); - memcpy(msg + 1, data, len); + if (data->len > 0) + memcpy(msg + 1, data->data, data->len); if (eth_ipcp_send_frame(dst_addr, #if defined(BUILD_ETH_DIX) @@ -549,7 +553,7 @@ static int eth_ipcp_alloc_resp(uint8_t * dst_addr, reverse_bits(MGMT_SAP), reverse_bits(MGMT_SAP), #endif - buf, sizeof(*msg) + len)) { + buf, sizeof(*msg) + data->len)) { free(buf); return -1; } @@ -559,42 +563,20 @@ static int eth_ipcp_alloc_resp(uint8_t * dst_addr, return 0; } -static int eth_ipcp_req(uint8_t * r_addr, +static int eth_ipcp_req(uint8_t * r_addr, #if defined(BUILD_ETH_DIX) - uint16_t r_eid, + uint16_t r_eid, #elif defined(BUILD_ETH_LLC) - uint8_t r_sap, + uint8_t r_sap, #endif - const uint8_t * dst, - qosspec_t qs, - const void * data, - size_t len) + const uint8_t * dst, + qosspec_t qs, + const buffer_t * data) { - struct timespec ts = {0, ALLOC_TIMEO * MILLION}; - struct timespec abstime; - int fd; + int fd; - clock_gettime(PTHREAD_COND_CLOCK, &abstime); - - pthread_mutex_lock(&ipcpi.alloc_lock); - - while (ipcpi.alloc_id != -1 && ipcp_get_state() == IPCP_OPERATIONAL) { - ts_add(&abstime, &ts, &abstime); - pthread_cond_timedwait(&ipcpi.alloc_cond, - &ipcpi.alloc_lock, - &abstime); - } - - if (ipcp_get_state() != IPCP_OPERATIONAL) { - log_dbg("Won't allocate over non-operational IPCP."); - pthread_mutex_unlock(&ipcpi.alloc_lock); - return -1; - } - - /* reply to IRM, called under lock to prevent race */ - fd = ipcp_flow_req_arr(dst, ipcp_dir_hash_len(), qs, data, len); + fd = ipcp_wait_flow_req_arr(dst, qs, IPCP_ETH_MPL, data); if (fd < 0) { - pthread_mutex_unlock(&ipcpi.alloc_lock); log_err("Could not get new flow from IRMd."); return -1; } @@ -609,11 +591,6 @@ static int eth_ipcp_req(uint8_t * r_addr, pthread_rwlock_unlock(ð_data.flows_lock); - ipcpi.alloc_id = fd; - pthread_cond_broadcast(&ipcpi.alloc_cond); - - pthread_mutex_unlock(&ipcpi.alloc_lock); - #if defined(BUILD_ETH_DIX) log_dbg("New flow request, fd %d, remote endpoint %d.", fd, r_eid); #elif defined(BUILD_ETH_LLC) @@ -622,20 +599,20 @@ static int eth_ipcp_req(uint8_t * r_addr, return 0; } -static int eth_ipcp_alloc_reply(uint8_t * r_addr, +static int eth_ipcp_alloc_reply(uint8_t * r_addr, #if defined(BUILD_ETH_DIX) - uint16_t seid, - uint16_t deid, + uint16_t seid, + uint16_t deid, #elif defined(BUILD_ETH_LLC) - uint8_t ssap, - int dsap, + uint8_t ssap, + int dsap, #endif - int response, - const void * data, - size_t len) + int response, + const buffer_t * data) { - int ret = 0; - int fd = -1; + int ret = 0; + int fd = -1; + time_t mpl = IPCP_ETH_MPL; pthread_rwlock_wrlock(ð_data.flows_lock); @@ -670,11 +647,12 @@ static int eth_ipcp_alloc_reply(uint8_t * r_addr, #elif defined(BUILD_ETH_LLC) log_dbg("Flow reply, fd %d, SSAP %d, DSAP %d.", fd, ssap, dsap); #endif - if ((ret = ipcp_flow_alloc_reply(fd, response, data, len)) < 0) + if ((ret = ipcp_flow_alloc_reply(fd, response, mpl, data)) < 0) { + log_err("Failed to reply to flow allocation."); return -1; + } return ret; - } static int eth_ipcp_name_query_req(const uint8_t * hash, @@ -691,6 +669,8 @@ static int eth_ipcp_name_query_req(const uint8_t * hash, if (buf == NULL) return -1; + memset(buf, 0, len + ETH_HEADER_TOT_SIZE); + msg = (struct mgmt_msg *) (buf + ETH_HEADER_TOT_SIZE); msg->code = NAME_QUERY_REPLY; @@ -718,11 +698,11 @@ static int eth_ipcp_name_query_req(const uint8_t * hash, static int eth_ipcp_name_query_reply(const uint8_t * hash, uint8_t * r_addr) { - uint64_t address = 0; + struct addr addr; - memcpy(&address, r_addr, MAC_SIZE); + memcpy(&addr.mac, r_addr, MAC_SIZE); - shim_data_dir_add_entry(eth_data.shim_data, hash, address); + shim_data_dir_add_entry(eth_data.shim_data, hash, addr); shim_data_dir_query_respond(eth_data.shim_data, hash); @@ -736,6 +716,7 @@ static int eth_ipcp_mgmt_frame(const uint8_t * buf, struct mgmt_msg * msg; size_t msg_len; qosspec_t qs; + buffer_t data; msg = (struct mgmt_msg *) buf; @@ -752,7 +733,10 @@ static int eth_ipcp_mgmt_frame(const uint8_t * buf, qs.ber = ntoh32(msg->ber); qs.in_order = msg->in_order; qs.max_gap = ntoh32(msg->max_gap); - qs.cypher_s = ntoh16(msg->cypher_s); + qs.timeout = ntoh32(msg->timeout); + + data.data = (uint8_t *) buf + msg_len; + data.len = len - msg_len; if (shim_data_reg_has(eth_data.shim_data, buf + sizeof(*msg))) { @@ -764,13 +748,15 @@ static int eth_ipcp_mgmt_frame(const uint8_t * buf, #endif buf + sizeof(*msg), qs, - buf + msg_len, - len - msg_len); + &data); } break; case FLOW_REPLY: assert(len >= sizeof(*msg)); + data.data = (uint8_t *) buf + sizeof(*msg); + data.len = len - sizeof(*msg); + eth_ipcp_alloc_reply(r_addr, #if defined(BUILD_ETH_DIX) ntohs(msg->seid), @@ -779,9 +765,8 @@ static int eth_ipcp_mgmt_frame(const uint8_t * buf, msg->ssap, msg->dsap, #endif - msg->response, - buf + sizeof(*msg), - len - sizeof(*msg)); + ntoh32(msg->response), + &data); break; case NAME_QUERY_REQ: eth_ipcp_name_query_req(buf + sizeof(*msg), r_addr); @@ -799,18 +784,15 @@ static int eth_ipcp_mgmt_frame(const uint8_t * buf, static void * eth_ipcp_mgmt_handler(void * o) { - int ret; - struct timespec timeout = {(MGMT_TIMEO / 1000), - (MGMT_TIMEO % 1000) * MILLION}; - struct timespec abstime; - struct mgmt_frame * frame; - (void) o; pthread_cleanup_push(__cleanup_mutex_unlock, ð_data.mgmt_lock); while (true) { - ret = 0; + int ret = 0; + struct timespec timeout = TIMESPEC_INIT_MS(MGMT_TIMEO); + struct timespec abstime; + struct mgmt_frame * frame = NULL; clock_gettime(PTHREAD_COND_CLOCK, &abstime); ts_add(&abstime, &timeout, &abstime); @@ -822,23 +804,19 @@ static void * eth_ipcp_mgmt_handler(void * o) ret = -pthread_cond_timedwait(ð_data.mgmt_cond, ð_data.mgmt_lock, &abstime); + if (ret != -ETIMEDOUT) + frame = list_first_entry((ð_data.mgmt_frames), + struct mgmt_frame, next); + if (frame != NULL) + list_del(&frame->next); - if (ret == -ETIMEDOUT) { - pthread_mutex_unlock(ð_data.mgmt_lock); - continue; - } + pthread_mutex_unlock(ð_data.mgmt_lock); - frame = list_first_entry((ð_data.mgmt_frames), - struct mgmt_frame, next); - if (frame == NULL) { - pthread_mutex_unlock(ð_data.mgmt_lock); + if (frame == NULL) continue; - } - - list_del(&frame->next); - pthread_mutex_unlock(ð_data.mgmt_lock); eth_ipcp_mgmt_frame(frame->buf, frame->len, frame->r_addr); + free(frame); } @@ -862,7 +840,7 @@ static void * eth_ipcp_packet_reader(void * o) #if defined(HAVE_NETMAP) struct nm_pkthdr hdr; #else - struct shm_du_buff * sdb; + struct ssm_pk_buff * spb; fd_set fds; int frame_len; #endif @@ -884,7 +862,7 @@ static void * eth_ipcp_packet_reader(void * o) buf = nm_nextpkt(eth_data.nmd, &hdr); if (buf == NULL) { - log_err("Bad read from netmap device."); + log_dbg("Bad read from netmap device."); continue; } #else @@ -894,28 +872,29 @@ static void * eth_ipcp_packet_reader(void * o) if (select(eth_data.bpf + 1, &fds, NULL, NULL, NULL)) continue; assert(FD_ISSET(eth_data.bpf, &fds)); - if (ipcp_sdb_reserve(&sdb, BPF_LEN)) + if (ipcp_spb_reserve(&spb, BPF_LEN)) continue; - buf = shm_du_buff_head(sdb); + buf = ssm_pk_buff_head(spb); frame_len = read(eth_data.bpf, buf, BPF_BLEN); #elif defined(HAVE_RAW_SOCKETS) FD_SET(eth_data.s_fd, &fds); if (select(eth_data.s_fd + 1, &fds, NULL, NULL, NULL) < 0) continue; assert(FD_ISSET(eth_data.s_fd, &fds)); - if (ipcp_sdb_reserve(&sdb, ETH_MTU)) + if (ipcp_spb_reserve(&spb, ETH_MTU)) continue; - buf = shm_du_buff_head_alloc(sdb, ETH_HEADER_TOT_SIZE); + buf = ssm_pk_buff_head_alloc(spb, ETH_HEADER_TOT_SIZE); if (buf == NULL) { log_dbg("Failed to allocate header."); - ipcp_sdb_release(sdb); + ipcp_spb_release(spb); continue; } frame_len = recv(eth_data.s_fd, buf, ETH_MTU + ETH_HEADER_TOT_SIZE, 0); #endif if (frame_len <= 0) { - ipcp_sdb_release(sdb); + log_dbg("Failed to receive frame."); + ipcp_spb_release(spb); continue; } #endif @@ -941,22 +920,14 @@ static void * eth_ipcp_packet_reader(void * o) #endif length = ntohs(e_frame->length); #if defined(BUILD_ETH_DIX) - if (e_frame->ethertype != eth_data.ethertype) { -#ifndef HAVE_NETMAP - ipcp_sdb_release(sdb); -#endif - continue; - } + if (e_frame->ethertype != eth_data.ethertype) + goto fail_frame; deid = ntohs(e_frame->eid); if (deid == MGMT_EID) { #elif defined (BUILD_ETH_LLC) - if (length > 0x05FF) {/* DIX */ -#ifndef HAVE_NETMAP - ipcp_sdb_release(sdb); -#endif - continue; - } + if (length > 0x05FF) /* DIX */ + goto fail_frame; length -= LLC_HEADER_SIZE; @@ -965,12 +936,18 @@ static void * eth_ipcp_packet_reader(void * o) if (ssap == MGMT_SAP && dsap == MGMT_SAP) { #endif + ipcp_spb_release(spb); /* No need for the N+1 buffer. */ + + if (length > MGMT_FRAME_SIZE) { + log_warn("Management frame size %u exceeds %u.", + length, MGMT_FRAME_SIZE); + goto fail_frame; + } + frame = malloc(sizeof(*frame)); if (frame == NULL) { -#ifndef HAVE_NETMAP - ipcp_sdb_release(sdb); -#endif - continue; + log_err("Failed to allocate frame."); + goto fail_frame; } memcpy(frame->buf, &e_frame->payload, length); @@ -981,10 +958,6 @@ static void * eth_ipcp_packet_reader(void * o) list_add(&frame->next, ð_data.mgmt_frames); pthread_cond_signal(ð_data.mgmt_cond); pthread_mutex_unlock(ð_data.mgmt_lock); - -#ifndef HAVE_NETMAP - ipcp_sdb_release(sdb); -#endif } else { pthread_rwlock_rdlock(ð_data.flows_lock); @@ -995,10 +968,7 @@ static void * eth_ipcp_packet_reader(void * o) #endif if (fd < 0) { pthread_rwlock_unlock(ð_data.flows_lock); -#ifndef HAVE_NETMAP - ipcp_sdb_release(sdb); -#endif - continue; + goto fail_frame; } #ifdef BUILD_ETH_LLC @@ -1006,20 +976,28 @@ static void * eth_ipcp_packet_reader(void * o) || memcmp(eth_data.fd_to_ef[fd].r_addr, e_frame->src_hwaddr, MAC_SIZE)) { pthread_rwlock_unlock(ð_data.flows_lock); -#ifndef HAVE_NETMAP - ipcp_sdb_release(sdb); -#endif - continue; + goto fail_frame; } #endif pthread_rwlock_unlock(ð_data.flows_lock); #ifndef HAVE_NETMAP - shm_du_buff_head_release(sdb, ETH_HEADER_TOT_SIZE); - shm_du_buff_truncate(sdb, length); - ipcp_flow_write(fd, sdb); + ssm_pk_buff_head_release(spb, ETH_HEADER_TOT_SIZE); + ssm_pk_buff_truncate(spb, length); #else - flow_write(fd, &e_frame->payload, length); + if (ipcp_spb_reserve(&spb, length)) + continue; + + buf = ssm_pk_buff_head(spb); + memcpy(buf, &e_frame->payload, length); +#endif + if (np1_flow_write(fd, spb, NP1_GET_POOL(fd)) < 0) + ipcp_spb_release(spb); + + continue; + fail_frame: +#ifndef HAVE_NETMAP + ipcp_spb_release(spb); #endif } } @@ -1035,7 +1013,7 @@ static void cleanup_writer(void * o) static void * eth_ipcp_packet_writer(void * o) { int fd; - struct shm_du_buff * sdb; + struct ssm_pk_buff * spb; size_t len; #if defined(BUILD_ETH_DIX) uint16_t deid; @@ -1053,27 +1031,28 @@ static void * eth_ipcp_packet_writer(void * o) (void) o; - pthread_cleanup_push(cleanup_writer, fq); - ipcp_lock_to_core(); + pthread_cleanup_push(cleanup_writer, fq); + while (true) { fevent(eth_data.np1_flows, fq, NULL); while ((fd = fqueue_next(fq)) >= 0) { if (fqueue_type(fq) != FLOW_PKT) continue; - if (ipcp_flow_read(fd, &sdb)) { + if (np1_flow_read(fd, &spb, NP1_GET_POOL(fd))) { log_dbg("Bad read from fd %d.", fd); continue; } - len = shm_du_buff_tail(sdb) - shm_du_buff_head(sdb); + len = ssm_pk_buff_len(spb); - if (shm_du_buff_head_alloc(sdb, ETH_HEADER_TOT_SIZE) + if (ssm_pk_buff_head_alloc(spb, ETH_HEADER_TOT_SIZE) == NULL) { log_dbg("Failed to allocate header."); - ipcp_sdb_release(sdb); + ipcp_spb_release(spb); + continue; } pthread_rwlock_rdlock(ð_data.flows_lock); @@ -1089,15 +1068,16 @@ static void * eth_ipcp_packet_writer(void * o) pthread_rwlock_unlock(ð_data.flows_lock); - eth_ipcp_send_frame(r_addr, + if (eth_ipcp_send_frame(r_addr, #if defined(BUILD_ETH_DIX) deid, #elif defined(BUILD_ETH_LLC) dsap, ssap, #endif - shm_du_buff_head(sdb), - len); - ipcp_sdb_release(sdb); + ssm_pk_buff_head(spb), + len)) + log_dbg("Failed to send frame."); + ipcp_spb_release(spb); } } @@ -1246,148 +1226,138 @@ static int open_bpf_device(void) } #endif -static int eth_ipcp_bootstrap(const struct ipcp_config * conf) -{ - int idx; - struct ifreq ifr; -#if defined(HAVE_NETMAP) - char ifn[IFNAMSIZ]; -#elif defined(HAVE_BPF) - int enable = 1; - int disable = 0; - int blen; -#endif /* HAVE_NETMAP */ - #if defined(__FreeBSD__) || defined(__APPLE__) +static int ifr_hwaddr_from_ifaddrs(struct ifreq * ifr) +{ struct ifaddrs * ifaddr; struct ifaddrs * ifa; -#elif defined(__linux__) - int skfd; -#endif -#ifndef SHM_RDRB_MULTI_BLOCK - size_t maxsz; -#endif -#if defined(HAVE_RAW_SOCKETS) - #if defined(IPCP_ETH_QDISC_BYPASS) - int qdisc_bypass = 1; - #endif /* ENABLE_QDISC_BYPASS */ - int flags; -#endif - assert(conf); - assert(conf->type == THIS_TYPE); + int idx; - ipcpi.dir_hash_algo = conf->layer_info.dir_hash_algo; - ipcpi.layer_name = strdup(conf->layer_info.layer_name); - if (ipcpi.layer_name == NULL) { - log_err("Failed to set layer name"); - return -ENOMEM; + if (getifaddrs(&ifaddr) < 0) { + log_err("Could not get interfaces."); + goto fail_ifaddrs; } - if (conf->dev == NULL) { - log_err("Device name is NULL."); - return -1; + for (ifa = ifaddr, idx = 0; ifa != NULL; ifa = ifa->ifa_next, ++idx) { + if (strcmp(ifa->ifa_name, ifr->ifr_name) == 0) + break; } - if (strlen(conf->dev) >= IFNAMSIZ) { - log_err("Invalid device name: %s.", conf->dev); - return -1; + if (ifa == NULL) { + log_err("Interface not found."); + goto fail_ifa; } - memset(&ifr, 0, sizeof(ifr)); - strcpy(ifr.ifr_name, conf->dev); + memcpy(&ifr->ifr_addr, ifa->ifa_addr, sizeof(*ifa->ifa_addr)); -#ifdef BUILD_ETH_DIX - if (conf->ethertype < 0x0600 || conf->ethertype == 0xFFFF) { - log_err("Invalid Ethertype."); - return -1; - } - eth_data.ethertype = htons(conf->ethertype); -#endif + log_dbg("Interface %s hwaddr " MAC_FMT ".", ifr->ifr_name, + MAC_VAL(ifr->ifr_addr.sa_data)); -#if defined(__FreeBSD__) || defined(__APPLE__) - if (getifaddrs(&ifaddr) < 0) { - log_err("Could not get interfaces."); - return -1; + freeifaddrs(ifaddr); + + return 0; + fail_ifa: + freeifaddrs(ifaddr); + fail_ifaddrs: + return -1; + +} +#elif defined(__linux__) +static int ifr_hwaddr_from_socket(struct ifreq * ifr) +{ + int skfd; + + skfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (skfd < 0) { + log_err("Failed to open socket."); + goto fail_socket; } - for (ifa = ifaddr, idx = 0; ifa != NULL; ifa = ifa->ifa_next, ++idx) { - if (strcmp(ifa->ifa_name, conf->dev)) - continue; - log_dbg("Interface %s found.", conf->dev); - - #if defined(HAVE_NETMAP) || defined(HAVE_BPF) - memcpy(eth_data.hw_addr, - LLADDR((struct sockaddr_dl *) (ifa)->ifa_addr), - MAC_SIZE); - #elif defined (HAVE_RAW_SOCKETS) - memcpy(&ifr.ifr_addr, ifa->ifa_addr, sizeof(*ifa->ifa_addr)); - #endif - break; + if (ioctl(skfd, SIOCGIFHWADDR, ifr)) { + log_err("Failed to get hwaddr."); + goto fail_ifr; } - freeifaddrs(ifaddr); + log_dbg("Interface %s hwaddr " MAC_FMT ".", ifr->ifr_name, + MAC_VAL(ifr->ifr_hwaddr.sa_data)); - if (ifa == NULL) { - log_err("Interface not found."); - return -1; - } + close(skfd); + return 0; + + fail_ifr: + close(skfd); + fail_socket: + return -1; +} +#endif + +static int eth_ifr_hwaddr(struct ifreq * ifr) +{ +#if defined(__FreeBSD__) || defined(__APPLE__) + return ifr_hwaddr_from_ifaddrs(ifr); #elif defined(__linux__) + return ifr_hwaddr_from_socket(ifr); +#else + return -1; +#endif +} + +static int eth_ifr_mtu(struct ifreq * ifr) +{ + int skfd; + skfd = socket(AF_UNIX, SOCK_STREAM, 0); if (skfd < 0) { log_err("Failed to open socket."); - return -1; + goto fail_socket; } - if (ioctl(skfd, SIOCGIFMTU, &ifr)) { + if (ioctl(skfd, SIOCGIFMTU, ifr) < 0) { log_err("Failed to get MTU."); - close(skfd); + goto fail_mtu; + } + close(skfd); + + return 0; + + fail_mtu: + close(skfd); + fail_socket: + return -1; +} + +static int eth_set_mtu(struct ifreq * ifr) +{ + if (eth_ifr_mtu(ifr) < 0) { + log_err("Failed to get interface MTU."); return -1; } - log_dbg("Device MTU is %d.", ifr.ifr_mtu); + log_dbg("Device MTU is %d.", ifr->ifr_mtu); - eth_data.mtu = MIN((int) ETH_MTU_MAX, ifr.ifr_mtu); - if (memcmp(conf->dev, "lo", 2) == 0 && eth_data.mtu > IPCP_ETH_LO_MTU) { + eth_data.mtu = MIN((int) ETH_MTU_MAX, ifr->ifr_mtu); + if (memcmp(ifr->ifr_name, "lo", 2) == 0 && + eth_data.mtu > IPCP_ETH_LO_MTU) { log_dbg("Using loopback interface. MTU restricted to %d.", IPCP_ETH_LO_MTU); eth_data.mtu = IPCP_ETH_LO_MTU; } -#ifndef SHM_RDRB_MULTI_BLOCK - maxsz = SHM_RDRB_BLOCK_SIZE - 5 * sizeof(size_t) - - (DU_BUFF_HEADSPACE + DU_BUFF_TAILSPACE); - if ((size_t) eth_data.mtu > maxsz ) { - log_dbg("Layer MTU truncated to shm block size."); - eth_data.mtu = maxsz; - } -#endif log_dbg("Layer MTU is %d.", eth_data.mtu); - if (ioctl(skfd, SIOCGIFHWADDR, &ifr)) { - log_err("Failed to get hwaddr."); - close(skfd); - return -1; - } - - close(skfd); - - idx = if_nametoindex(conf->dev); - if (idx == 0) { - log_err("Failed to retrieve interface index."); - return -1; - } - eth_data.if_idx = idx; -#endif /* __FreeBSD__ */ - + return 0; +} #if defined(HAVE_NETMAP) +static int eth_init_nmd(struct ifreq * ifr) +{ strcpy(ifn, "netmap:"); - strcat(ifn, conf->dev); + strcat(ifn, ifr->ifr_name); eth_data.nmd = nm_open(ifn, NULL, 0, NULL); if (eth_data.nmd == NULL) { log_err("Failed to open netmap device."); - return -1; + goto fail_nmd; } memset(ð_data.poll_in, 0, sizeof(eth_data.poll_in)); @@ -1399,11 +1369,22 @@ static int eth_ipcp_bootstrap(const struct ipcp_config * conf) eth_data.poll_out.events = POLLOUT; log_info("Using netmap device."); -#elif defined(HAVE_BPF) /* !HAVE_NETMAP */ + + return 0; + fail_nmd: + return -1; +} +#elif defined (HAVE_BPF) +static int eth_init_bpf(struct ifreq * ifr) +{ + int enable = 1; + int disable = 0; + int blen; + eth_data.bpf = open_bpf_device(); if (eth_data.bpf < 0) { log_err("Failed to open bpf device."); - return -1; + goto fail_bpf; } ioctl(eth_data.bpf, BIOCGBLEN, &blen); @@ -1413,7 +1394,7 @@ static int eth_ipcp_bootstrap(const struct ipcp_config * conf) goto fail_device; } - if (ioctl(eth_data.bpf, BIOCSETIF, &ifr) < 0) { + if (ioctl(eth_data.bpf, BIOCSETIF, ifr) < 0) { log_err("Failed to set interface."); goto fail_device; } @@ -1434,25 +1415,42 @@ static int eth_ipcp_bootstrap(const struct ipcp_config * conf) } log_info("Using Berkeley Packet Filter."); + + return 0; + + fail_device: + close(eth_data.bpf); + fail_bpf: + return -1; +} #elif defined(HAVE_RAW_SOCKETS) +static int eth_init_raw_socket(struct ifreq * ifr) +{ + int idx; + int flags; +#if defined(IPCP_ETH_QDISC_BYPASS) + int qdisc_bypass = 1; +#endif /* ENABLE_QDISC_BYPASS */ + + idx = if_nametoindex(ifr->ifr_name); + if (idx == 0) { + log_err("Failed to retrieve interface index."); + return -1; + } memset(&(eth_data.device), 0, sizeof(eth_data.device)); eth_data.device.sll_ifindex = idx; eth_data.device.sll_family = AF_PACKET; - memcpy(eth_data.device.sll_addr, ifr.ifr_hwaddr.sa_data, MAC_SIZE); + memcpy(eth_data.device.sll_addr, ifr->ifr_hwaddr.sa_data, MAC_SIZE); eth_data.device.sll_halen = MAC_SIZE; eth_data.device.sll_protocol = htons(ETH_P_ALL); - - #if defined (BUILD_ETH_DIX) +#if defined (BUILD_ETH_DIX) eth_data.s_fd = socket(AF_PACKET, SOCK_RAW, eth_data.ethertype); - #elif defined (BUILD_ETH_LLC) +#elif defined (BUILD_ETH_LLC) eth_data.s_fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_802_2)); - #endif - - log_info("Using raw socket device."); - +#endif if (eth_data.s_fd < 0) { log_err("Failed to create socket."); - return -1; + goto fail_socket; } flags = fcntl(eth_data.s_fd, F_GETFL, 0); @@ -1466,80 +1464,137 @@ static int eth_ipcp_bootstrap(const struct ipcp_config * conf) goto fail_device; } - #if defined(IPCP_ETH_QDISC_BYPASS) +#if defined(IPCP_ETH_QDISC_BYPASS) if (setsockopt(eth_data.s_fd, SOL_PACKET, PACKET_QDISC_BYPASS, &qdisc_bypass, sizeof(qdisc_bypass))) { log_info("Qdisc bypass not supported."); } - #endif +#endif if (bind(eth_data.s_fd, (struct sockaddr *) ð_data.device, - sizeof(eth_data.device))) { + sizeof(eth_data.device)) < 0) { log_err("Failed to bind socket to interface."); goto fail_device; } +#ifdef __linux__ + eth_data.if_idx = idx; +#endif + log_info("Using raw socket device."); + return 0; + fail_device: + close(eth_data.s_fd); + fail_socket: + return -1; +} +#endif + +static int eth_ipcp_bootstrap(struct ipcp_config * conf) +{ + struct ifreq ifr; + int i; +#if defined(HAVE_NETMAP) + char ifn[IFNAMSIZ]; #endif /* HAVE_NETMAP */ - ipcp_set_state(IPCP_OPERATIONAL); -#if defined(__linux__) - if (pthread_create(ð_data.if_monitor, - NULL, - eth_ipcp_if_monitor, - NULL)) { - ipcp_set_state(IPCP_INIT); - goto fail_device; + assert(conf); + assert(conf->type == THIS_TYPE); + + memset(&ifr, 0, sizeof(ifr)); + strcpy(ifr.ifr_name, conf->eth.dev); + + if (strlen(conf->eth.dev) >= IFNAMSIZ) { + log_err("Invalid device name: %s.", conf->eth.dev); + return -1; } +#ifdef BUILD_ETH_DIX + if (conf->eth.ethertype < 0x0600 || conf->eth.ethertype == 0xFFFF) { + log_err("Invalid Ethertype: %d.", conf->eth.ethertype); + return -1; + } + eth_data.ethertype = htons(conf->eth.ethertype); #endif + if (eth_set_mtu(&ifr) < 0) { + log_err("Failed to set MTU."); + return -1; + } - if (pthread_create(ð_data.mgmt_handler, - NULL, - eth_ipcp_mgmt_handler, - NULL)) { - ipcp_set_state(IPCP_INIT); + if (eth_ifr_hwaddr(&ifr) < 0) { + log_err("Failed to get hardware addr."); + return -1; + } +#if defined(HAVE_NETMAP) || defined(HAVE_BPF) + memcpy(eth_data.hw_addr, LLADDR((struct sockaddr_dl *) &ifr.ifr_addr), + MAC_SIZE); +#endif +#if defined(HAVE_NETMAP) + if (eth_init_nmd(&ifr) < 0) { + log_err("Failed to initialize netmap device."); + return -1; + } +#elif defined(HAVE_BPF) /* !HAVE_NETMAP */ + if (eth_init_bpf(&ifr) < 0) { + log_err("Failed to initialize BPF device."); + return -1; + } +#elif defined(HAVE_RAW_SOCKETS) + if (eth_init_raw_socket(&ifr) < 0) { + log_err("Failed to initialize raw socket device."); + return -1; + } +#endif /* HAVE_NETMAP */ +#if defined(__linux__) + if (pthread_create(ð_data.if_monitor, NULL, + eth_ipcp_if_monitor, NULL)) { + log_err("Failed to create monitor thread: %s.", + strerror(errno)); + goto fail_monitor; + } +#endif + if (pthread_create(ð_data.mgmt_handler, NULL, + eth_ipcp_mgmt_handler, NULL)) { + log_err("Failed to create mgmt handler thread: %s.", + strerror(errno)); goto fail_mgmt_handler; } - for (idx = 0; idx < IPCP_ETH_RD_THR; ++idx) { - if (pthread_create(ð_data.packet_reader[idx], - NULL, - eth_ipcp_packet_reader, - NULL)) { - ipcp_set_state(IPCP_INIT); + for (i = 0; i < IPCP_ETH_RD_THR; i++) { + if (pthread_create(ð_data.packet_reader[i], NULL, + eth_ipcp_packet_reader, NULL)) { + log_err("Failed to create packet reader thread: %s", + strerror(errno)); goto fail_packet_reader; } } - for (idx = 0; idx < IPCP_ETH_WR_THR; ++idx) { - if (pthread_create(ð_data.packet_writer[idx], - NULL, - eth_ipcp_packet_writer, - NULL)) { - ipcp_set_state(IPCP_INIT); + for (i = 0; i < IPCP_ETH_WR_THR; i++) { + if (pthread_create(ð_data.packet_writer[i], NULL, + eth_ipcp_packet_writer, NULL)) { + log_err("Failed to create packet writer thread: %s", + strerror(errno)); goto fail_packet_writer; } } #if defined(BUILD_ETH_DIX) log_dbg("Bootstrapped IPCP over DIX Ethernet with pid %d " - "and Ethertype 0x%X.", getpid(), conf->ethertype); + "and Ethertype 0x%X.", getpid(), conf->eth.ethertype); #elif defined(BUILD_ETH_LLC) log_dbg("Bootstrapped IPCP over Ethernet with LLC with pid %d.", getpid()); #endif - return 0; fail_packet_writer: - while (idx > 0) { - pthread_cancel(eth_data.packet_writer[--idx]); - pthread_join(eth_data.packet_writer[idx], NULL); + while (i-- > 0) { + pthread_cancel(eth_data.packet_writer[i]); + pthread_join(eth_data.packet_writer[i], NULL); } - idx = IPCP_ETH_RD_THR; + i = IPCP_ETH_RD_THR; fail_packet_reader: - while (idx > 0) { - pthread_cancel(eth_data.packet_reader[--idx]); - pthread_join(eth_data.packet_reader[idx], NULL); + while (i-- > 0) { + pthread_cancel(eth_data.packet_reader[i]); + pthread_join(eth_data.packet_reader[i], NULL); } pthread_cancel(eth_data.mgmt_handler); pthread_join(eth_data.mgmt_handler, NULL); @@ -1548,8 +1603,8 @@ static int eth_ipcp_bootstrap(const struct ipcp_config * conf) pthread_cancel(eth_data.if_monitor); pthread_join(eth_data.if_monitor, NULL); #endif -#if defined(__linux__) || !defined(HAVE_NETMAP) - fail_device: +#if defined(__linux__) + fail_monitor: #endif #if defined(HAVE_NETMAP) nm_close(eth_data.nmd); @@ -1564,13 +1619,11 @@ static int eth_ipcp_bootstrap(const struct ipcp_config * conf) static int eth_ipcp_reg(const uint8_t * hash) { if (shim_data_reg_add_entry(eth_data.shim_data, hash)) { - log_err("Failed to add " HASH_FMT " to local registry.", - HASH_VAL(hash)); + log_err("Failed to add " HASH_FMT32 " to local registry.", + HASH_VAL32(hash)); return -1; } - log_dbg("Registered " HASH_FMT ".", HASH_VAL(hash)); - return 0; } @@ -1584,8 +1637,7 @@ static int eth_ipcp_unreg(const uint8_t * hash) static int eth_ipcp_query(const uint8_t * hash) { uint8_t r_addr[MAC_SIZE]; - struct timespec timeout = {(NAME_QUERY_TIMEO / 1000), - (NAME_QUERY_TIMEO % 1000) * MILLION}; + struct timespec timeout = TIMESPEC_INIT_MS(NAME_QUERY_TIMEO); struct dir_query * query; int ret; uint8_t * buf; @@ -1601,6 +1653,8 @@ static int eth_ipcp_query(const uint8_t * hash) if (buf == NULL) return -1; + memset(buf, 0, len + ETH_HEADER_TOT_SIZE); + msg = (struct mgmt_msg *) (buf + ETH_HEADER_TOT_SIZE); msg->code = NAME_QUERY_REQ; @@ -1637,42 +1691,41 @@ static int eth_ipcp_query(const uint8_t * hash) return ret; } -static int eth_ipcp_flow_alloc(int fd, - const uint8_t * hash, - qosspec_t qs, - const void * data, - size_t len) +static int eth_ipcp_flow_alloc(int fd, + const uint8_t * hash, + qosspec_t qs, + const buffer_t * data) { #ifdef BUILD_ETH_LLC uint8_t ssap = 0; #endif uint8_t r_addr[MAC_SIZE]; - uint64_t addr = 0; - - log_dbg("Allocating flow to " HASH_FMT ".", HASH_VAL(hash)); + struct addr addr; assert(hash); if (!shim_data_dir_has(eth_data.shim_data, hash)) { - log_err("Destination unreachable."); + log_err("Destination "HASH_FMT32 "unreachable.", + HASH_VAL32(hash)); return -1; } + addr = shim_data_dir_get_addr(eth_data.shim_data, hash); + memcpy(r_addr, &addr.mac, MAC_SIZE); - pthread_rwlock_wrlock(ð_data.flows_lock); #ifdef BUILD_ETH_LLC + pthread_rwlock_wrlock(ð_data.flows_lock); ssap = bmp_allocate(eth_data.saps); if (!bmp_is_id_valid(eth_data.saps, ssap)) { pthread_rwlock_unlock(ð_data.flows_lock); + log_err("Failed to allocate SSAP."); return -1; } eth_data.fd_to_ef[fd].sap = ssap; eth_data.ef_to_fd[ssap] = fd; -#endif pthread_rwlock_unlock(ð_data.flows_lock); - - memcpy(r_addr, &addr, MAC_SIZE); +#endif if (eth_ipcp_alloc(r_addr, #if defined(BUILD_ETH_DIX) @@ -1682,34 +1735,29 @@ static int eth_ipcp_flow_alloc(int fd, #endif hash, qs, - data, - len) < 0) { + data) < 0) { #ifdef BUILD_ETH_LLC pthread_rwlock_wrlock(ð_data.flows_lock); bmp_release(eth_data.saps, eth_data.fd_to_ef[fd].sap); eth_data.fd_to_ef[fd].sap = -1; eth_data.ef_to_fd[ssap] = -1; pthread_rwlock_unlock(ð_data.flows_lock); + log_err("Failed to allocate with peer."); #endif return -1; } fset_add(eth_data.np1_flows, fd); -#if defined(BUILD_ETH_DIX) - log_dbg("Pending flow with fd %d.", fd); -#elif defined(BUILD_ETH_LLC) - log_dbg("Pending flow with fd %d on SAP %d.", fd, ssap); +#if defined(BUILD_ETH_LLC) + log_dbg("Assigned SAP %d for fd %d.", ssap, fd); #endif return 0; } -static int eth_ipcp_flow_alloc_resp(int fd, - int response, - const void * data, - size_t len) +static int eth_ipcp_flow_alloc_resp(int fd, + int response, + const buffer_t * data) { - struct timespec ts = {0, ALLOC_TIMEO * MILLION}; - struct timespec abstime; #if defined(BUILD_ETH_DIX) uint16_t r_eid; #elif defined(BUILD_ETH_LLC) @@ -1718,27 +1766,11 @@ static int eth_ipcp_flow_alloc_resp(int fd, #endif uint8_t r_addr[MAC_SIZE]; - clock_gettime(PTHREAD_COND_CLOCK, &abstime); - - pthread_mutex_lock(&ipcpi.alloc_lock); - - while (ipcpi.alloc_id != fd && ipcp_get_state() == IPCP_OPERATIONAL) { - ts_add(&abstime, &ts, &abstime); - pthread_cond_timedwait(&ipcpi.alloc_cond, - &ipcpi.alloc_lock, - &abstime); - } - - if (ipcp_get_state() != IPCP_OPERATIONAL) { - pthread_mutex_unlock(&ipcpi.alloc_lock); + if (ipcp_wait_flow_resp(fd) < 0) { + log_err("Failed to wait for flow response."); return -1; } - ipcpi.alloc_id = -1; - pthread_cond_broadcast(&ipcpi.alloc_cond); - - pthread_mutex_unlock(&ipcpi.alloc_lock); - pthread_rwlock_wrlock(ð_data.flows_lock); #if defined(BUILD_ETH_DIX) r_eid = eth_data.fd_to_ef[fd].r_eid; @@ -1746,6 +1778,7 @@ static int eth_ipcp_flow_alloc_resp(int fd, ssap = bmp_allocate(eth_data.saps); if (!bmp_is_id_valid(eth_data.saps, ssap)) { pthread_rwlock_unlock(ð_data.flows_lock); + log_err("Failed to allocate SSAP."); return -1; } @@ -1764,21 +1797,19 @@ static int eth_ipcp_flow_alloc_resp(int fd, ssap, r_sap, #endif response, - data, - len) < 0) { + data) < 0) { #ifdef BUILD_ETH_LLC pthread_rwlock_wrlock(ð_data.flows_lock); bmp_release(eth_data.saps, eth_data.fd_to_ef[fd].sap); pthread_rwlock_unlock(ð_data.flows_lock); #endif + log_err("Failed to respond to peer."); return -1; } fset_add(eth_data.np1_flows, fd); -#if defined(BUILD_ETH_DIX) - log_dbg("Accepted flow, fd %d.", fd); -#elif defined(BUILD_ETH_LLC) - log_dbg("Accepted flow, fd %d, SAP %d.", fd, (uint8_t)ssap); +#if defined(BUILD_ETH_LLC) + log_dbg("Assigned SAP %d for fd %d.", ssap, fd); #endif return 0; } @@ -1807,9 +1838,7 @@ static int eth_ipcp_flow_dealloc(int fd) pthread_rwlock_unlock(ð_data.flows_lock); - flow_dealloc(fd); - - log_dbg("Flow with fd %d deallocated.", fd); + ipcp_flow_dealloc(fd); return 0; } @@ -1833,9 +1862,6 @@ int main(int argc, { int i; - if (ipcp_init(argc, argv, ð_ops, THIS_TYPE) < 0) - goto fail_init; - if (eth_data_init() < 0) { #if defined(BUILD_ETH_DIX) log_err("Failed to init eth-llc data."); @@ -1845,18 +1871,17 @@ int main(int argc, goto fail_data_init; } - if (ipcp_boot() < 0) { - log_err("Failed to boot IPCP."); - goto fail_boot; + if (ipcp_init(argc, argv, ð_ops, THIS_TYPE) < 0) { + log_err("Failed to initialize IPCP."); + goto fail_init; } - if (ipcp_create_r(0)) { - log_err("Failed to notify IRMd we are initialized."); - ipcp_set_state(IPCP_NULL); - goto fail_create_r; + if (ipcp_start() < 0) { + log_err("Failed to start IPCP."); + goto fail_start; } - ipcp_shutdown(); + ipcp_sigwait(); if (ipcp_get_state() == IPCP_SHUTDOWN) { for (i = 0; i < IPCP_ETH_WR_THR; ++i) @@ -1879,19 +1904,18 @@ int main(int argc, #endif } - eth_data_fini(); + ipcp_stop(); ipcp_fini(); + eth_data_fini(); + exit(EXIT_SUCCESS); - fail_create_r: - ipcp_shutdown(); - fail_boot: - eth_data_fini(); - fail_data_init: + fail_start: ipcp_fini(); fail_init: - ipcp_create_r(-1); + eth_data_fini(); + fail_data_init: exit(EXIT_FAILURE); } diff --git a/src/ipcpd/eth/llc.c b/src/ipcpd/eth/llc.c index d1e8bbdc..c900dcab 100644 --- a/src/ipcpd/eth/llc.c +++ b/src/ipcpd/eth/llc.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * IPC processes over Ethernet - LLC * diff --git a/src/ipcpd/ipcp.c b/src/ipcpd/ipcp.c index cd09a7cf..3ea77da9 100644 --- a/src/ipcpd/ipcp.c +++ b/src/ipcpd/ipcp.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * IPC process main loop * @@ -35,20 +35,24 @@ #define OUROBOROS_PREFIX "ipcpd/ipcp" #define IPCP_INFO "info" +#define ALLOC_TIMEOUT 50 /* ms */ +#include <ouroboros/bitmap.h> +#include <ouroboros/dev.h> +#include <ouroboros/errno.h> #include <ouroboros/hash.h> +#include <ouroboros/ipcp-dev.h> #include <ouroboros/logs.h> -#include <ouroboros/time_utils.h> -#include <ouroboros/utils.h> -#include <ouroboros/sockets.h> -#include <ouroboros/errno.h> -#include <ouroboros/dev.h> -#include <ouroboros/bitmap.h> #include <ouroboros/np1_flow.h> -#include <ouroboros/rib.h> +#include <ouroboros/protobuf.h> #include <ouroboros/pthread.h> +#include <ouroboros/rib.h> +#include <ouroboros/sockets.h> +#include <ouroboros/time.h> +#include <ouroboros/utils.h> #include "ipcp.h" +#include "np1.h" #include <signal.h> #include <string.h> @@ -61,13 +65,75 @@ #endif #endif -char * info[LAYER_NAME_SIZE + 1] = { - "_state", - "_type", - "_layer", - NULL +#ifndef CLOCK_REALTIME_COARSE +#define CLOCK_REALTIME_COARSE CLOCK_REALTIME +#endif + +static char * ipcp_type_str[] = { + "local", + "unicast", + "broadcast", + "eth-llc", + "eth-dix", + "udp4", + "udp6" }; +static char * dir_hash_str[] = { + "SHA3-224", + "SHA3-256", + "SHA3-384", + "SHA3-512", + "CRC32", + "MD5" +}; + +static char * ipcp_state_str[] = { + "null", + "init", + "boot", + "bootstrapped", + "enrolled", + "operational", + "shutdown" +}; + +struct { + pid_t irmd_pid; + char * name; + + enum ipcp_type type; + char layer_name[LAYER_NAME_SIZE + 1]; + + uint64_t dt_addr; + + enum hash_algo dir_hash_algo; + + struct ipcp_ops * ops; + int irmd_fd; + + enum ipcp_state state; + pthread_cond_t state_cond; + pthread_mutex_t state_mtx; + + int sockfd; + char * sock_path; + + struct list_head cmds; + pthread_cond_t cmd_cond; + pthread_mutex_t cmd_lock; + + int alloc_id; + pthread_cond_t alloc_cond; + pthread_mutex_t alloc_lock; + + struct tpm * tpm; + + pthread_t acceptor; +} ipcpd; + +struct np1_state np1; + struct cmd { struct list_head next; @@ -76,9 +142,38 @@ struct cmd { int fd; }; +enum ipcp_type ipcp_get_type(void) +{ + return ipcpd.type; +} + +const char * ipcp_get_name(void) +{ + return ipcpd.name; +} + +void ipcp_set_dir_hash_algo(enum hash_algo algo) +{ + ipcpd.dir_hash_algo = algo; +} + +size_t ipcp_dir_hash_len(void) +{ + return hash_len(ipcpd.dir_hash_algo); +} + +int ipcp_get_layer_name(char * layer) +{ + if (ipcp_get_state() < IPCP_OPERATIONAL) + return -EIPCPSTATE; + + strcpy(layer, ipcpd.layer_name); + return 0; +} + uint8_t * ipcp_hash_dup(const uint8_t * hash) { - uint8_t * dup = malloc(hash_len(ipcpi.dir_hash_algo)); + uint8_t * dup = malloc(hash_len(ipcpd.dir_hash_algo)); if (dup == NULL) return NULL; @@ -102,6 +197,13 @@ void ipcp_hash_str(char * buf, buf[2 * i] = '\0'; } +static const char * info[] = { + "_state", + "_type", + "_layer", + NULL +}; + static int ipcp_rib_read(const char * path, char * buf, size_t len) @@ -129,18 +231,20 @@ static int ipcp_rib_read(const char * path, } if (strcmp(entry, info[1]) == 0) { /* _type */ - if (ipcpi.type == IPCP_LOCAL) + if (ipcpd.type == IPCP_LOCAL) strcpy(buf, "local\n"); - else if (ipcpi.type == IPCP_UNICAST) + else if (ipcpd.type == IPCP_UNICAST) strcpy(buf, "unicast\n"); - else if (ipcpi.type == IPCP_BROADCAST) + else if (ipcpd.type == IPCP_BROADCAST) strcpy(buf, "broadcast\n"); - else if (ipcpi.type == IPCP_ETH_LLC) + else if (ipcpd.type == IPCP_ETH_LLC) strcpy(buf, "eth-llc\n"); - else if (ipcpi.type == IPCP_ETH_DIX) + else if (ipcpd.type == IPCP_ETH_DIX) strcpy(buf, "eth-dix\n"); - else if (ipcpi.type == IPCP_UDP) - strcpy(buf, "udp\n"); + else if (ipcpd.type == IPCP_UDP4) + strcpy(buf, "udp4\n"); + else if (ipcpd.type == IPCP_UDP6) + strcpy(buf, "udp6\n"); else strcpy(buf, "bug\n"); } @@ -150,7 +254,7 @@ static int ipcp_rib_read(const char * path, if (ipcp_get_state() < IPCP_OPERATIONAL) strcpy(buf, "(null)"); else - strcpy(buf, ipcpi.layer_name); + strcpy(buf, ipcpd.layer_name); buf[strlen(buf)] = '\n'; } @@ -162,38 +266,40 @@ static int ipcp_rib_readdir(char *** buf) { int i = 0; - while (info[i] != NULL) - i++; + while (info[i++] != NULL); *buf = malloc(sizeof(**buf) * i); if (*buf == NULL) - goto fail; + goto fail_entries; i = 0; while (info[i] != NULL) { (*buf)[i] = strdup(info[i]); - if (*buf == NULL) + if ((*buf)[i] == NULL) goto fail_dup; i++; } return i; fail_dup: - while (--i > 0) + while (i-- > 0) free((*buf)[i]); - fail: free(*buf); - - return -1; + fail_entries: + return -ENOMEM; } static int ipcp_rib_getattr(const char * path, struct rib_attr * attr) { - (void) path; + char buf[LAYER_NAME_SIZE + 2]; + struct timespec now; + + clock_gettime(CLOCK_REALTIME_COARSE, &now); - attr->size = LAYER_NAME_SIZE; + attr->size = ipcp_rib_read(path, buf, LAYER_NAME_SIZE + 2); + attr->mtime = now.tv_sec; return 0; } @@ -206,24 +312,18 @@ static struct rib_ops r_ops = { static void * acceptloop(void * o) { - int csockfd; - struct timeval tv = {(SOCKET_TIMEOUT / 1000), - (SOCKET_TIMEOUT % 1000) * 1000}; + int csockfd; (void) o; while (ipcp_get_state() != IPCP_SHUTDOWN && - ipcp_get_state() != IPCP_NULL) { + ipcp_get_state() != IPCP_INIT) { struct cmd * cmd; - csockfd = accept(ipcpi.sockfd, 0, 0); + csockfd = accept(ipcpd.sockfd, 0, 0); if (csockfd < 0) continue; - if (setsockopt(csockfd, SOL_SOCKET, SO_RCVTIMEO, - (void *) &tv, sizeof(tv))) - log_warn("Failed to set timeout on socket."); - cmd = malloc(sizeof(*cmd)); if (cmd == NULL) { log_err("Out of memory"); @@ -248,51 +348,501 @@ static void * acceptloop(void * o) cmd->fd = csockfd; - pthread_mutex_lock(&ipcpi.cmd_lock); + pthread_mutex_lock(&ipcpd.cmd_lock); - list_add(&cmd->next, &ipcpi.cmds); + list_add(&cmd->next, &ipcpd.cmds); - pthread_cond_signal(&ipcpi.cmd_cond); + pthread_cond_signal(&ipcpd.cmd_cond); - pthread_mutex_unlock(&ipcpi.cmd_lock); + pthread_mutex_unlock(&ipcpd.cmd_lock); } return (void *) 0; } +int ipcp_wait_flow_req_arr(const uint8_t * dst, + qosspec_t qs, + time_t mpl, + const buffer_t * data) +{ + struct timespec ts = TIMESPEC_INIT_MS(ALLOC_TIMEOUT); + struct timespec abstime; + int fd; + buffer_t hash; + + hash.data = (uint8_t *) dst; + hash.len = ipcp_dir_hash_len(); + + clock_gettime(PTHREAD_COND_CLOCK, &abstime); + + pthread_mutex_lock(&ipcpd.alloc_lock); + + while (ipcpd.alloc_id != -1 && ipcp_get_state() == IPCP_OPERATIONAL) { + ts_add(&abstime, &ts, &abstime); + pthread_cond_timedwait(&ipcpd.alloc_cond, + &ipcpd.alloc_lock, + &abstime); + } + + if (ipcp_get_state() != IPCP_OPERATIONAL) { + pthread_mutex_unlock(&ipcpd.alloc_lock); + log_err("Won't allocate over non-operational IPCP."); + return -EIPCPSTATE; + } + + assert(ipcpd.alloc_id == -1); + + fd = ipcp_flow_req_arr(&hash, qs, mpl, data); + if (fd < 0) { + pthread_mutex_unlock(&ipcpd.alloc_lock); + log_err("Failed to get fd for flow."); + return fd; + } + + ipcpd.alloc_id = fd; + pthread_cond_broadcast(&ipcpd.alloc_cond); + + pthread_mutex_unlock(&ipcpd.alloc_lock); + + return fd; + +} + +int ipcp_wait_flow_resp(const int fd) +{ + struct timespec ts = TIMESPEC_INIT_MS(ALLOC_TIMEOUT); + struct timespec abstime; + + clock_gettime(PTHREAD_COND_CLOCK, &abstime); + + pthread_mutex_lock(&ipcpd.alloc_lock); + + while (ipcpd.alloc_id != fd && ipcp_get_state() == IPCP_OPERATIONAL) { + ts_add(&abstime, &ts, &abstime); + pthread_cond_timedwait(&ipcpd.alloc_cond, + &ipcpd.alloc_lock, + &abstime); + } + + if (ipcp_get_state() != IPCP_OPERATIONAL) { + pthread_mutex_unlock(&ipcpd.alloc_lock); + return -1; + } + + assert(ipcpd.alloc_id == fd); + + ipcpd.alloc_id = -1; + pthread_cond_broadcast(&ipcpd.alloc_cond); + + pthread_mutex_unlock(&ipcpd.alloc_lock); + + return 0; +} + static void free_msg(void * o) { ipcp_msg__free_unpacked((ipcp_msg_t *) o, NULL); } -static void * mainloop(void * o) + +static void do_bootstrap(ipcp_config_msg_t * conf_msg, + ipcp_msg_t * ret_msg) { - int sfd; - buffer_t buffer; struct ipcp_config conf; - struct layer_info info; - ipcp_config_msg_t * conf_msg; - ipcp_msg_t * msg; + struct layer_info * info; + + log_info("Bootstrapping..."); + + if (ipcpd.ops->ipcp_bootstrap == NULL) { + log_err("Failed to Bootstrap: operation unsupported."); + ret_msg->result = -ENOTSUP; + return; + } + + if (ipcp_get_state() != IPCP_BOOT) { + + log_err("Failed to bootstrap: IPCP in state <%s>, need <%s>.", + ipcp_state_str[ipcp_get_state()], + ipcp_state_str[IPCP_BOOT]); + ret_msg->result = -EIPCPSTATE; + return; + } + + conf = ipcp_config_msg_to_s(conf_msg); + switch(conf.type) { /* FIXED algorithms */ + case IPCP_UDP4: + /* FALLTHRU */ + case IPCP_UDP6: + conf.layer_info.dir_hash_algo = (enum pol_dir_hash) HASH_MD5; + break; + case IPCP_BROADCAST: + conf.layer_info.dir_hash_algo = DIR_HASH_SHA3_256; + break; + default: + break; + } + + ret_msg->result = ipcpd.ops->ipcp_bootstrap(&conf); + if (ret_msg->result < 0) { + log_err("Failed to bootstrap IPCP."); + return; + } + + info = &conf.layer_info; + + strcpy(ipcpd.layer_name, info->name); + ipcpd.dir_hash_algo = (enum hash_algo) info->dir_hash_algo; + ret_msg->layer_info = layer_info_s_to_msg(info); + ipcp_set_state(IPCP_OPERATIONAL); + + log_info("Finished bootstrapping in %s.", info->name); + log_info(" type: %s", ipcp_type_str[ipcpd.type]); + log_info(" hash: %s [%zd bytes]", + dir_hash_str[ipcpd.dir_hash_algo], + ipcp_dir_hash_len()); +} + +static void do_enroll(const char * dst, + ipcp_msg_t * ret_msg) +{ + struct layer_info info; + + log_info("Enrolling with %s...", dst); + + if (ipcpd.ops->ipcp_enroll == NULL) { + log_err("Failed to enroll: operation unsupported."); + ret_msg->result = -ENOTSUP; + return; + } + + if (ipcp_get_state() != IPCP_BOOT) { + log_err("Failed to enroll: IPCP in state <%s>, need <%s>.", + ipcp_state_str[ipcp_get_state()], + ipcp_state_str[IPCP_BOOT]); + ret_msg->result = -EIPCPSTATE; + return; + } + + ret_msg->result = ipcpd.ops->ipcp_enroll(dst, &info); + if (ret_msg->result < 0) { + log_err("Failed to bootstrap IPCP."); + return; + } + + strcpy(ipcpd.layer_name, info.name); + ipcpd.dir_hash_algo = (enum hash_algo) info.dir_hash_algo; + ret_msg->layer_info = layer_info_s_to_msg(&info); + ipcp_set_state(IPCP_OPERATIONAL); + + log_info("Finished enrolling with %s in layer %s.", dst, info.name); + log_info(" type: %s", ipcp_type_str[ipcpd.type]); + log_info(" hash: %s [%zd bytes]", + dir_hash_str[ipcpd.dir_hash_algo], + ipcp_dir_hash_len()); +} + +static void do_connect(const char * dst, + const char * comp, + qosspec_t qs, + ipcp_msg_t * ret_msg) +{ + log_info("Connecting %s to %s...", comp, dst); + + if (ipcpd.ops->ipcp_connect == NULL) { + log_err("Failed to connect: operation unsupported."); + ret_msg->result = -ENOTSUP; + return; + } + + ret_msg->result = ipcpd.ops->ipcp_connect(dst, comp, qs); + + log_info("Finished connecting."); +} + +static void do_disconnect(const char * dst, + const char * comp, + ipcp_msg_t * ret_msg) +{ + log_info("Disconnecting %s from %s...", comp, dst); + + if (ipcpd.ops->ipcp_disconnect == NULL) { + log_err("Failed to disconnect: operation unsupported."); + ret_msg->result = -ENOTSUP; + return; + } + + ret_msg->result = ipcpd.ops->ipcp_disconnect(dst, comp); + + log_info("Finished disconnecting %s from %s.", comp, dst); +} + +static void do_reg(const uint8_t * hash, + ipcp_msg_t * ret_msg) +{ + + log_info("Registering " HASH_FMT32 "...", HASH_VAL32(hash)); + + if (ipcpd.ops->ipcp_reg == NULL) { + log_err("Failed to register: operation unsupported."); + ret_msg->result = -ENOTSUP; + return; + } + + ret_msg->result = ipcpd.ops->ipcp_reg(hash); + + log_info("Finished registering " HASH_FMT32 ".", HASH_VAL32(hash)); +} + +static void do_unreg(const uint8_t * hash, + ipcp_msg_t * ret_msg) +{ + log_info("Unregistering " HASH_FMT32 "...", HASH_VAL32(hash)); + + if (ipcpd.ops->ipcp_unreg == NULL) { + log_err("Failed to unregister: operation unsupported."); + ret_msg->result = -ENOTSUP; + return; + } + + ret_msg->result = ipcpd.ops->ipcp_unreg(hash); + + log_info("Finished unregistering " HASH_FMT32 ".", HASH_VAL32(hash)); +} + +static void do_query(const uint8_t * hash, + ipcp_msg_t * ret_msg) +{ + /* TODO: Log this operation when IRMd has internal caches. */ + + if (ipcpd.ops->ipcp_query == NULL) { + log_err("Failed to query: operation unsupported."); + ret_msg->result = -ENOTSUP; + return; + } + + if (ipcp_get_state() != IPCP_OPERATIONAL) { + log_dbg("Failed to query: IPCP in state <%s>, need <%s>.", + ipcp_state_str[ipcp_get_state()], + ipcp_state_str[IPCP_OPERATIONAL]); + ret_msg->result = -EIPCPSTATE; + return; + } + + ret_msg->result = ipcpd.ops->ipcp_query(hash); +} + +static void do_flow_alloc(pid_t pid, + int flow_id, + uint8_t * dst, + qosspec_t qs, + const buffer_t * data, + uid_t uid, + ipcp_msg_t * ret_msg) +{ + int fd; + struct ssm_pool * pool = NULL; + + log_info("Allocating flow %d for %d to " HASH_FMT32 ".", + flow_id, pid, HASH_VAL32(dst)); + + if (ipcpd.ops->ipcp_flow_alloc == NULL) { + log_err("Flow allocation failed: operation unsupported."); + ret_msg->result = -ENOTSUP; + return; + } + + if (ipcp_get_state() != IPCP_OPERATIONAL) { + log_err("Failed to enroll: IPCP in state <%s>, need <%s>.", + ipcp_state_str[ipcp_get_state()], + ipcp_state_str[IPCP_OPERATIONAL]); + ret_msg->result = -EIPCPSTATE; + return; + } + + fd = np1_flow_alloc(pid, flow_id); + if (fd < 0) { + log_err("Failed allocating n + 1 fd on flow_id %d: %d", + flow_id, fd); + ret_msg->result = -EFLOWDOWN; + return; + } + + if (uid != 0) { + pool = ssm_pool_open(uid); + if (pool == NULL) { + log_err("Failed to open PUP for uid %d.", uid); + ret_msg->result = -ENOMEM; + return; + } + } + + NP1_SET_POOL(fd, pool); + + ret_msg->result = ipcpd.ops->ipcp_flow_alloc(fd, dst, qs, data); + + log_info("Finished allocating flow %d to " HASH_FMT32 ".", + flow_id, HASH_VAL32(dst)); +} + + +static void do_flow_join(pid_t pid, + int flow_id, + const uint8_t * dst, + uid_t uid, + ipcp_msg_t * ret_msg) +{ + int fd; + struct ssm_pool * pool = NULL; + + log_info("Joining layer " HASH_FMT32 ".", HASH_VAL32(dst)); + + if (ipcpd.ops->ipcp_flow_join == NULL) { + log_err("Failed to join: operation unsupported."); + ret_msg->result = -ENOTSUP; + return; + } + + if (ipcp_get_state() != IPCP_OPERATIONAL) { + log_err("Failed to join: IPCP in state <%s>, need <%s>.", + ipcp_state_str[ipcp_get_state()], + ipcp_state_str[IPCP_OPERATIONAL]); + ret_msg->result = -EIPCPSTATE; + return; + } + + fd = np1_flow_alloc(pid, flow_id); + if (fd < 0) { + log_err("Failed allocating n + 1 fd on flow_id %d.", flow_id); + ret_msg->result = -1; + return; + } + + if (uid != 0) { + pool = ssm_pool_open(uid); + if (pool == NULL) { + log_err("Failed to open PUP for uid %d.", uid); + ret_msg->result = -ENOMEM; + return; + } + } + + NP1_SET_POOL(fd, pool); + + ret_msg->result = ipcpd.ops->ipcp_flow_join(fd, dst); + + log_info("Finished joining layer " HASH_FMT32 ".", HASH_VAL32(dst)); +} + +static void do_flow_alloc_resp(int resp, + int flow_id, + uid_t uid, + const buffer_t * data, + ipcp_msg_t * ret_msg) +{ + int fd = -1; + struct ssm_pool * pool = NULL; + + log_info("Responding %d to alloc on flow_id %d.", resp, flow_id); + + if (ipcpd.ops->ipcp_flow_alloc_resp == NULL) { + log_err("Failed to respond on flow %d: operation unsupported.", + flow_id); + ret_msg->result = -ENOTSUP; + return; + } + + if (ipcp_get_state() != IPCP_OPERATIONAL) { + log_err("Failed to respond to flow %d:" + "IPCP in state <%s>, need <%s>.", + flow_id, + ipcp_state_str[ipcp_get_state()], + ipcp_state_str[IPCP_OPERATIONAL]); + ret_msg->result = -EIPCPSTATE; + return; + } + + fd = np1_flow_resp(flow_id, resp); + if (fd < 0) { + log_warn("Flow_id %d is not known.", flow_id); + ret_msg->result = -1; + return; + } + + if (uid != 0) { + pool = ssm_pool_open(uid); + if (pool == NULL) { + log_err("Failed to open PUP for uid %d.", uid); + ret_msg->result = -ENOMEM; + return; + } + } + + NP1_SET_POOL(fd, pool); + + ret_msg->result = ipcpd.ops->ipcp_flow_alloc_resp(fd, resp, data); + + log_info("Finished responding %d to allocation request.", + ret_msg->result); +} + +static void do_flow_dealloc(int flow_id, + int timeo_sec, + ipcp_msg_t * ret_msg) +{ + int fd; + + log_info("Deallocating flow %d.", flow_id); + + if (ipcpd.ops->ipcp_flow_dealloc == NULL) { + log_err("Failed to dealloc: operation unsupported."); + ret_msg->result = -ENOTSUP; + return; + } + + if (ipcp_get_state() != IPCP_OPERATIONAL) { + log_err("Failed to enroll: IPCP in state <%s>, need <%s>.", + ipcp_state_str[ipcp_get_state()], + ipcp_state_str[IPCP_OPERATIONAL]); + ret_msg->result = -EIPCPSTATE; + return; + } + + fd = np1_flow_dealloc(flow_id, timeo_sec); + if (fd < 0) { + log_warn("Could not deallocate flow_id %d.", flow_id); + ret_msg->result = -1; + return; + } + + ret_msg->result = ipcpd.ops->ipcp_flow_dealloc(fd); + + log_info("Finished deallocating flow %d.", flow_id); +} + +static void * mainloop(void * o) +{ + int sfd; + buffer_t buffer; + ipcp_msg_t * msg; (void) o; while (true) { - ipcp_msg_t ret_msg = IPCP_MSG__INIT; - layer_info_msg_t layer_info = LAYER_INFO_MSG__INIT; - int fd = -1; - struct cmd * cmd; - qosspec_t qs; + ipcp_msg_t ret_msg = IPCP_MSG__INIT; + qosspec_t qs; + struct cmd * cmd; + buffer_t data; ret_msg.code = IPCP_MSG_CODE__IPCP_REPLY; - pthread_mutex_lock(&ipcpi.cmd_lock); + pthread_mutex_lock(&ipcpd.cmd_lock); - pthread_cleanup_push(__cleanup_mutex_unlock, &ipcpi.cmd_lock); + pthread_cleanup_push(__cleanup_mutex_unlock, &ipcpd.cmd_lock); - while (list_is_empty(&ipcpi.cmds)) - pthread_cond_wait(&ipcpi.cmd_cond, &ipcpi.cmd_lock); + while (list_is_empty(&ipcpd.cmds)) + pthread_cond_wait(&ipcpd.cmd_cond, &ipcpd.cmd_lock); - cmd = list_last_entry(&ipcpi.cmds, struct cmd, next); + cmd = list_last_entry(&ipcpd.cmds, struct cmd, next); list_del(&cmd->next); pthread_cleanup_pop(true); @@ -307,334 +857,72 @@ static void * mainloop(void * o) continue; } - tpm_dec(ipcpi.tpm); + tpm_begin_work(ipcpd.tpm); pthread_cleanup_push(__cleanup_close_ptr, &sfd); pthread_cleanup_push(free_msg, msg); + ret_msg.has_result = true; + switch (msg->code) { case IPCP_MSG_CODE__IPCP_BOOTSTRAP: - ret_msg.has_result = true; - - if (ipcpi.ops->ipcp_bootstrap == NULL) { - log_err("Bootstrap unsupported."); - ret_msg.result = -ENOTSUP; - break; - } - - if (ipcp_get_state() != IPCP_INIT) { - log_err("IPCP in wrong state."); - ret_msg.result = -EIPCPSTATE; - break; - } - - conf_msg = msg->conf; - conf.type = conf_msg->ipcp_type; - strcpy(conf.layer_info.layer_name, - conf_msg->layer_info->layer_name); - - switch(conf_msg->ipcp_type) { - case IPCP_LOCAL: - break; - case IPCP_UNICAST: - conf.addr_size = conf_msg->addr_size; - conf.eid_size = conf_msg->eid_size; - conf.max_ttl = conf_msg->max_ttl; - conf.addr_auth_type = conf_msg->addr_auth_type; - conf.routing_type = conf_msg->routing_type; - conf.cong_avoid = conf_msg->cong_avoid; - break; - case IPCP_ETH_DIX: - conf.ethertype = conf_msg->ethertype; - /* FALLTHRU */ - case IPCP_ETH_LLC: - conf.dev = conf_msg->dev; - break; - case IPCP_UDP: - conf.ip_addr = conf_msg->ip_addr; - conf.dns_addr = conf_msg->dns_addr; - conf.port = conf_msg->port; - conf.layer_info.dir_hash_algo = HASH_MD5; - layer_info.dir_hash_algo = HASH_MD5; - break; - case IPCP_BROADCAST: - conf.layer_info.dir_hash_algo = HASH_SHA3_256; - layer_info.dir_hash_algo = HASH_SHA3_256; - break; - default: - log_err("Unknown IPCP type: %d.", - conf_msg->ipcp_type); - ret_msg.result = -EIPCP; - goto exit; /* break from outer switch/case */ - } - - /* UDP and broadcast use fixed hash algorithm. */ - if (conf_msg->ipcp_type != IPCP_UDP && - conf_msg->ipcp_type != IPCP_BROADCAST) { - switch(conf_msg->layer_info->dir_hash_algo) { - case DIR_HASH_SHA3_224: - conf.layer_info.dir_hash_algo = - HASH_SHA3_224; - break; - case DIR_HASH_SHA3_256: - conf.layer_info.dir_hash_algo = - HASH_SHA3_256; - break; - case DIR_HASH_SHA3_384: - conf.layer_info.dir_hash_algo = - HASH_SHA3_384; - break; - case DIR_HASH_SHA3_512: - conf.layer_info.dir_hash_algo = - HASH_SHA3_512; - break; - default: - assert(false); - } - - layer_info.dir_hash_algo = - conf.layer_info.dir_hash_algo; - } - - ret_msg.result = ipcpi.ops->ipcp_bootstrap(&conf); - if (ret_msg.result == 0) { - ret_msg.layer_info = &layer_info; - layer_info.layer_name = - conf.layer_info.layer_name; - } + do_bootstrap(msg->conf, &ret_msg); break; case IPCP_MSG_CODE__IPCP_ENROLL: - ret_msg.has_result = true; - - if (ipcpi.ops->ipcp_enroll == NULL) { - log_err("Enroll unsupported."); - ret_msg.result = -ENOTSUP; - break; - } - - if (ipcp_get_state() != IPCP_INIT) { - log_err("IPCP in wrong state."); - ret_msg.result = -EIPCPSTATE; - break; - } - - ret_msg.result = ipcpi.ops->ipcp_enroll(msg->dst, - &info); - if (ret_msg.result == 0) { - ret_msg.layer_info = &layer_info; - layer_info.dir_hash_algo = info.dir_hash_algo; - layer_info.layer_name = info.layer_name; - } + do_enroll(msg->dst, &ret_msg); break; case IPCP_MSG_CODE__IPCP_CONNECT: - ret_msg.has_result = true; - - if (ipcpi.ops->ipcp_connect == NULL) { - log_err("Connect unsupported."); - ret_msg.result = -ENOTSUP; - break; - } - - qs = msg_to_spec(msg->qosspec); - ret_msg.result = ipcpi.ops->ipcp_connect(msg->dst, - msg->comp, - qs); + qs = qos_spec_msg_to_s(msg->qosspec); + do_connect(msg->dst, msg->comp, qs, &ret_msg); break; case IPCP_MSG_CODE__IPCP_DISCONNECT: - ret_msg.has_result = true; - - if (ipcpi.ops->ipcp_disconnect == NULL) { - log_err("Disconnect unsupported."); - ret_msg.result = -ENOTSUP; - break; - } - - ret_msg.result = ipcpi.ops->ipcp_disconnect(msg->dst, - msg->comp); + do_disconnect(msg->dst, msg->comp, &ret_msg); break; case IPCP_MSG_CODE__IPCP_REG: - ret_msg.has_result = true; - - if (ipcpi.ops->ipcp_reg == NULL) { - log_err("Registration unsupported."); - ret_msg.result = -ENOTSUP; - break; - } - assert(msg->hash.len == ipcp_dir_hash_len()); - - ret_msg.result = - ipcpi.ops->ipcp_reg(msg->hash.data); + do_reg(msg->hash.data, &ret_msg); break; case IPCP_MSG_CODE__IPCP_UNREG: - ret_msg.has_result = true; - - if (ipcpi.ops->ipcp_unreg == NULL) { - log_err("Unregistration unsupported."); - ret_msg.result = -ENOTSUP; - break; - } - assert(msg->hash.len == ipcp_dir_hash_len()); - - ret_msg.result = - ipcpi.ops->ipcp_unreg(msg->hash.data); + do_unreg(msg->hash.data, &ret_msg); break; case IPCP_MSG_CODE__IPCP_QUERY: - ret_msg.has_result = true; - - if (ipcpi.ops->ipcp_query == NULL) { - log_err("Directory query unsupported."); - ret_msg.result = -ENOTSUP; - break; - } - assert(msg->hash.len == ipcp_dir_hash_len()); - - if (ipcp_get_state() != IPCP_OPERATIONAL) { - log_err("IPCP in wrong state."); - ret_msg.result = -EIPCPSTATE; - break; - } - - ret_msg.result = - ipcpi.ops->ipcp_query(msg->hash.data); + do_query(msg->hash.data, &ret_msg); break; case IPCP_MSG_CODE__IPCP_FLOW_ALLOC: - ret_msg.has_result = true; - - if (ipcpi.ops->ipcp_flow_alloc == NULL) { - log_err("Flow allocation unsupported."); - ret_msg.result = -ENOTSUP; - break; - } - assert(msg->hash.len == ipcp_dir_hash_len()); assert(msg->pk.len > 0 ? msg->pk.data != NULL : msg->pk.data == NULL); - - if (ipcp_get_state() != IPCP_OPERATIONAL) { - log_err("IPCP in wrong state."); - ret_msg.result = -EIPCPSTATE; - break; - } - - qs = msg_to_spec(msg->qosspec); - fd = np1_flow_alloc(msg->pid, - msg->flow_id, - qs); - if (fd < 0) { - log_err("Failed allocating fd on flow_id %d.", - msg->flow_id); - ret_msg.result = -1; - break; - } - - ret_msg.result = - ipcpi.ops->ipcp_flow_alloc(fd, - msg->hash.data, - qs, - msg->pk.data, - msg->pk.len); + data.len = msg->pk.len; + data.data = msg->pk.data; + qs = qos_spec_msg_to_s(msg->qosspec); + do_flow_alloc(msg->pid, msg->flow_id, + msg->hash.data, qs, + &data, msg->uid, &ret_msg); break; case IPCP_MSG_CODE__IPCP_FLOW_JOIN: - ret_msg.has_result = true; - - if (ipcpi.ops->ipcp_flow_join == NULL) { - log_err("Broadcast unsupported."); - ret_msg.result = -ENOTSUP; - break; - } - assert(msg->hash.len == ipcp_dir_hash_len()); - - if (ipcp_get_state() != IPCP_OPERATIONAL) { - log_err("IPCP in wrong state."); - ret_msg.result = -EIPCPSTATE; - break; - } - - qs = msg_to_spec(msg->qosspec); - fd = np1_flow_alloc(msg->pid, - msg->flow_id, - qs); - if (fd < 0) { - log_err("Failed allocating fd on flow_id %d.", - msg->flow_id); - ret_msg.result = -1; - break; - } - - ret_msg.result = - ipcpi.ops->ipcp_flow_join(fd, - msg->hash.data, - qs); + do_flow_join(msg->pid, msg->flow_id, + msg->hash.data, msg->uid, &ret_msg); break; case IPCP_MSG_CODE__IPCP_FLOW_ALLOC_RESP: - ret_msg.has_result = true; - if (ipcpi.ops->ipcp_flow_alloc_resp == NULL) { - log_err("Flow_alloc_resp unsupported."); - ret_msg.result = -ENOTSUP; - break; - } - - if (ipcp_get_state() != IPCP_OPERATIONAL) { - log_err("IPCP in wrong state."); - ret_msg.result = -EIPCPSTATE; - break; - } - - if (!msg->response) { - fd = np1_flow_resp(msg->flow_id); - if (fd < 0) { - log_warn("Port_id %d is not known.", - msg->flow_id); - ret_msg.result = -1; - break; - } - } - assert(msg->pk.len > 0 ? msg->pk.data != NULL - : msg->pk.data == NULL); - - ret_msg.result = - ipcpi.ops->ipcp_flow_alloc_resp(fd, - msg->response, - msg->pk.data, - msg->pk.len); + : msg->pk.data == NULL); + data.len = msg->pk.len; + data.data = msg->pk.data; + do_flow_alloc_resp(msg->response, msg->flow_id, + msg->uid, &data, &ret_msg); break; case IPCP_MSG_CODE__IPCP_FLOW_DEALLOC: - ret_msg.has_result = true; - if (ipcpi.ops->ipcp_flow_dealloc == NULL) { - log_err("Flow deallocation unsupported."); - ret_msg.result = -ENOTSUP; - break; - } - - if (ipcp_get_state() != IPCP_OPERATIONAL) { - log_err("IPCP in wrong state."); - ret_msg.result = -EIPCPSTATE; - break; - } - - fd = np1_flow_dealloc(msg->flow_id, msg->timeo_sec); - if (fd < 0) { - log_warn("Could not deallocate flow_id %d.", - msg->flow_id); - ret_msg.result = -1; - break; - } - - ret_msg.result = - ipcpi.ops->ipcp_flow_dealloc(fd); + do_flow_dealloc(msg->flow_id, msg->timeo_sec, &ret_msg); break; default: - ret_msg.has_result = true; - ret_msg.result = -1; - log_err("Don't know that message code"); + ret_msg.result = -1; + log_err("Unknown message code: %d.", msg->code); break; } - exit: + pthread_cleanup_pop(true); pthread_cleanup_pop(false); @@ -642,7 +930,7 @@ static void * mainloop(void * o) if (buffer.len == 0) { log_err("Failed to pack reply message"); close(sfd); - tpm_inc(ipcpi.tpm); + tpm_end_work(ipcpd.tpm); continue; } @@ -650,21 +938,25 @@ static void * mainloop(void * o) if (buffer.data == NULL) { log_err("Failed to create reply buffer."); close(sfd); - tpm_inc(ipcpi.tpm); + tpm_end_work(ipcpd.tpm); continue; } ipcp_msg__pack(&ret_msg, buffer.data); + if (ret_msg.layer_info != NULL) + layer_info_msg__free_unpacked(ret_msg.layer_info, NULL); + + pthread_cleanup_push(free, buffer.data) pthread_cleanup_push(__cleanup_close_ptr, &sfd); if (write(sfd, buffer.data, buffer.len) == -1) log_warn("Failed to send reply message"); - free(buffer.data); - pthread_cleanup_pop(true); + pthread_cleanup_pop(true); /* close sfd */ + pthread_cleanup_pop(true); /* free buffer.data */ - tpm_inc(ipcpi.tpm); + tpm_end_work(ipcpd.tpm); } return (void *) 0; @@ -683,10 +975,10 @@ static int parse_args(int argc, if (atoi(argv[1]) == 0) return -1; - ipcpi.irmd_pid = atoi(argv[1]); + ipcpd.irmd_pid = atoi(argv[1]); /* argument 2: IPCP name */ - ipcpi.name = argv[2]; + ipcpd.name = argv[2]; /* argument 3: syslog */ if (argv[3] != NULL) @@ -702,149 +994,176 @@ int ipcp_init(int argc, { bool log; pthread_condattr_t cattr; - int ret = -1; if (parse_args(argc, argv, &log)) return -1; log_init(log); - ipcpi.irmd_fd = -1; - ipcpi.state = IPCP_NULL; - ipcpi.type = type; + ipcpd.type = type; #if defined (__linux__) prctl(PR_SET_TIMERSLACK, IPCP_LINUX_SLACK_NS, 0, 0, 0); #endif - ipcpi.sock_path = ipcp_sock_path(getpid()); - if (ipcpi.sock_path == NULL) + ipcpd.sock_path = sock_path(getpid(), IPCP_SOCK_PATH_PREFIX); + if (ipcpd.sock_path == NULL) goto fail_sock_path; - ipcpi.sockfd = server_socket_open(ipcpi.sock_path); - if (ipcpi.sockfd < 0) { - log_err("Could not open server socket."); + ipcpd.sockfd = server_socket_open(ipcpd.sock_path); + if (ipcpd.sockfd < 0) { + log_err("Failed to open server socket at %s.", + ipcpd.sock_path); goto fail_serv_sock; } - ipcpi.ops = ops; + ipcpd.ops = ops; - if (pthread_mutex_init(&ipcpi.state_mtx, NULL)) { - log_err("Could not create mutex."); + if (pthread_mutex_init(&ipcpd.state_mtx, NULL)) { + log_err("Failed to create mutex."); goto fail_state_mtx; } if (pthread_condattr_init(&cattr)) { - log_err("Could not create condattr."); + log_err("Failed to create condattr."); goto fail_cond_attr; } #ifndef __APPLE__ pthread_condattr_setclock(&cattr, PTHREAD_COND_CLOCK); #endif - if (pthread_cond_init(&ipcpi.state_cond, &cattr)) { - log_err("Could not init condvar."); + if (pthread_cond_init(&ipcpd.state_cond, &cattr)) { + log_err("Failed to init condvar."); goto fail_state_cond; } - if (pthread_mutex_init(&ipcpi.alloc_lock, NULL)) { + if (pthread_mutex_init(&ipcpd.alloc_lock, NULL)) { log_err("Failed to init mutex."); goto fail_alloc_lock; } - if (pthread_cond_init(&ipcpi.alloc_cond, &cattr)) { + if (pthread_cond_init(&ipcpd.alloc_cond, &cattr)) { log_err("Failed to init convar."); goto fail_alloc_cond; } - if (pthread_mutex_init(&ipcpi.cmd_lock, NULL)) { + if (pthread_mutex_init(&ipcpd.cmd_lock, NULL)) { log_err("Failed to init mutex."); goto fail_cmd_lock; } - if (pthread_cond_init(&ipcpi.cmd_cond, &cattr)) { + if (pthread_cond_init(&ipcpd.cmd_cond, &cattr)) { log_err("Failed to init convar."); goto fail_cmd_cond; } - if (rib_init(ipcpi.name)) { + if (rib_init(ipcpd.name)) { log_err("Failed to initialize RIB."); goto fail_rib_init; } - list_head_init(&ipcpi.cmds); + if (rib_reg(IPCP_INFO, &r_ops)) { + log_err("Failed to register rib."); + goto fail_rib_reg; + } + + list_head_init(&ipcpd.cmds); + + ipcpd.tpm = tpm_create(IPCP_MIN_THREADS, IPCP_ADD_THREADS, + mainloop, NULL); + if (ipcpd.tpm == NULL) { + log_err("Failed to create threadpool manager."); + goto fail_tpm_create; + } - ipcpi.alloc_id = -1; + ipcpd.alloc_id = -1; + + memset(&np1, 0, sizeof(np1)); pthread_condattr_destroy(&cattr); + ipcp_set_state(IPCP_INIT); + + log_info("IPCP %s %d initialized.", ipcp_type_str[ipcpd.type], + getpid()); + return 0; + fail_tpm_create: + rib_unreg(IPCP_INFO); + fail_rib_reg: + rib_fini(); fail_rib_init: - pthread_cond_destroy(&ipcpi.cmd_cond); + pthread_cond_destroy(&ipcpd.cmd_cond); fail_cmd_cond: - pthread_mutex_destroy(&ipcpi.cmd_lock); + pthread_mutex_destroy(&ipcpd.cmd_lock); fail_cmd_lock: - pthread_cond_destroy(&ipcpi.alloc_cond); + pthread_cond_destroy(&ipcpd.alloc_cond); fail_alloc_cond: - pthread_mutex_destroy(&ipcpi.alloc_lock); + pthread_mutex_destroy(&ipcpd.alloc_lock); fail_alloc_lock: - pthread_cond_destroy(&ipcpi.state_cond); + pthread_cond_destroy(&ipcpd.state_cond); fail_state_cond: pthread_condattr_destroy(&cattr); fail_cond_attr: - pthread_mutex_destroy(&ipcpi.state_mtx); + pthread_mutex_destroy(&ipcpd.state_mtx); fail_state_mtx: - close(ipcpi.sockfd); + close(ipcpd.sockfd); fail_serv_sock: - free(ipcpi.sock_path); + free(ipcpd.sock_path); fail_sock_path: - return ret; + return -1; } -int ipcp_boot() +int ipcp_start(void) { - sigset_t sigset; + sigset_t sigset; + struct ipcp_info info; + sigemptyset(&sigset); sigaddset(&sigset, SIGINT); sigaddset(&sigset, SIGQUIT); sigaddset(&sigset, SIGHUP); sigaddset(&sigset, SIGPIPE); - ipcpi.tpm = tpm_create(IPCP_MIN_THREADS, IPCP_ADD_THREADS, - mainloop, NULL); - if (ipcpi.tpm == NULL) - goto fail_tpm_create; - pthread_sigmask(SIG_BLOCK, &sigset, NULL); - if (tpm_start(ipcpi.tpm)) - goto fail_tpm_start; + info.pid = getpid(); + info.type = ipcpd.type; + strcpy(info.name, ipcpd.name); + info.state = IPCP_BOOT; - ipcp_set_state(IPCP_INIT); + ipcp_set_state(IPCP_BOOT); - if (rib_reg(IPCP_INFO, &r_ops)) - goto fail_rib_reg; + if (tpm_start(ipcpd.tpm)) { + log_err("Failed to start threadpool manager."); + goto fail_tpm_start; + } - if (pthread_create(&ipcpi.acceptor, NULL, acceptloop, NULL)) { + if (pthread_create(&ipcpd.acceptor, NULL, acceptloop, NULL)) { log_err("Failed to create acceptor thread."); - ipcp_set_state(IPCP_NULL); goto fail_acceptor; } - return 0; + if (ipcp_create_r(&info)) { + log_err("Failed to notify IRMd we are initialized."); + goto fail_create_r; + } + return 0; + fail_create_r: + pthread_cancel(ipcpd.acceptor); + pthread_join(ipcpd.acceptor, NULL); fail_acceptor: - rib_unreg(IPCP_INFO); - fail_rib_reg: - tpm_stop(ipcpi.tpm); + tpm_stop(ipcpd.tpm); fail_tpm_start: - tpm_destroy(ipcpi.tpm); - fail_tpm_create: + tpm_destroy(ipcpd.tpm); + ipcp_set_state(IPCP_INIT); + ipcp_create_r(&info); return -1; } -void ipcp_shutdown() +void ipcp_sigwait(void) { siginfo_t info; @@ -859,7 +1178,7 @@ void ipcp_shutdown() sigaddset(&sigset, SIGTERM); sigaddset(&sigset, SIGPIPE); - while(ipcp_get_state() != IPCP_NULL && + while(ipcp_get_state() != IPCP_INIT && ipcp_get_state() != IPCP_SHUTDOWN) { #ifdef __APPLE__ if (sigwait(&sigset, &sig) < 0) { @@ -873,7 +1192,7 @@ void ipcp_shutdown() #ifdef __APPLE__ memset(&info, 0, sizeof(info)); info.si_signo = sig; - info.si_pid = ipcpi.irmd_pid; + info.si_pid = ipcpd.irmd_pid; #endif switch(info.si_signo) { case SIGINT: @@ -883,9 +1202,9 @@ void ipcp_shutdown() case SIGHUP: /* FALLTHRU */ case SIGQUIT: - if (info.si_pid == ipcpi.irmd_pid) { - if (ipcp_get_state() == IPCP_INIT) - ipcp_set_state(IPCP_NULL); + if (info.si_pid == ipcpd.irmd_pid) { + if (ipcp_get_state() == IPCP_BOOT) + ipcp_set_state(IPCP_INIT); if (ipcp_get_state() == IPCP_OPERATIONAL) ipcp_set_state(IPCP_SHUTDOWN); @@ -898,93 +1217,72 @@ void ipcp_shutdown() continue; } } +} - pthread_cancel(ipcpi.acceptor); +void ipcp_stop(void) +{ + log_info("IPCP %d shutting down.", getpid()); - pthread_join(ipcpi.acceptor, NULL); - tpm_stop(ipcpi.tpm); - tpm_destroy(ipcpi.tpm); + pthread_cancel(ipcpd.acceptor); + pthread_join(ipcpd.acceptor, NULL); - log_info("IPCP %d shutting down.", getpid()); + tpm_stop(ipcpd.tpm); + + ipcp_set_state(IPCP_INIT); } -void ipcp_fini() +void ipcp_fini(void) { + tpm_destroy(ipcpd.tpm); + + rib_unreg(IPCP_INFO); + rib_fini(); - close(ipcpi.sockfd); - if (unlink(ipcpi.sock_path)) - log_warn("Could not unlink %s.", ipcpi.sock_path); + close(ipcpd.sockfd); + if (unlink(ipcpd.sock_path)) + log_warn("Could not unlink %s.", ipcpd.sock_path); - free(ipcpi.sock_path); + free(ipcpd.sock_path); - pthread_cond_destroy(&ipcpi.state_cond); - pthread_mutex_destroy(&ipcpi.state_mtx); - pthread_cond_destroy(&ipcpi.alloc_cond); - pthread_mutex_destroy(&ipcpi.alloc_lock); - pthread_cond_destroy(&ipcpi.cmd_cond); - pthread_mutex_destroy(&ipcpi.cmd_lock); + pthread_cond_destroy(&ipcpd.state_cond); + pthread_mutex_destroy(&ipcpd.state_mtx); + pthread_cond_destroy(&ipcpd.alloc_cond); + pthread_mutex_destroy(&ipcpd.alloc_lock); + pthread_cond_destroy(&ipcpd.cmd_cond); + pthread_mutex_destroy(&ipcpd.cmd_lock); log_info("IPCP %d out.", getpid()); log_fini(); + + ipcpd.state = IPCP_NULL; } void ipcp_set_state(enum ipcp_state state) { - pthread_mutex_lock(&ipcpi.state_mtx); + pthread_mutex_lock(&ipcpd.state_mtx); - ipcpi.state = state; + ipcpd.state = state; - pthread_cond_broadcast(&ipcpi.state_cond); - pthread_mutex_unlock(&ipcpi.state_mtx); + pthread_cond_broadcast(&ipcpd.state_cond); + pthread_mutex_unlock(&ipcpd.state_mtx); } -enum ipcp_state ipcp_get_state() +enum ipcp_state ipcp_get_state(void) { enum ipcp_state state; - pthread_mutex_lock(&ipcpi.state_mtx); + pthread_mutex_lock(&ipcpd.state_mtx); - state = ipcpi.state; + state = ipcpd.state; - pthread_mutex_unlock(&ipcpi.state_mtx); + pthread_mutex_unlock(&ipcpd.state_mtx); return state; } -int ipcp_wait_state(enum ipcp_state state, - const struct timespec * timeout) -{ - struct timespec abstime; - int ret = 0; - - clock_gettime(PTHREAD_COND_CLOCK, &abstime); - ts_add(&abstime, timeout, &abstime); - - pthread_mutex_lock(&ipcpi.state_mtx); - - pthread_cleanup_push(__cleanup_mutex_unlock, &ipcpi.state_mtx); - - while (ipcpi.state != state - && ipcpi.state != IPCP_SHUTDOWN - && ipcpi.state != IPCP_NULL - && ret != -ETIMEDOUT) { - if (timeout == NULL) - ret = -pthread_cond_wait(&ipcpi.state_cond, - &ipcpi.state_mtx); - else - ret = -pthread_cond_timedwait(&ipcpi.state_cond, - &ipcpi.state_mtx, - &abstime); - } - - pthread_cleanup_pop(true); - - return ret; -} - void ipcp_lock_to_core(void) { #if defined(__linux__) && !defined(DISABLE_CORE_LOCK) diff --git a/src/ipcpd/ipcp.h b/src/ipcpd/ipcp.h index eff2ae12..e8c31a32 100644 --- a/src/ipcpd/ipcp.h +++ b/src/ipcpd/ipcp.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * IPC process structure * @@ -26,22 +26,18 @@ #include <ouroboros/hash.h> #include <ouroboros/ipcp.h> #include <ouroboros/list.h> +#include <ouroboros/protobuf.h> +#include <ouroboros/qos.h> #include <ouroboros/sockets.h> #include <ouroboros/tpm.h> #include <pthread.h> #include <time.h> -enum ipcp_state { - IPCP_NULL = 0, - IPCP_INIT, - /* Layer name must be set for states below. */ - IPCP_OPERATIONAL, - IPCP_SHUTDOWN -}; +#define ipcp_dir_hash_strlen() (ipcp_dir_hash_len() * 2) struct ipcp_ops { - int (* ipcp_bootstrap)(const struct ipcp_config * conf); + int (* ipcp_bootstrap)(struct ipcp_config * conf); int (* ipcp_enroll)(const char * dst, struct layer_info * info); @@ -59,84 +55,59 @@ struct ipcp_ops { int (* ipcp_query)(const uint8_t * hash); - int (* ipcp_flow_alloc)(int fd, - const uint8_t * dst, - qosspec_t qs, - const void * data, - size_t len); + int (* ipcp_flow_alloc)(int fd, + const uint8_t * dst, + qosspec_t qs, + const buffer_t * data); int (* ipcp_flow_join)(int fd, - const uint8_t * dst, - qosspec_t qs); + const uint8_t * dst); - int (* ipcp_flow_alloc_resp)(int fd, - int response, - const void * data, - size_t len); + int (* ipcp_flow_alloc_resp)(int fd, + int response, + const buffer_t * data); int (* ipcp_flow_dealloc)(int fd); }; -#define ipcp_dir_hash_strlen() (hash_len(ipcpi.dir_hash_algo) * 2) -#define ipcp_dir_hash_len() (hash_len(ipcpi.dir_hash_algo)) - -extern struct ipcp { - pid_t irmd_pid; - char * name; - - enum ipcp_type type; - char * layer_name; - - uint64_t dt_addr; - - enum hash_algo dir_hash_algo; - - struct ipcp_ops * ops; - int irmd_fd; - - enum ipcp_state state; - pthread_rwlock_t state_lock; - pthread_mutex_t state_mtx; - pthread_cond_t state_cond; - - int sockfd; - char * sock_path; - - struct list_head cmds; - pthread_cond_t cmd_cond; - pthread_mutex_t cmd_lock; - - int alloc_id; - pthread_cond_t alloc_cond; - pthread_mutex_t alloc_lock; - - struct tpm * tpm; - - pthread_t acceptor; -} ipcpi; - int ipcp_init(int argc, char ** argv, struct ipcp_ops * ops, enum ipcp_type type); -int ipcp_boot(void); +int ipcp_start(void); -void ipcp_shutdown(void); +void ipcp_sigwait(void); + +void ipcp_stop(void); void ipcp_fini(void); +enum ipcp_type ipcp_get_type(void); + +const char * ipcp_get_name(void); + +/* TODO: Only specify hash algorithm in directory policy */ +void ipcp_set_dir_hash_algo(enum hash_algo algo); + void ipcp_set_state(enum ipcp_state state); enum ipcp_state ipcp_get_state(void); -int ipcp_wait_state(enum ipcp_state state, - const struct timespec * timeout); +/* Helper functions to handle races during flow allocation */ +int ipcp_wait_flow_req_arr(const uint8_t * dst, + qosspec_t qs, + time_t mpl, + const buffer_t * data); + +int ipcp_wait_flow_resp(const int fd); -int ipcp_parse_arg(int argc, - char * argv[]); /* Helper functions for directory entries, could be moved */ +size_t ipcp_dir_hash_len(void); + +int ipcp_get_layer_name(char * layer); + uint8_t * ipcp_hash_dup(const uint8_t * hash); void ipcp_hash_str(char buf[], diff --git a/src/ipcpd/local/CMakeLists.txt b/src/ipcpd/local/CMakeLists.txt index a84f4f1b..0da4d47a 100644 --- a/src/ipcpd/local/CMakeLists.txt +++ b/src/ipcpd/local/CMakeLists.txt @@ -1,32 +1,19 @@ -get_filename_component(CURRENT_SOURCE_PARENT_DIR - ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) -get_filename_component(CURRENT_BINARY_PARENT_DIR - ${CMAKE_CURRENT_BINARY_DIR} DIRECTORY) +# Local IPCP build configuration -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) +set(IPCP_LOCAL_TARGET ipcpd-local) -include_directories(${CURRENT_SOURCE_PARENT_DIR}) -include_directories(${CURRENT_BINARY_PARENT_DIR}) +add_executable(${IPCP_LOCAL_TARGET} + main.c + ${IPCP_SOURCES} +) -include_directories(${CMAKE_SOURCE_DIR}/include) -include_directories(${CMAKE_BINARY_DIR}/include) +target_include_directories(${IPCP_LOCAL_TARGET} PRIVATE ${IPCP_INCLUDE_DIRS}) +target_link_libraries(${IPCP_LOCAL_TARGET} PRIVATE ouroboros-dev) -set(IPCP_LOCAL_TARGET ipcpd-local CACHE INTERNAL "") +ouroboros_target_debug_definitions(${IPCP_LOCAL_TARGET}) -set(LOCAL_SOURCES - # Add source files here - ${CMAKE_CURRENT_SOURCE_DIR}/main.c) +if(IPCP_LOCAL_POLLING) + target_compile_definitions(${IPCP_LOCAL_TARGET} PRIVATE CONFIG_IPCP_LOCAL_POLLING) +endif() -add_executable(ipcpd-local ${LOCAL_SOURCES} ${IPCP_SOURCES}) -target_link_libraries(ipcpd-local LINK_PUBLIC ouroboros-common ouroboros-dev) - -include(AddCompileFlags) -if (CMAKE_BUILD_TYPE MATCHES "Debug*") - add_compile_flags(ipcpd-local -DCONFIG_OUROBOROS_DEBUG) -endif () - -install(TARGETS ipcpd-local RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}) - -# Enable once ipcp-local has tests -# add_subdirectory(tests) +install(TARGETS ${IPCP_LOCAL_TARGET} RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}) diff --git a/src/ipcpd/local/main.c b/src/ipcpd/local/main.c index 9c62c3cc..5372197f 100644 --- a/src/ipcpd/local/main.c +++ b/src/ipcpd/local/main.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Local IPC process * @@ -40,6 +40,7 @@ #include <ouroboros/local-dev.h> #include "ipcp.h" +#include "np1.h" #include "shim-data.h" #include <string.h> @@ -48,10 +49,7 @@ #include <sys/wait.h> #include <assert.h> -#define THIS_TYPE IPCP_LOCAL -#define ALLOC_TIMEOUT 10 /* ms */ - -struct ipcp ipcpi; +#define THIS_TYPE IPCP_LOCAL struct { struct shim_data * shim_data; @@ -72,117 +70,120 @@ static int local_data_init(void) local_data.flows = fset_create(); if (local_data.flows == NULL) - return -ENFILE; + goto fail_fset; local_data.fq = fqueue_create(); - if (local_data.fq == NULL) { - fset_destroy(local_data.flows); - return -ENOMEM; - } + if (local_data.fq == NULL) + goto fail_fqueue; local_data.shim_data = shim_data_create(); - if (local_data.shim_data == NULL) { - fqueue_destroy(local_data.fq); - fset_destroy(local_data.flows); - return -ENOMEM; - } + if (local_data.shim_data == NULL) + goto fail_shim_data; - pthread_rwlock_init(&local_data.lock, NULL); + if (pthread_rwlock_init(&local_data.lock, NULL) < 0) + goto fail_rwlock_init; return 0; + + fail_rwlock_init: + shim_data_destroy(local_data.shim_data); + fail_shim_data: + fqueue_destroy(local_data.fq); + fail_fqueue: + fset_destroy(local_data.flows); + fail_fset: + return -ENOMEM; } static void local_data_fini(void){ + pthread_rwlock_destroy(&local_data.lock); shim_data_destroy(local_data.shim_data); - fset_destroy(local_data.flows); fqueue_destroy(local_data.fq); - pthread_rwlock_destroy(&local_data.lock); + fset_destroy(local_data.flows); } -static void * ipcp_local_packet_loop(void * o) +static void * local_ipcp_packet_loop(void * o) { + int src_fd; + int dst_fd; + struct timespec * timeout; +#ifdef CONFIG_IPCP_LOCAL_POLLING + struct timespec ts_poll = {0, 0}; +#endif (void) o; ipcp_lock_to_core(); - while (true) { - int fd; - ssize_t idx; +#ifdef CONFIG_IPCP_LOCAL_POLLING + timeout = &ts_poll; /* Spin poll with zero timeout */ +#else + timeout = NULL; /* Block until event */ +#endif - fevent(local_data.flows, local_data.fq, NULL); + while (true) { + fevent(local_data.flows, local_data.fq, timeout); - while ((fd = fqueue_next(local_data.fq)) >= 0) { + while ((src_fd = fqueue_next(local_data.fq)) >= 0) { if (fqueue_type(local_data.fq) != FLOW_PKT) continue; - idx = local_flow_read(fd); - if (idx < 0) - continue; - - assert(idx < (SHM_BUFFER_SIZE)); - pthread_rwlock_rdlock(&local_data.lock); - fd = local_data.in_out[fd]; + dst_fd = local_data.in_out[src_fd]; pthread_rwlock_unlock(&local_data.lock); - if (fd != -1) - local_flow_write(fd, idx); + if (dst_fd == -1) + continue; + + local_flow_transfer(src_fd, dst_fd, + NP1_GET_POOL(src_fd), + NP1_GET_POOL(dst_fd)); } } return (void *) 0; } -static int ipcp_local_bootstrap(const struct ipcp_config * conf) +static int local_ipcp_bootstrap(struct ipcp_config * conf) { + assert(conf); assert(conf->type == THIS_TYPE); - ipcpi.dir_hash_algo = conf->layer_info.dir_hash_algo; - ipcpi.layer_name = strdup(conf->layer_info.layer_name); - if (ipcpi.layer_name == NULL) { - log_err("Failed to set layer name"); - return -ENOMEM; - } - - ipcp_set_state(IPCP_OPERATIONAL); + (void) conf; if (pthread_create(&local_data.packet_loop, NULL, - ipcp_local_packet_loop, NULL)) { + local_ipcp_packet_loop, NULL)) { + log_err("Failed to create pthread: %s", strerror(errno)); ipcp_set_state(IPCP_INIT); return -1; } - log_info("Bootstrapped local IPCP with pid %d.", getpid()); - return 0; } -static int ipcp_local_reg(const uint8_t * hash) +static int local_ipcp_reg(const uint8_t * hash) { if (shim_data_reg_add_entry(local_data.shim_data, hash)) { - log_dbg("Failed to add " HASH_FMT " to local registry.", - HASH_VAL(hash)); + log_err("Failed to add " HASH_FMT32 " to local registry.", + HASH_VAL32(hash)); return -1; } - log_info("Registered " HASH_FMT ".", HASH_VAL(hash)); - return 0; } -static int ipcp_local_unreg(const uint8_t * hash) +static int local_ipcp_unreg(const uint8_t * hash) { shim_data_reg_del_entry(local_data.shim_data, hash); - log_info("Unregistered " HASH_FMT ".", HASH_VAL(hash)); + log_info("Unregistered " HASH_FMT32 ".", HASH_VAL32(hash)); return 0; } -static int ipcp_local_query(const uint8_t * hash) +static int local_ipcp_query(const uint8_t * hash) { int ret; @@ -191,41 +192,19 @@ static int ipcp_local_query(const uint8_t * hash) return ret; } -static int ipcp_local_flow_alloc(int fd, - const uint8_t * dst, - qosspec_t qs, - const void * data, - size_t len) +static int local_ipcp_flow_alloc(int fd, + const uint8_t * dst, + qosspec_t qs, + const buffer_t * data) { - struct timespec ts = {0, ALLOC_TIMEOUT * MILLION}; - struct timespec abstime; - int out_fd = -1; + int out_fd = -1; - log_dbg("Allocating flow to " HASH_FMT " on fd %d.", HASH_VAL(dst), fd); + log_dbg("Allocating flow to " HASH_FMT32 " on fd %d.", + HASH_VAL32(dst), fd); assert(dst); - clock_gettime(PTHREAD_COND_CLOCK, &abstime); - - pthread_mutex_lock(&ipcpi.alloc_lock); - - while (ipcpi.alloc_id != -1 && ipcp_get_state() == IPCP_OPERATIONAL) { - ts_add(&abstime, &ts, &abstime); - pthread_cond_timedwait(&ipcpi.alloc_cond, - &ipcpi.alloc_lock, - &abstime); - } - - if (ipcp_get_state() != IPCP_OPERATIONAL) { - log_dbg("Won't allocate over non-operational IPCP."); - pthread_mutex_unlock(&ipcpi.alloc_lock); - return -1; - } - - assert(ipcpi.alloc_id == -1); - - out_fd = ipcp_flow_req_arr(dst, ipcp_dir_hash_len(), qs, data, len); + out_fd = ipcp_wait_flow_req_arr(dst, qs, IPCP_LOCAL_MPL, data); if (out_fd < 0) { - pthread_mutex_unlock(&ipcpi.alloc_lock); log_dbg("Flow allocation failed: %d", out_fd); return -1; } @@ -237,11 +216,6 @@ static int ipcp_local_flow_alloc(int fd, pthread_rwlock_unlock(&local_data.lock); - ipcpi.alloc_id = out_fd; - pthread_cond_broadcast(&ipcpi.alloc_cond); - - pthread_mutex_unlock(&ipcpi.alloc_lock); - fset_add(local_data.flows, fd); log_info("Pending local allocation request on fd %d.", fd); @@ -249,39 +223,21 @@ static int ipcp_local_flow_alloc(int fd, return 0; } -static int ipcp_local_flow_alloc_resp(int fd, - int response, - const void * data, - size_t len) +static int local_ipcp_flow_alloc_resp(int fd, + int response, + const buffer_t * data) { - struct timespec ts = {0, ALLOC_TIMEOUT * MILLION}; - struct timespec abstime; - int out_fd = -1; - - clock_gettime(PTHREAD_COND_CLOCK, &abstime); + struct timespec wait = TIMESPEC_INIT_MS(1); + time_t mpl = IPCP_LOCAL_MPL; + int out_fd; - pthread_mutex_lock(&ipcpi.alloc_lock); - - while (ipcpi.alloc_id != fd && ipcp_get_state() == IPCP_OPERATIONAL) { - ts_add(&abstime, &ts, &abstime); - pthread_cond_timedwait(&ipcpi.alloc_cond, - &ipcpi.alloc_lock, - &abstime); - } - - if (ipcp_get_state() != IPCP_OPERATIONAL) { - pthread_mutex_unlock(&ipcpi.alloc_lock); + if (ipcp_wait_flow_resp(fd) < 0) { + log_err("Failed waiting for IRMd response."); return -1; } - ipcpi.alloc_id = -1; - pthread_cond_broadcast(&ipcpi.alloc_cond); - - pthread_mutex_unlock(&ipcpi.alloc_lock); - - pthread_rwlock_wrlock(&local_data.lock); - - if (response) { + if (response < 0) { + pthread_rwlock_wrlock(&local_data.lock); if (local_data.in_out[fd] != -1) local_data.in_out[local_data.in_out[fd]] = fd; local_data.in_out[fd] = -1; @@ -289,25 +245,38 @@ static int ipcp_local_flow_alloc_resp(int fd, return 0; } + pthread_rwlock_rdlock(&local_data.lock); + out_fd = local_data.in_out[fd]; if (out_fd == -1) { pthread_rwlock_unlock(&local_data.lock); - return -1; + log_dbg("Potential race detected"); + nanosleep(&wait, NULL); + pthread_rwlock_rdlock(&local_data.lock); + out_fd = local_data.in_out[fd]; } pthread_rwlock_unlock(&local_data.lock); + if (out_fd == -1) { + log_err("Invalid out_fd."); + return -1; + } + fset_add(local_data.flows, fd); - if (ipcp_flow_alloc_reply(out_fd, response, data, len) < 0) + if (ipcp_flow_alloc_reply(out_fd, response, mpl, data) < 0) { + log_err("Failed to reply to allocation"); + fset_del(local_data.flows, fd); return -1; + } log_info("Flow allocation completed, fds (%d, %d).", out_fd, fd); return 0; } -static int ipcp_local_flow_dealloc(int fd) +static int local_ipcp_flow_dealloc(int fd) { assert(!(fd < 0)); @@ -321,7 +290,7 @@ static int ipcp_local_flow_dealloc(int fd) pthread_rwlock_unlock(&local_data.lock); - flow_dealloc(fd); + ipcp_flow_dealloc(fd); log_info("Flow with fd %d deallocated.", fd); @@ -329,60 +298,54 @@ static int ipcp_local_flow_dealloc(int fd) } static struct ipcp_ops local_ops = { - .ipcp_bootstrap = ipcp_local_bootstrap, + .ipcp_bootstrap = local_ipcp_bootstrap, .ipcp_enroll = NULL, .ipcp_connect = NULL, .ipcp_disconnect = NULL, - .ipcp_reg = ipcp_local_reg, - .ipcp_unreg = ipcp_local_unreg, - .ipcp_query = ipcp_local_query, - .ipcp_flow_alloc = ipcp_local_flow_alloc, + .ipcp_reg = local_ipcp_reg, + .ipcp_unreg = local_ipcp_unreg, + .ipcp_query = local_ipcp_query, + .ipcp_flow_alloc = local_ipcp_flow_alloc, .ipcp_flow_join = NULL, - .ipcp_flow_alloc_resp = ipcp_local_flow_alloc_resp, - .ipcp_flow_dealloc = ipcp_local_flow_dealloc + .ipcp_flow_alloc_resp = local_ipcp_flow_alloc_resp, + .ipcp_flow_dealloc = local_ipcp_flow_dealloc }; int main(int argc, char * argv[]) { - if (ipcp_init(argc, argv, &local_ops, THIS_TYPE) < 0) - goto fail_init; - if (local_data_init() < 0) { log_err("Failed to init local data."); goto fail_data_init; } - if (ipcp_boot() < 0) { - log_err("Failed to boot IPCP."); - goto fail_boot; - } + if (ipcp_init(argc, argv, &local_ops, THIS_TYPE) < 0) + goto fail_init; - if (ipcp_create_r(0)) { - log_err("Failed to notify IRMd we are initialized."); - goto fail_create_r; + if (ipcp_start() < 0) { + log_err("Failed to start IPCP."); + goto fail_start; } - ipcp_shutdown(); + ipcp_sigwait(); if (ipcp_get_state() == IPCP_SHUTDOWN) { pthread_cancel(local_data.packet_loop); pthread_join(local_data.packet_loop, NULL); } - local_data_fini(); + ipcp_stop(); ipcp_fini(); - exit(EXIT_SUCCESS); - fail_create_r: - ipcp_set_state(IPCP_NULL); - ipcp_shutdown(); - fail_boot: local_data_fini(); - fail_data_init: + + exit(EXIT_SUCCESS); + + fail_start: ipcp_fini(); fail_init: - ipcp_create_r(-1); + local_data_fini(); + fail_data_init: exit(EXIT_FAILURE); } diff --git a/src/ipcpd/np1.h b/src/ipcpd/np1.h new file mode 100644 index 00000000..a3f21200 --- /dev/null +++ b/src/ipcpd/np1.h @@ -0,0 +1,41 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * N+1 flow pool tracking for IPCPs + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#ifndef OUROBOROS_IPCPD_NP1_H +#define OUROBOROS_IPCPD_NP1_H + +#include "config.h" + +#include <ouroboros/ssm_pool.h> + +#define NP1_LOAD(ptr) (__atomic_load_n((ptr), __ATOMIC_ACQUIRE)) +#define NP1_STORE(ptr, v) (__atomic_store_n((ptr), (v), __ATOMIC_RELEASE)) +#define NP1_GET_POOL(fd) (NP1_LOAD(&np1.pool[(fd)])) +#define NP1_SET_POOL(fd, p) (NP1_STORE(&np1.pool[(fd)], (p))) + +struct np1_state { + struct ssm_pool * pool[SYS_MAX_FLOWS]; +}; + +extern struct np1_state np1; + +#endif /* OUROBOROS_IPCPD_NP1_H */ diff --git a/src/ipcpd/shim-data.c b/src/ipcpd/shim-data.c index ade157ce..8801213a 100644 --- a/src/ipcpd/shim-data.c +++ b/src/ipcpd/shim-data.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * IPC process utilities * @@ -30,18 +30,18 @@ #define OUROBOROS_PREFIX "shim-data" -#include <ouroboros/endian.h> -#include <ouroboros/logs.h> -#include <ouroboros/list.h> -#include <ouroboros/time_utils.h> #include <ouroboros/errno.h> +#include <ouroboros/hash.h> +#include <ouroboros/list.h> +#include <ouroboros/logs.h> +#include <ouroboros/time.h> #include "shim-data.h" #include "ipcp.h" -#include <string.h> -#include <stdlib.h> #include <assert.h> +#include <stdlib.h> +#include <string.h> struct reg_entry { struct list_head list; @@ -51,7 +51,7 @@ struct reg_entry { struct dir_entry { struct list_head list; uint8_t * hash; - uint64_t addr; + struct addr addr; }; static void destroy_dir_query(struct dir_query * query) @@ -108,14 +108,12 @@ static void reg_entry_destroy(struct reg_entry * entry) { assert(entry); - if (entry->hash != NULL) - free(entry->hash); - + free(entry->hash); free(entry); } -static struct dir_entry * dir_entry_create(uint8_t * hash, - uint64_t addr) +static struct dir_entry * dir_entry_create(uint8_t * hash, + struct addr addr) { struct dir_entry * entry = malloc(sizeof(*entry)); if (entry == NULL) @@ -133,15 +131,15 @@ static void dir_entry_destroy(struct dir_entry * entry) { assert(entry); - if (entry->hash != NULL) - free(entry->hash); - + free(entry->hash); free(entry); } -struct shim_data * shim_data_create() +struct shim_data * shim_data_create(void) { - struct shim_data * sd = malloc(sizeof(*sd)); + struct shim_data * sd; + + sd = malloc(sizeof(*sd)); if (sd == NULL) return NULL; @@ -151,11 +149,23 @@ struct shim_data * shim_data_create() list_head_init(&sd->dir_queries); /* init the locks */ - pthread_rwlock_init(&sd->reg_lock, NULL); - pthread_rwlock_init(&sd->dir_lock, NULL); - pthread_mutex_init(&sd->dir_queries_lock, NULL); + if (pthread_rwlock_init(&sd->reg_lock, NULL) < 0) + goto fail_reg_lock_init; + + if (pthread_rwlock_init(&sd->dir_lock, NULL) < 0) + goto fail_dir_lock_init; + + if (pthread_mutex_init(&sd->dir_queries_lock, NULL) < 0) + goto fail_mutex_init; return sd; + + fail_mutex_init: + pthread_rwlock_destroy(&sd->dir_lock); + fail_dir_lock_init: + pthread_rwlock_destroy(&sd->reg_lock); + fail_reg_lock_init: + return NULL; } static void clear_registry(struct shim_data * data) @@ -244,13 +254,15 @@ static struct reg_entry * find_reg_entry_by_hash(struct shim_data * data, static struct dir_entry * find_dir_entry(struct shim_data * data, const uint8_t * hash, - uint64_t addr) + struct addr addr) { struct list_head * h; list_for_each(h, &data->directory) { struct dir_entry * e = list_entry(h, struct dir_entry, list); - if (e->addr == addr && - !memcmp(e->hash, hash, ipcp_dir_hash_len())) + if (memcmp(&e->addr, &addr, sizeof(addr)) != 0) + continue; + + if (memcmp(e->hash, hash, ipcp_dir_hash_len()) == 0) return e; } @@ -283,8 +295,8 @@ int shim_data_reg_add_entry(struct shim_data * data, if (find_reg_entry_by_hash(data, hash)) { pthread_rwlock_unlock(&data->reg_lock); - log_dbg(HASH_FMT " was already in the directory.", - HASH_VAL(hash)); + log_dbg(HASH_FMT32 " was already in the directory.", + HASH_VAL32(hash)); return 0; } @@ -350,7 +362,7 @@ bool shim_data_reg_has(struct shim_data * data, int shim_data_dir_add_entry(struct shim_data * data, const uint8_t * hash, - uint64_t addr) + struct addr addr) { struct dir_entry * entry; uint8_t * entry_hash; @@ -386,7 +398,7 @@ int shim_data_dir_add_entry(struct shim_data * data, int shim_data_dir_del_entry(struct shim_data * data, const uint8_t * hash, - uint64_t addr) + struct addr addr) { struct dir_entry * e; if (data == NULL) @@ -423,19 +435,19 @@ bool shim_data_dir_has(struct shim_data * data, return ret; } -uint64_t shim_data_dir_get_addr(struct shim_data * data, - const uint8_t * hash) +struct addr shim_data_dir_get_addr(struct shim_data * data, + const uint8_t * hash) { struct dir_entry * entry; - uint64_t addr; + struct addr addr = {0}; pthread_rwlock_rdlock(&data->dir_lock); entry = find_dir_entry_any(data, hash); - if (entry == NULL) { pthread_rwlock_unlock(&data->dir_lock); - return 0; /* undefined behaviour, 0 may be a valid address */ + log_warn("No address for " HASH_FMT32 ".", HASH_VAL32(hash)); + return addr; /* undefined behaviour, 0 may be a valid address */ } addr = entry->addr; diff --git a/src/ipcpd/shim-data.h b/src/ipcpd/shim-data.h index 12a4b02e..ea4ce413 100644 --- a/src/ipcpd/shim-data.h +++ b/src/ipcpd/shim-data.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Utitilies for building IPC processes * @@ -25,9 +25,12 @@ #include <ouroboros/list.h> -#include <sys/types.h> #include <pthread.h> #include <stdint.h> +#include <netinet/in.h> +#include <sys/types.h> + +#define MAC_SIZE 6 enum dir_query_state { QUERY_INIT = 0, @@ -46,6 +49,14 @@ struct dir_query { pthread_cond_t cond; }; +struct addr { + union { + uint8_t mac[MAC_SIZE]; + struct in_addr ip4; + struct in6_addr ip6; + }; +}; + struct shim_data { struct list_head registry; pthread_rwlock_t reg_lock; @@ -72,16 +83,16 @@ bool shim_data_reg_has(struct shim_data * data, int shim_data_dir_add_entry(struct shim_data * data, const uint8_t * hash, - uint64_t addr); + struct addr addr); int shim_data_dir_del_entry(struct shim_data * data, const uint8_t * hash, - uint64_t addr); + struct addr addr); bool shim_data_dir_has(struct shim_data * data, const uint8_t * hash); -uint64_t shim_data_dir_get_addr(struct shim_data * data, +struct addr shim_data_dir_get_addr(struct shim_data * data, const uint8_t * hash); struct dir_query * shim_data_dir_query_create(struct shim_data * data, diff --git a/src/ipcpd/udp/CMakeLists.txt b/src/ipcpd/udp/CMakeLists.txt index f1a29ef6..159e9bf5 100644 --- a/src/ipcpd/udp/CMakeLists.txt +++ b/src/ipcpd/udp/CMakeLists.txt @@ -1,67 +1,17 @@ -get_filename_component(CURRENT_SOURCE_PARENT_DIR - ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) -get_filename_component(CURRENT_BINARY_PARENT_DIR - ${CMAKE_CURRENT_BINARY_DIR} DIRECTORY) +# UDP IPCPs build configuration (UDP4 and UDP6) +# DDNS detection is in cmake/dependencies/udp/ddns.cmake -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) +set(IPCP_UDP4_TARGET ipcpd-udp4) +set(IPCP_UDP6_TARGET ipcpd-udp6) -include_directories(${CURRENT_SOURCE_PARENT_DIR}) -include_directories(${CURRENT_BINARY_PARENT_DIR}) +add_executable(${IPCP_UDP4_TARGET} udp4.c ${IPCP_SOURCES}) +add_executable(${IPCP_UDP6_TARGET} udp6.c ${IPCP_SOURCES}) -include_directories(${CMAKE_SOURCE_DIR}/include) -include_directories(${CMAKE_BINARY_DIR}/include) +foreach(target ${IPCP_UDP4_TARGET} ${IPCP_UDP6_TARGET}) + target_include_directories(${target} PRIVATE ${IPCP_INCLUDE_DIRS}) + target_link_libraries(${target} PRIVATE ouroboros-dev) + ouroboros_target_debug_definitions(${target}) +endforeach() -set(IPCP_UDP_TARGET ipcpd-udp CACHE INTERNAL "") - -set(UDP_SOURCES - # Add source files here - ${CMAKE_CURRENT_SOURCE_DIR}/main.c - ) - -add_executable(ipcpd-udp ${UDP_SOURCES} ${IPCP_SOURCES}) - -target_link_libraries(ipcpd-udp LINK_PUBLIC ouroboros-dev) - -# Find the nsupdate executable -find_program(NSUPDATE_EXECUTABLE - NAMES nsupdate - DOC "The nsupdate tool that enables DDNS") - -# Find the nslookup executable -find_program(NSLOOKUP_EXECUTABLE - NAMES nslookup - DOC "The nslookup tool that resolves DNS names") - -mark_as_advanced(NSLOOKUP_EXECUTABLE NSUPDATE_EXECUTABLE) - -if (NSLOOKUP_EXECUTABLE AND NSUPDATE_EXECUTABLE) - set(DISABLE_DDNS FALSE CACHE BOOL "Disable DDNS support") - if (NOT DISABLE_DNS) - message(STATUS "DDNS support enabled") - set(HAVE_DDNS TRUE CACHE INTERNAL "") - else () - message(STATUS "DDNS support disabled by user") - unset(HAVE_DDNS CACHE) - endif () -else () - if (NSLOOKUP_EXECUTABLE) - message(STATUS "Install nsupdate to enable DDNS support") - elseif (NSUPDATE_EXECUTABLE) - message(STATUS "Install nslookup to enable DDNS support") - else () - message(STATUS "Install nslookup and nsupdate to enable DDNS support") - endif () -endif () - -set(IPCP_UDP_RD_THR 3 CACHE STRING - "Number of reader threads in UDP IPCP") -set(IPCP_UDP_WR_THR 3 CACHE STRING - "Number of writer threads in UDP IPCP") - -include(AddCompileFlags) -if (CMAKE_BUILD_TYPE MATCHES "Debug*") - add_compile_flags(ipcpd-udp -DCONFIG_OUROBOROS_DEBUG) -endif () - -install(TARGETS ipcpd-udp RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}) +install(TARGETS ${IPCP_UDP4_TARGET} ${IPCP_UDP6_TARGET} + RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}) diff --git a/src/ipcpd/udp/main.c b/src/ipcpd/udp/udp.c index 6a3fb24a..5803e674 100644 --- a/src/ipcpd/udp/main.c +++ b/src/ipcpd/udp/udp.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * IPC process over UDP * @@ -20,28 +20,21 @@ * Foundation, Inc., http://www.fsf.org/about/contact/. */ -#if defined(__linux__) || defined(__CYGWIN__) -#define _DEFAULT_SOURCE -#else -#define _POSIX_C_SOURCE 200112L -#endif - #include "config.h" -#define OUROBOROS_PREFIX "ipcpd/udp" - #include <ouroboros/bitmap.h> +#include <ouroboros/endian.h> #include <ouroboros/hash.h> #include <ouroboros/list.h> #include <ouroboros/utils.h> #include <ouroboros/dev.h> -#include <ouroboros/ipcp-dev.h> #include <ouroboros/fqueue.h> #include <ouroboros/errno.h> #include <ouroboros/logs.h> #include <ouroboros/pthread.h> #include "ipcp.h" +#include "np1.h" #include "shim-data.h" #include <string.h> @@ -58,14 +51,12 @@ #define FLOW_REQ 1 #define FLOW_REPLY 2 -#define THIS_TYPE IPCP_UDP -#define IPCP_UDP_MAX_PACKET_SIZE 8980 #define OUR_HEADER_LEN sizeof(uint32_t) /* adds eid */ -#define IPCP_UDP_BUF_SIZE 8980 -#define IPCP_UDP_MSG_SIZE 8980 +#define IPCP_UDP_BUF_SIZE IPCP_UDP_MAX_PACKET_SIZE +#define IPCP_UDP_MSG_SIZE IPCP_UDP_MAX_PACKET_SIZE + #define DNS_TTL 86400 -#define FD_UPDATE_TIMEOUT 100 /* microseconds */ #define SADDR ((struct sockaddr *) &udp_data.s_saddr) #define SADDR_SIZE (sizeof(udp_data.s_saddr)) @@ -81,45 +72,42 @@ #define SENDTO_FLAGS 0 #endif -struct ipcp ipcpi; - /* Keep order for alignment. */ struct mgmt_msg { uint32_t eid; uint32_t s_eid; uint32_t d_eid; - uint8_t code; - int8_t response; - /* QoS parameters from spec */ - uint8_t availability; - uint8_t in_order; + int32_t response; uint64_t bandwidth; uint32_t delay; uint32_t loss; uint32_t ber; uint32_t max_gap; - uint16_t cypher_s; + uint32_t timeout; + uint8_t code; + /* QoS parameters from spec */ + uint8_t availability; + uint8_t in_order; } __attribute__((packed)); struct mgmt_frame { - struct list_head next; - struct sockaddr_in r_saddr; - uint8_t buf[MGMT_FRAME_BUF_SIZE]; - size_t len; + struct list_head next; + struct __SOCKADDR r_saddr; + uint8_t buf[MGMT_FRAME_BUF_SIZE]; + size_t len; }; /* UDP flow */ struct uf { - int d_eid; - struct sockaddr_in r_saddr; + int d_eid; + struct __SOCKADDR r_saddr; }; struct { struct shim_data * shim_data; - uint32_t dns_addr; - - struct sockaddr_in s_saddr; + struct __ADDR dns_addr; + struct __SOCKADDR s_saddr; int s_fd; fset_t * np1_flows; @@ -136,14 +124,26 @@ struct { struct list_head mgmt_frames; } udp_data; +static const char * __inet_ntop(const struct __ADDR * addr, + char * buf) +{ + return inet_ntop(__AF, addr, buf, __ADDRSTRLEN); +} + static int udp_data_init(void) { - int i; + int i; + pthread_condattr_t cattr; if (pthread_rwlock_init(&udp_data.flows_lock, NULL)) goto fail_rwlock_init; - if (pthread_cond_init(&udp_data.mgmt_cond, NULL)) + if (pthread_condattr_init(&cattr)) + goto fail_condattr; +#ifndef __APPLE__ + pthread_condattr_setclock(&cattr, PTHREAD_COND_CLOCK); +#endif + if (pthread_cond_init(&udp_data.mgmt_cond, &cattr)) goto fail_mgmt_cond; if (pthread_mutex_init(&udp_data.mgmt_lock, NULL)) @@ -160,9 +160,12 @@ static int udp_data_init(void) if (udp_data.shim_data == NULL) goto fail_data; + pthread_condattr_destroy(&cattr); + list_head_init(&udp_data.mgmt_frames); return 0; + fail_data: fset_destroy(udp_data.np1_flows); fail_fset: @@ -170,6 +173,8 @@ static int udp_data_init(void) fail_mgmt_lock: pthread_cond_destroy(&udp_data.mgmt_cond); fail_mgmt_cond: + pthread_condattr_destroy(&cattr); + fail_condattr: pthread_rwlock_destroy(&udp_data.flows_lock); fail_rwlock_init: return -1; @@ -186,25 +191,26 @@ static void udp_data_fini(void) pthread_mutex_destroy(&udp_data.mgmt_lock); } -static int ipcp_udp_port_alloc(const struct sockaddr_in * r_saddr, - uint32_t s_eid, - const uint8_t * dst, - qosspec_t qs, - const void * data, - size_t dlen) +static int udp_ipcp_port_alloc(const struct __SOCKADDR * r_saddr, + uint32_t s_eid, + const uint8_t * dst, + qosspec_t qs, + const buffer_t * data) { uint8_t * buf; struct mgmt_msg * msg; size_t len; - assert(dlen > 0 ? data != NULL : data == NULL); + assert(data->len > 0 ? data->data != NULL : data->data == NULL); len = sizeof(*msg) + ipcp_dir_hash_len(); - buf = malloc(len + dlen); + buf = malloc(len + data->len); if (buf == NULL) return -1; + memset(buf, 0, len + data->len); + msg = (struct mgmt_msg *) buf; msg->eid = hton32(MGMT_EID); msg->code = FLOW_REQ; @@ -216,14 +222,17 @@ static int ipcp_udp_port_alloc(const struct sockaddr_in * r_saddr, msg->ber = hton32(qs.ber); msg->in_order = qs.in_order; msg->max_gap = hton32(qs.max_gap); - msg->cypher_s = hton16(qs.cypher_s); + msg->timeout = hton32(qs.timeout); memcpy(msg + 1, dst, ipcp_dir_hash_len()); - memcpy(buf + len, data, dlen); + if (data->len > 0) + memcpy(buf + len, data->data, data->len); - if (sendto(udp_data.s_fd, msg, len + dlen, + if (sendto(udp_data.s_fd, msg, len + data->len, SENDTO_FLAGS, (const struct sockaddr *) r_saddr, sizeof(*r_saddr)) < 0) { + log_err("Failed to send flow allocation request: %s.", + strerror(errno)); free(buf); return -1; } @@ -233,28 +242,30 @@ static int ipcp_udp_port_alloc(const struct sockaddr_in * r_saddr, return 0; } -static int ipcp_udp_port_alloc_resp(const struct sockaddr_in * r_saddr, - uint32_t s_eid, - uint32_t d_eid, - int8_t response, - const void * data, - size_t len) +static int udp_ipcp_port_alloc_resp(const struct __SOCKADDR * r_saddr, + uint32_t s_eid, + uint32_t d_eid, + int32_t response, + const buffer_t * data) { struct mgmt_msg * msg; - msg = malloc(sizeof(*msg) + len); + msg = malloc(sizeof(*msg) + data->len); if (msg == NULL) return -1; + memset(msg, 0, sizeof(*msg) + data->len); + msg->eid = hton32(MGMT_EID); msg->code = FLOW_REPLY; msg->s_eid = hton32(s_eid); msg->d_eid = hton32(d_eid); - msg->response = response; + msg->response = hton32(response); - memcpy(msg + 1, data, len); + if (data->len > 0) + memcpy(msg + 1, data->data, data->len); - if (sendto(udp_data.s_fd, msg, sizeof(*msg) + len, + if (sendto(udp_data.s_fd, msg, sizeof(*msg) + data->len, SENDTO_FLAGS, (const struct sockaddr *) r_saddr, sizeof(*r_saddr)) < 0 ) { free(msg); @@ -266,37 +277,16 @@ static int ipcp_udp_port_alloc_resp(const struct sockaddr_in * r_saddr, return 0; } -static int ipcp_udp_port_req(struct sockaddr_in * c_saddr, - int d_eid, - const uint8_t * dst, - qosspec_t qs, - const void * data, - size_t len) +static int udp_ipcp_port_req(struct __SOCKADDR * c_saddr, + int d_eid, + const uint8_t * dst, + qosspec_t qs, + const buffer_t * data) { - struct timespec ts = {0, FD_UPDATE_TIMEOUT * 1000}; - struct timespec abstime; - int fd; - - clock_gettime(PTHREAD_COND_CLOCK, &abstime); - - pthread_mutex_lock(&ipcpi.alloc_lock); - - while (ipcpi.alloc_id != -1 && ipcp_get_state() == IPCP_OPERATIONAL) { - ts_add(&abstime, &ts, &abstime); - pthread_cond_timedwait(&ipcpi.alloc_cond, &ipcpi.alloc_lock, - &abstime); - } - - if (ipcp_get_state() != IPCP_OPERATIONAL) { - log_dbg("Won't allocate over non-operational IPCP."); - pthread_mutex_unlock(&ipcpi.alloc_lock); - return -1; - } + int fd; - /* reply to IRM */ - fd = ipcp_flow_req_arr(dst, ipcp_dir_hash_len(), qs, data, len); + fd = ipcp_wait_flow_req_arr(dst, qs, IPCP_UDP_MPL, data); if (fd < 0) { - pthread_mutex_unlock(&ipcpi.alloc_lock); log_err("Could not get new flow from IRMd."); return -1; } @@ -308,30 +298,32 @@ static int ipcp_udp_port_req(struct sockaddr_in * c_saddr, pthread_rwlock_unlock(&udp_data.flows_lock); - ipcpi.alloc_id = fd; - pthread_cond_broadcast(&ipcpi.alloc_cond); - - pthread_mutex_unlock(&ipcpi.alloc_lock); - log_dbg("Pending allocation request, fd %d, remote eid %d.", fd, d_eid); return 0; } -static int ipcp_udp_port_alloc_reply(const struct sockaddr_in * saddr, - uint32_t s_eid, - uint32_t d_eid, - int8_t response, - const void * data, - size_t len) +static int udp_ipcp_port_alloc_reply(const struct __SOCKADDR * saddr, + uint32_t s_eid, + uint32_t d_eid, + int32_t response, + const buffer_t * data) { + time_t mpl = IPCP_UDP_MPL; + pthread_rwlock_wrlock(&udp_data.flows_lock); if (memcmp(&udp_data.fd_to_uf[s_eid].r_saddr, saddr, sizeof(*saddr))) { + char ipstr[__ADDRSTRLEN]; pthread_rwlock_unlock(&udp_data.flows_lock); - log_warn("Flow allocation reply for %u from wrong source.", - s_eid); + #ifdef BUILD_IPCP_UDP4 + __inet_ntop(&saddr->sin_addr, ipstr); + #else + __inet_ntop(&saddr->sin6_addr, ipstr); + #endif + log_err("Flow allocation reply for %u from wrong source %s.", + s_eid, ipstr); return -1; } @@ -340,8 +332,8 @@ static int ipcp_udp_port_alloc_reply(const struct sockaddr_in * saddr, pthread_rwlock_unlock(&udp_data.flows_lock); - if (ipcp_flow_alloc_reply(s_eid, response, data, len) < 0) { - log_dbg("Failed to reply to flow allocation."); + if (ipcp_flow_alloc_reply(s_eid, response, mpl, data) < 0) { + log_err("Failed to reply to flow allocation."); return -1; } @@ -351,13 +343,14 @@ static int ipcp_udp_port_alloc_reply(const struct sockaddr_in * saddr, return 0; } -static int ipcp_udp_mgmt_frame(const uint8_t * buf, - size_t len, - struct sockaddr_in c_saddr) +static int udp_ipcp_mgmt_frame(struct __SOCKADDR c_saddr, + const uint8_t * buf, + size_t len) { struct mgmt_msg * msg; size_t msg_len; qosspec_t qs; + buffer_t data; msg = (struct mgmt_msg *) buf; @@ -367,6 +360,10 @@ static int ipcp_udp_mgmt_frame(const uint8_t * buf, assert(len >= msg_len); + data.len = len - msg_len; + data.data = (uint8_t *) buf + msg_len; + + qs.delay = ntoh32(msg->delay); qs.bandwidth = ntoh64(msg->bandwidth); qs.availability = msg->availability; @@ -374,28 +371,29 @@ static int ipcp_udp_mgmt_frame(const uint8_t * buf, qs.ber = ntoh32(msg->ber); qs.in_order = msg->in_order; qs.max_gap = ntoh32(msg->max_gap); - qs.cypher_s = ntoh16(msg->cypher_s); + qs.timeout = ntoh32(msg->timeout); - return ipcp_udp_port_req(&c_saddr, ntoh32(msg->s_eid), + return udp_ipcp_port_req(&c_saddr, ntoh32(msg->s_eid), (uint8_t *) (msg + 1), qs, - buf + msg_len, - len - msg_len); + &data); case FLOW_REPLY: assert(len >= sizeof(*msg)); - return ipcp_udp_port_alloc_reply(&c_saddr, + data.len = len - sizeof(*msg); + data.data = (uint8_t *) buf + sizeof(*msg); + + return udp_ipcp_port_alloc_reply(&c_saddr, ntoh32(msg->s_eid), ntoh32(msg->d_eid), - msg->response, - buf + sizeof(*msg), - len - sizeof(*msg)); + ntoh32(msg->response), + &data); default: log_err("Unknown message received %d.", msg->code); return -1; } } -static void * ipcp_udp_mgmt_handler(void * o) +static void * udp_ipcp_mgmt_handler(void * o) { (void) o; @@ -417,7 +415,7 @@ static void * ipcp_udp_mgmt_handler(void * o) pthread_mutex_unlock(&udp_data.mgmt_lock); - ipcp_udp_mgmt_frame(frame->buf, frame->len, frame->r_saddr); + udp_ipcp_mgmt_frame(frame->r_saddr, frame->buf, frame->len); free(frame); } @@ -427,7 +425,7 @@ static void * ipcp_udp_mgmt_handler(void * o) return (void *) 0; } -static void * ipcp_udp_packet_reader(void * o) +static void * udp_ipcp_packet_reader(void * o) { uint8_t buf[IPCP_UDP_MAX_PACKET_SIZE]; uint8_t * data; @@ -437,13 +435,17 @@ static void * ipcp_udp_packet_reader(void * o) (void) o; + ipcp_lock_to_core(); + data = buf + sizeof(uint32_t); eid_p = (uint32_t *) buf; while (true) { - struct mgmt_frame * frame; - struct sockaddr_in r_saddr; - socklen_t len; + struct mgmt_frame * frame; + struct __SOCKADDR r_saddr; + socklen_t len; + struct ssm_pk_buff * spb; + uint8_t * head; len = sizeof(r_saddr); @@ -484,10 +486,18 @@ static void * ipcp_udp_packet_reader(void * o) continue; } - flow_write(eid, data, n - sizeof(eid)); + n-= sizeof(eid); + + if (ipcp_spb_reserve(&spb, n)) + continue; + + head = ssm_pk_buff_head(spb); + memcpy(head, data, n); + if (np1_flow_write(eid, spb, NP1_GET_POOL(eid)) < 0) + ipcp_spb_release(spb); } - return 0; + return (void *) 0; } static void cleanup_fqueue(void * fq) @@ -495,12 +505,12 @@ static void cleanup_fqueue(void * fq) fqueue_destroy((fqueue_t *) fq); } -static void cleanup_sdb(void * sdb) +static void cleanup_spb(void * spb) { - ipcp_sdb_release((struct shm_du_buff *) sdb); + ipcp_spb_release((struct ssm_pk_buff *) spb); } -static void * ipcp_udp_packet_writer(void * o) +static void * udp_ipcp_packet_writer(void * o) { fqueue_t * fq; @@ -515,34 +525,34 @@ static void * ipcp_udp_packet_writer(void * o) pthread_cleanup_push(cleanup_fqueue, fq); while (true) { - struct sockaddr_in saddr; - int eid; - int fd; + struct __SOCKADDR saddr; + int eid; + int fd; fevent(udp_data.np1_flows, fq, NULL); while ((fd = fqueue_next(fq)) >= 0) { - struct shm_du_buff * sdb; + struct ssm_pk_buff * spb; uint8_t * buf; uint16_t len; if (fqueue_type(fq) != FLOW_PKT) continue; - if (ipcp_flow_read(fd, &sdb)) { + if (np1_flow_read(fd, &spb, NP1_GET_POOL(fd))) { log_dbg("Bad read from fd %d.", fd); continue; } - len = shm_du_buff_tail(sdb) - shm_du_buff_head(sdb); + len = ssm_pk_buff_len(spb); if (len > IPCP_UDP_MAX_PACKET_SIZE) { log_dbg("Packet length exceeds MTU."); - ipcp_sdb_release(sdb); + ipcp_spb_release(spb); continue; } - buf = shm_du_buff_head_alloc(sdb, OUR_HEADER_LEN); + buf = ssm_pk_buff_head_alloc(spb, OUR_HEADER_LEN); if (buf == NULL) { log_dbg("Failed to allocate header."); - ipcp_sdb_release(sdb); + ipcp_spb_release(spb); continue; } @@ -555,7 +565,7 @@ static void * ipcp_udp_packet_writer(void * o) memcpy(buf, &eid, sizeof(eid)); - pthread_cleanup_push(cleanup_sdb, sdb); + pthread_cleanup_push(cleanup_spb, spb); if (sendto(udp_data.s_fd, buf, len + OUR_HEADER_LEN, SENDTO_FLAGS, @@ -572,103 +582,115 @@ static void * ipcp_udp_packet_writer(void * o) return (void *) 1; } -static int ipcp_udp_bootstrap(const struct ipcp_config * conf) +static bool is_addr_specified(const struct __ADDR * addr) { - char ipstr[INET_ADDRSTRLEN]; - char dnsstr[INET_ADDRSTRLEN]; - char portstr[128]; /* port is max 64535 = 5 chars */ +#ifdef BUILD_IPCP_UDP4 + return addr->s_addr != 0; +#else + return !IN6_IS_ADDR_UNSPECIFIED(addr); +#endif +} + +static int udp_ipcp_bootstrap(struct ipcp_config * conf) +{ + char ipstr[__ADDRSTRLEN]; + char dnsstr[__ADDRSTRLEN]; int i = 1; +#ifdef BUILD_IPCP_UDP4 + struct udp4_config * udp; + udp = &conf->udp4; +#else + struct udp6_config * udp; + udp = &conf->udp6; +#endif - assert(conf); + assert(conf != NULL); assert(conf->type == THIS_TYPE); + assert(conf->layer_info.dir_hash_algo == (enum pol_dir_hash) HASH_MD5); - ipcpi.dir_hash_algo = conf->layer_info.dir_hash_algo; - ipcpi.layer_name = strdup(conf->layer_info.layer_name); - if (ipcpi.layer_name == NULL) { - log_err("Failed to set layer name"); - return -ENOMEM; - } - - if (inet_ntop(AF_INET, &conf->ip_addr, ipstr, INET_ADDRSTRLEN) - == NULL) { - log_err("Failed to convert IP address"); + if (__inet_ntop(&udp->ip_addr, ipstr) == NULL) { + log_err("Failed to convert IP address."); return -1; } - if (conf->dns_addr != 0) { - if (inet_ntop(AF_INET, &conf->dns_addr, dnsstr, INET_ADDRSTRLEN) - == NULL) { - log_err("Failed to convert DNS address"); + if (is_addr_specified(&udp->dns_addr)) { + if (__inet_ntop(&udp->dns_addr, dnsstr) == NULL) { + log_err("Failed to convert DNS address."); return -1; } #ifndef HAVE_DDNS - log_warn("DNS disabled at compile time, address ignored"); + log_warn("DNS disabled at compile time, address ignored."); #endif } else { strcpy(dnsstr, "not set"); } /* UDP listen server */ - udp_data.s_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + udp_data.s_fd = socket(__AF, SOCK_DGRAM, IPPROTO_UDP); if (udp_data.s_fd < 0) { log_err("Can't create socket: %s", strerror(errno)); goto fail_socket; } memset((char *) &udp_data.s_saddr, 0, sizeof(udp_data.s_saddr)); - udp_data.s_saddr.sin_family = AF_INET; - udp_data.s_saddr.sin_addr.s_addr = conf->ip_addr; - udp_data.s_saddr.sin_port = htons(conf->port); - +#ifdef BUILD_IPCP_UDP4 + udp_data.s_saddr.sin_family = AF_INET; + udp_data.s_saddr.sin_addr = udp->ip_addr; + udp_data.s_saddr.sin_port = htons(udp->port); +#else + udp_data.s_saddr.sin6_family = AF_INET6; + udp_data.s_saddr.sin6_addr = udp->ip_addr; + udp_data.s_saddr.sin6_port = htons(udp->port); +#endif if (bind(udp_data.s_fd, SADDR, SADDR_SIZE) < 0) { - log_err("Couldn't bind to %s.", ipstr); + log_err("Couldn't bind to %s:%d. %s.", + ipstr, udp->port, strerror(errno)); goto fail_bind; } - udp_data.dns_addr = conf->dns_addr; - - ipcp_set_state(IPCP_OPERATIONAL); + udp_data.dns_addr = udp->dns_addr; if (pthread_create(&udp_data.mgmt_handler, NULL, - ipcp_udp_mgmt_handler, NULL)) { - ipcp_set_state(IPCP_INIT); + udp_ipcp_mgmt_handler, NULL)) { + log_err("Failed to create management thread."); goto fail_bind; } for (i = 0; i < IPCP_UDP_RD_THR; ++i) { if (pthread_create(&udp_data.packet_reader[i], NULL, - ipcp_udp_packet_reader, NULL)) { - ipcp_set_state(IPCP_INIT); + udp_ipcp_packet_reader, NULL)) { + log_err("Failed to create reader thread."); goto fail_packet_reader; } } for (i = 0; i < IPCP_UDP_WR_THR; ++i) { if (pthread_create(&udp_data.packet_writer[i], NULL, - ipcp_udp_packet_writer, NULL)) { - ipcp_set_state(IPCP_INIT); + udp_ipcp_packet_writer, NULL)) { + log_err("Failed to create writer thread."); goto fail_packet_writer; } } - sprintf(portstr, "%d", conf->port); - - log_dbg("Bootstrapped IPCP over UDP with pid %d.", getpid()); + log_dbg("Bootstrapped " TYPE_STR " with pid %d.", getpid()); log_dbg("Bound to IP address %s.", ipstr); - log_dbg("Using port %u.", conf->port); - log_dbg("DNS server address is %s.", dnsstr); + log_dbg("Using port %u.", udp->port); + if (is_addr_specified(&udp_data.dns_addr)) + log_dbg("DNS server address is %s.", dnsstr); + else + log_dbg("DNS server not in use."); return 0; fail_packet_writer: - while (i > 0) { - pthread_cancel(udp_data.packet_writer[--i]); + while (i-- > 0) { + pthread_cancel(udp_data.packet_writer[i]); pthread_join(udp_data.packet_writer[i], NULL); } i = IPCP_UDP_RD_THR; fail_packet_reader: - while (i > 0) { - pthread_cancel(udp_data.packet_reader[--i]); + while (i-- > 0) { + pthread_cancel(udp_data.packet_reader[i]); pthread_join(udp_data.packet_reader[i], NULL); } pthread_cancel(udp_data.mgmt_handler); @@ -684,20 +706,22 @@ static int ipcp_udp_bootstrap(const struct ipcp_config * conf) /* NOTE: Disgusted with this crap */ static int ddns_send(char * cmd) { - pid_t pid = -1; + pid_t pid; int wstatus; int pipe_fd[2]; char * argv[] = {NSUPDATE_EXEC, 0}; char * envp[] = {0}; if (pipe(pipe_fd)) { - log_err("Failed to create pipe."); + log_err("Failed to create pipe: %s.", strerror(errno)); return -1; } pid = fork(); if (pid == -1) { - log_err("Failed to fork."); + log_err("Failed to fork: %s.", strerror(errno)); + close(pipe_fd[0]); + close(pipe_fd[1]); return -1; } @@ -705,12 +729,15 @@ static int ddns_send(char * cmd) close(pipe_fd[1]); dup2(pipe_fd[0], 0); execve(argv[0], &argv[0], envp); + log_err("Failed to execute: %s", strerror(errno)); + exit(1); } close(pipe_fd[0]); if (write(pipe_fd[1], cmd, strlen(cmd)) == -1) { - log_err("Failed to communicate with nsupdate."); + log_err("Failed to communicate with nsupdate: %s.", + strerror(errno)); close(pipe_fd[1]); return -1; } @@ -726,32 +753,34 @@ static int ddns_send(char * cmd) return 0; } -static uint32_t ddns_resolve(char * name, - uint32_t dns_addr) +static struct __ADDR ddns_resolve(char * name, + struct __ADDR dns_addr) { - pid_t pid = -1; - int wstatus; - int pipe_fd[2]; - char dnsstr[INET_ADDRSTRLEN]; - char buf[IPCP_UDP_BUF_SIZE]; - ssize_t count = 0; - char * substr = NULL; - char * substr2 = NULL; - char * addr_str = "Address:"; - uint32_t ip_addr = 0; - - if (inet_ntop(AF_INET, &dns_addr, dnsstr, INET_ADDRSTRLEN) == NULL) - return 0; + pid_t pid = -1; + int wstatus; + int pipe_fd[2]; + char dnsstr[__ADDRSTRLEN]; + char buf[IPCP_UDP_BUF_SIZE]; + ssize_t count = 0; + char * substr = NULL; + char * substr2 = NULL; + char * addr_str = "Address:"; + struct __ADDR ip_addr = __ADDR_ANY_INIT; + + if (__inet_ntop(&dns_addr, dnsstr) == NULL) + return ip_addr; if (pipe(pipe_fd)) { - log_err("Failed to create pipe."); - return 0; + log_err("Failed to create pipe: %s.", strerror(errno)); + return ip_addr; } pid = fork(); if (pid == -1) { - log_err("Failed to fork."); - return 0; + log_err("Failed to fork: %s.", strerror(errno)); + close(pipe_fd[0]); + close(pipe_fd[1]); + return ip_addr; } if (pid == 0) { @@ -761,22 +790,24 @@ static uint32_t ddns_resolve(char * name, close(pipe_fd[0]); dup2(pipe_fd[1], 1); execve(argv[0], &argv[0], envp); + log_err("Failed to execute: %s", strerror(errno)); + exit(1); } close(pipe_fd[1]); - count = read(pipe_fd[0], buf, IPCP_UDP_BUF_SIZE); + count = read(pipe_fd[0], buf, IPCP_UDP_BUF_SIZE - 1); if (count <= 0) { log_err("Failed to communicate with nslookup."); close(pipe_fd[0]); - return 0; + return ip_addr; } close(pipe_fd[0]); waitpid(pid, &wstatus, 0); if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 0 && - count != IPCP_UDP_BUF_SIZE) + count != IPCP_UDP_BUF_SIZE - 1) log_dbg("Succesfully communicated with nslookup."); else log_err("Failed to resolve DNS address."); @@ -790,40 +821,43 @@ static uint32_t ddns_resolve(char * name, if (substr2 == NULL || strstr(substr2, addr_str) == NULL) { log_err("Failed to resolve DNS address."); - return 0; + return ip_addr; } - if (inet_pton(AF_INET, substr2 + strlen(addr_str) + 1, &ip_addr) != 1) { + if (inet_pton(__AF, substr2 + strlen(addr_str) + 1, &ip_addr) != 1) { log_err("Failed to resolve DNS address."); - return 0; + assert(!is_addr_specified(&ip_addr)); + return ip_addr; } return ip_addr; } #endif -static int ipcp_udp_reg(const uint8_t * hash) +static int udp_ipcp_reg(const uint8_t * hash) { #ifdef HAVE_DDNS - char ipstr[INET_ADDRSTRLEN]; - char dnsstr[INET_ADDRSTRLEN]; - char cmd[1000]; - uint32_t dns_addr; - uint32_t ip_addr; + char ipstr[__ADDRSTRLEN]; + char dnsstr[__ADDRSTRLEN]; + char cmd[1000]; + struct __ADDR dns_addr; + struct __ADDR ip_addr; #endif char * hashstr; hashstr = malloc(ipcp_dir_hash_strlen() + 1); - if (hashstr == NULL) + if (hashstr == NULL) { + log_err("Failed to malloc hashstr."); return -1; + } assert(hash); ipcp_hash_str(hashstr, hash); if (shim_data_reg_add_entry(udp_data.shim_data, hash)) { - log_err("Failed to add " HASH_FMT " to local registry.", - HASH_VAL(hash)); + log_err("Failed to add " HASH_FMT32 " to local registry.", + HASH_VAL32(hash)); free(hashstr); return -1; } @@ -833,17 +867,20 @@ static int ipcp_udp_reg(const uint8_t * hash) dns_addr = udp_data.dns_addr; - if (dns_addr != 0) { - ip_addr = udp_data.s_saddr.sin_addr.s_addr; - - if (inet_ntop(AF_INET, &ip_addr, - ipstr, INET_ADDRSTRLEN) == NULL) { + if (is_addr_specified(&dns_addr)) { +#ifdef BUILD_IPCP_UDP4 + ip_addr = udp_data.s_saddr.sin_addr; +#else + ip_addr = udp_data.s_saddr.sin6_addr; +#endif + if (__inet_ntop(&ip_addr, ipstr) == NULL) { + log_err("Failed to convert IP address to string."); free(hashstr); return -1; } - if (inet_ntop(AF_INET, &dns_addr, - dnsstr, INET_ADDRSTRLEN) == NULL) { + if (__inet_ntop(&dns_addr, dnsstr) == NULL) { + log_err("Failed to convert DNS address to string."); free(hashstr); return -1; } @@ -852,34 +889,35 @@ static int ipcp_udp_reg(const uint8_t * hash) dnsstr, hashstr, DNS_TTL, ipstr); if (ddns_send(cmd)) { + log_err("Failed to send DDNS message."); shim_data_reg_del_entry(udp_data.shim_data, hash); free(hashstr); return -1; } } #endif - log_dbg("Registered " HASH_FMT ".", HASH_VAL(hash)); - free(hashstr); return 0; } -static int ipcp_udp_unreg(const uint8_t * hash) +static int udp_ipcp_unreg(const uint8_t * hash) { #ifdef HAVE_DDNS - char dnsstr[INET_ADDRSTRLEN]; + char dnsstr[__ADDRSTRLEN]; /* max DNS name length + max IP length + max command length */ - char cmd[100]; - uint32_t dns_addr; + char cmd[100]; + struct __ADDR dns_addr; #endif - char * hashstr; + char * hashstr; assert(hash); hashstr = malloc(ipcp_dir_hash_strlen() + 1); - if (hashstr == NULL) + if (hashstr == NULL) { + log_err("Failed to malloc hashstr."); return -1; + } ipcp_hash_str(hashstr, hash); @@ -888,9 +926,9 @@ static int ipcp_udp_unreg(const uint8_t * hash) dns_addr = udp_data.dns_addr; - if (dns_addr != 0) { - if (inet_ntop(AF_INET, &dns_addr, dnsstr, INET_ADDRSTRLEN) - == NULL) { + if (is_addr_specified(&dns_addr)) { + if (__inet_ntop(&dns_addr, dnsstr) == NULL) { + log_err("Failed to convert DNS address to string."); free(hashstr); return -1; } @@ -903,26 +941,28 @@ static int ipcp_udp_unreg(const uint8_t * hash) shim_data_reg_del_entry(udp_data.shim_data, hash); - log_dbg("Unregistered " HASH_FMT ".", HASH_VAL(hash)); - free(hashstr); return 0; } -static int ipcp_udp_query(const uint8_t * hash) +static int udp_ipcp_query(const uint8_t * hash) { - uint32_t ip_addr = 0; - char * hashstr; - struct hostent * h; + struct addr addr = {}; + char * hashstr; + struct addrinfo hints; + struct addrinfo * ai; #ifdef HAVE_DDNS - uint32_t dns_addr = 0; + struct __ADDR dns_addr = __ADDR_ANY_INIT; + struct __ADDR ip_addr = __ADDR_ANY_INIT; #endif assert(hash); hashstr = malloc(ipcp_dir_hash_strlen() + 1); - if (hashstr == NULL) + if (hashstr == NULL) { + log_err("Failed to malloc hashstr."); return -ENOMEM; + } ipcp_hash_str(hashstr, hash); @@ -934,28 +974,42 @@ static int ipcp_udp_query(const uint8_t * hash) #ifdef HAVE_DDNS dns_addr = udp_data.dns_addr; - if (dns_addr != 0) { + if (is_addr_specified(&dns_addr)) { ip_addr = ddns_resolve(hashstr, dns_addr); - if (ip_addr == 0) { - log_dbg("Could not resolve %s.", hashstr); + if (!is_addr_specified(&ip_addr)) { + log_err("Could not resolve %s.", hashstr); free(hashstr); return -1; } } else { #endif - h = gethostbyname(hashstr); - if (h == NULL) { - log_dbg("Could not resolve %s.", hashstr); + memset(&hints, 0, sizeof(hints)); + + hints.ai_family = __AF; + if (getaddrinfo(hashstr, NULL, &hints, &ai) != 0) { + log_err("Could not resolve %s: %s.", hashstr, + gai_strerror(errno)); + free(hashstr); + return -1; + } + + if (ai->ai_family != __AF) { + log_err("Wrong addres family for %s.", hashstr); + freeaddrinfo(ai); free(hashstr); return -1; } - ip_addr = *((uint32_t *) (h->h_addr_list[0])); + #ifdef BUILD_IPCP_UDP4 + addr.ip4 = ((struct sockaddr_in *) (ai->ai_addr))->sin_addr; + #else + addr.ip6 = ((struct sockaddr_in6 *) (ai->ai_addr))->sin6_addr; + #endif + freeaddrinfo(ai); #ifdef HAVE_DDNS } #endif - - if (shim_data_dir_add_entry(udp_data.shim_data, hash, ip_addr)) { + if (shim_data_dir_add_entry(udp_data.shim_data, hash, addr)) { log_err("Failed to add directory entry."); free(hashstr); return -1; @@ -966,38 +1020,51 @@ static int ipcp_udp_query(const uint8_t * hash) return 0; } -static int ipcp_udp_flow_alloc(int fd, - const uint8_t * dst, - qosspec_t qs, - const void * data, - size_t len) +static int udp_ipcp_flow_alloc(int fd, + const uint8_t * dst, + qosspec_t qs, + const buffer_t * data) { - struct sockaddr_in r_saddr; /* Server address */ - uint32_t ip_addr = 0; - char ipstr[INET_ADDRSTRLEN]; - - log_dbg("Allocating flow to " HASH_FMT ".", HASH_VAL(dst)); + struct __SOCKADDR r_saddr; /* Server address */ + struct __ADDR ip_addr; + struct addr addr; + char ipstr[__ADDRSTRLEN]; (void) qs; assert(dst); if (!shim_data_dir_has(udp_data.shim_data, dst)) { - log_dbg("Could not resolve destination."); + log_err("Could not resolve destination."); return -1; } - ip_addr = (uint32_t) shim_data_dir_get_addr(udp_data.shim_data, dst); + addr = shim_data_dir_get_addr(udp_data.shim_data, dst); +#ifdef BUILD_IPCP_UDP4 + ip_addr = addr.ip4; +#else + ip_addr = addr.ip6; +#endif + if (__inet_ntop(&ip_addr, ipstr) == NULL) { + log_err("Could not convert IP address."); + return -1; + } - inet_ntop(AF_INET, &ip_addr, ipstr, INET_ADDRSTRLEN); - log_dbg("Destination UDP ipcp resolved at %s.", ipstr); + log_dbg("Destination " HASH_FMT32 " resolved at IP %s.", + HASH_VAL32(dst), ipstr); memset((char *) &r_saddr, 0, sizeof(r_saddr)); - r_saddr.sin_family = AF_INET; - r_saddr.sin_addr.s_addr = ip_addr; - r_saddr.sin_port = udp_data.s_saddr.sin_port; +#ifdef BUILD_IPCP_UDP4 + r_saddr.sin_family = AF_INET; + r_saddr.sin_addr = addr.ip4; + r_saddr.sin_port = udp_data.s_saddr.sin_port; +#else + r_saddr.sin6_family = AF_INET6; + r_saddr.sin6_addr = addr.ip6; + r_saddr.sin6_port = udp_data.s_saddr.sin6_port; +#endif - if (ipcp_udp_port_alloc(&r_saddr, fd, dst, qs, data, len) < 0) { + if (udp_ipcp_port_alloc(&r_saddr, fd, dst, qs, data) < 0) { log_err("Could not allocate port."); return -1; } @@ -1011,45 +1078,21 @@ static int ipcp_udp_flow_alloc(int fd, fset_add(udp_data.np1_flows, fd); - log_dbg("Flow to %s pending on fd %d.", ipstr, fd); - return 0; } -static int ipcp_udp_flow_alloc_resp(int fd, - int resp, - const void * data, - size_t len) +static int udp_ipcp_flow_alloc_resp(int fd, + int resp, + const buffer_t * data) { - struct timespec ts = {0, FD_UPDATE_TIMEOUT * 1000}; - struct timespec abstime; - struct sockaddr_in saddr; - int d_eid; - - if (resp) - return 0; - - clock_gettime(PTHREAD_COND_CLOCK, &abstime); - - pthread_mutex_lock(&ipcpi.alloc_lock); - - while (ipcpi.alloc_id != fd && ipcp_get_state() == IPCP_OPERATIONAL) { - ts_add(&abstime, &ts, &abstime); - pthread_cond_timedwait(&ipcpi.alloc_cond, - &ipcpi.alloc_lock, - &abstime); - } + struct __SOCKADDR saddr; + int d_eid; - if (ipcp_get_state() != IPCP_OPERATIONAL) { - pthread_mutex_unlock(&ipcpi.alloc_lock); + if (ipcp_wait_flow_resp(fd) < 0) { + log_err("Failed to wait for flow response."); return -1; } - ipcpi.alloc_id = -1; - pthread_cond_broadcast(&ipcpi.alloc_cond); - - pthread_mutex_unlock(&ipcpi.alloc_lock); - pthread_rwlock_rdlock(&udp_data.flows_lock); saddr = udp_data.fd_to_uf[fd].r_saddr; @@ -1057,7 +1100,7 @@ static int ipcp_udp_flow_alloc_resp(int fd, pthread_rwlock_unlock(&udp_data.flows_lock); - if (ipcp_udp_port_alloc_resp(&saddr, d_eid, fd, resp, data, len) < 0) { + if (udp_ipcp_port_alloc_resp(&saddr, d_eid, fd, resp, data) < 0) { fset_del(udp_data.np1_flows, fd); log_err("Failed to respond to flow request."); return -1; @@ -1065,13 +1108,10 @@ static int ipcp_udp_flow_alloc_resp(int fd, fset_add(udp_data.np1_flows, fd); - log_dbg("Accepted flow, fd %d on eid %d.", - fd, d_eid); - return 0; } -static int ipcp_udp_flow_dealloc(int fd) +static int udp_ipcp_flow_dealloc(int fd) { ipcp_flow_fini(fd); @@ -1084,25 +1124,23 @@ static int ipcp_udp_flow_dealloc(int fd) pthread_rwlock_unlock(&udp_data.flows_lock); - flow_dealloc(fd); - - log_dbg("Flow with fd %d deallocated.", fd); + ipcp_flow_dealloc(fd); return 0; } static struct ipcp_ops udp_ops = { - .ipcp_bootstrap = ipcp_udp_bootstrap, + .ipcp_bootstrap = udp_ipcp_bootstrap, .ipcp_enroll = NULL, .ipcp_connect = NULL, .ipcp_disconnect = NULL, - .ipcp_reg = ipcp_udp_reg, - .ipcp_unreg = ipcp_udp_unreg, - .ipcp_query = ipcp_udp_query, - .ipcp_flow_alloc = ipcp_udp_flow_alloc, + .ipcp_reg = udp_ipcp_reg, + .ipcp_unreg = udp_ipcp_unreg, + .ipcp_query = udp_ipcp_query, + .ipcp_flow_alloc = udp_ipcp_flow_alloc, .ipcp_flow_join = NULL, - .ipcp_flow_alloc_resp = ipcp_udp_flow_alloc_resp, - .ipcp_flow_dealloc = ipcp_udp_flow_dealloc + .ipcp_flow_alloc_resp = udp_ipcp_flow_alloc_resp, + .ipcp_flow_dealloc = udp_ipcp_flow_dealloc }; int main(int argc, @@ -1110,53 +1148,51 @@ int main(int argc, { int i; - if (ipcp_init(argc, argv, &udp_ops, THIS_TYPE) < 0) - goto fail_init; if (udp_data_init() < 0) { log_err("Failed to init udp data."); goto fail_data_init; } - if (ipcp_boot() < 0) { - log_err("Failed to boot IPCP."); - goto fail_boot; + if (ipcp_init(argc, argv, &udp_ops, THIS_TYPE) < 0) { + log_err("Failed to initialize IPCP."); + goto fail_init; } - if (ipcp_create_r(0)) { - log_err("Failed to notify IRMd we are initialized."); - goto fail_create_r; + if (ipcp_start() < 0) { + log_err("Failed to start IPCP."); + goto fail_start; } - ipcp_shutdown(); + ipcp_sigwait(); if (ipcp_get_state() == IPCP_SHUTDOWN) { - for (i = 0; i < IPCP_UDP_RD_THR; ++i) - pthread_cancel(udp_data.packet_reader[i]); for (i = 0; i < IPCP_UDP_WR_THR; ++i) pthread_cancel(udp_data.packet_writer[i]); + for (i = 0; i < IPCP_UDP_RD_THR; ++i) + pthread_cancel(udp_data.packet_reader[i]); pthread_cancel(udp_data.mgmt_handler); - for (i = 0; i < IPCP_UDP_RD_THR; ++i) - pthread_join(udp_data.packet_reader[i], NULL); for (i = 0; i < IPCP_UDP_WR_THR; ++i) pthread_join(udp_data.packet_writer[i], NULL); + for (i = 0; i < IPCP_UDP_RD_THR; ++i) + pthread_join(udp_data.packet_reader[i], NULL); pthread_join(udp_data.mgmt_handler, NULL); + close(udp_data.s_fd); } - udp_data_fini(); + ipcp_stop(); ipcp_fini(); - exit(EXIT_SUCCESS); - fail_create_r: - ipcp_set_state(IPCP_NULL); - ipcp_shutdown(); - fail_boot: udp_data_fini(); - fail_data_init: + + exit(EXIT_SUCCESS); + + fail_start: ipcp_fini(); fail_init: - ipcp_create_r(-1); + udp_data_fini(); + fail_data_init: exit(EXIT_FAILURE); } diff --git a/src/ipcpd/udp/udp4.c b/src/ipcpd/udp/udp4.c new file mode 100644 index 00000000..07d5f818 --- /dev/null +++ b/src/ipcpd/udp/udp4.c @@ -0,0 +1,42 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * IPC process over UDP/IPv4 + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#if defined(__linux__) || defined(__CYGWIN__) +#define _DEFAULT_SOURCE +#else +#define _POSIX_C_SOURCE 200112L +#endif + +#include <ouroboros/ipcp-dev.h> + +#define BUILD_IPCP_UDP4 +#define THIS_TYPE IPCP_UDP4 +#define TYPE_STR "IPCP over UDP/IPv4" +#define OUROBOROS_PREFIX "ipcpd/udp4" +#define IPCP_UDP_MAX_PACKET_SIZE 8980 +#define __AF AF_INET +#define __ADDRSTRLEN INET_ADDRSTRLEN +#define __SOCKADDR sockaddr_in +#define __ADDR in_addr +#define __ADDR_ANY_INIT { .s_addr = INADDR_ANY } + +#include "udp.c" diff --git a/src/ipcpd/udp/udp6.c b/src/ipcpd/udp/udp6.c new file mode 100644 index 00000000..b7924a3f --- /dev/null +++ b/src/ipcpd/udp/udp6.c @@ -0,0 +1,42 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * IPC process over UDP/IPv6 + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#if defined(__linux__) || defined(__CYGWIN__) +#define _DEFAULT_SOURCE +#else +#define _POSIX_C_SOURCE 200112L +#endif + +#include <ouroboros/ipcp-dev.h> + +#define BUILD_IPCP_UDP6 +#define THIS_TYPE IPCP_UDP6 +#define TYPE_STR "IPCP over UDP/IPv6" +#define OUROBOROS_PREFIX "ipcpd/udp6" +#define IPCP_UDP_MAX_PACKET_SIZE 8952 +#define __AF AF_INET6 +#define __ADDRSTRLEN INET6_ADDRSTRLEN +#define __SOCKADDR sockaddr_in6 +#define __ADDR in6_addr +#define __ADDR_ANY_INIT IN6ADDR_ANY_INIT + +#include "udp.c" diff --git a/src/ipcpd/unicast/CMakeLists.txt b/src/ipcpd/unicast/CMakeLists.txt index f4887160..1e095f8b 100644 --- a/src/ipcpd/unicast/CMakeLists.txt +++ b/src/ipcpd/unicast/CMakeLists.txt @@ -1,48 +1,21 @@ -get_filename_component(CURRENT_SOURCE_PARENT_DIR - ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) -get_filename_component(CURRENT_BINARY_PARENT_DIR - ${CMAKE_CURRENT_BINARY_DIR} DIRECTORY) +# Unicast IPCP build configuration -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) +set(IPCP_UNICAST_TARGET ipcpd-unicast) -include_directories(${CURRENT_SOURCE_PARENT_DIR}) -include_directories(${CURRENT_BINARY_PARENT_DIR}) +protobuf_generate_c(DHT_PROTO_SRCS DHT_PROTO_HDRS + "${CMAKE_CURRENT_SOURCE_DIR}/dir/dht.proto") -include_directories(${CMAKE_SOURCE_DIR}/include) -include_directories(${CMAKE_BINARY_DIR}/include) - -set(IPCP_UNICAST_TARGET ipcpd-unicast CACHE INTERNAL "") - -protobuf_generate_c(KAD_PROTO_SRCS KAD_PROTO_HDRS dir/kademlia.proto) - -math(EXPR PFT_EXPR "1 << 12") -set(PFT_SIZE ${PFT_EXPR} CACHE STRING - "Size of the PDU forwarding table") -if (HAVE_FUSE) - set(IPCP_FLOW_STATS TRUE CACHE BOOL - "Enable flow statistics tracking in IPCP") - if (IPCP_FLOW_STATS) - message(STATUS "IPCP flow statistics enabled") - else () - message(STATUS "IPCP flow statistics disabled") - endif () -endif () - -set(SOURCE_FILES - # Add source files here +set(UNICAST_SOURCES addr-auth.c ca.c connmgr.c dir.c dt.c - enroll.c fa.c main.c pff.c routing.c psched.c - # Add policies last addr-auth/flat.c ca/mb-ecn.c ca/nop.c @@ -53,22 +26,26 @@ set(SOURCE_FILES pff/pft.c routing/link-state.c routing/graph.c - ) +) -add_executable(ipcpd-unicast ${SOURCE_FILES} ${IPCP_SOURCES} - ${KAD_PROTO_SRCS} ${LAYER_CONFIG_PROTO_SRCS}) -target_link_libraries(ipcpd-unicast LINK_PUBLIC ouroboros-dev) +add_executable(${IPCP_UNICAST_TARGET} + ${UNICAST_SOURCES} + ${IPCP_SOURCES} + ${COMMON_SOURCES} + ${DHT_PROTO_SRCS} +) -include(AddCompileFlags) -if (CMAKE_BUILD_TYPE MATCHES "Debug*") - add_compile_flags(ipcpd-unicast -DCONFIG_OUROBOROS_DEBUG) -endif () +target_include_directories(${IPCP_UNICAST_TARGET} PRIVATE ${IPCP_INCLUDE_DIRS}) +target_include_directories(${IPCP_UNICAST_TARGET} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +target_include_directories(${IPCP_UNICAST_TARGET} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +target_link_libraries(${IPCP_UNICAST_TARGET} PRIVATE ouroboros-dev) -install(TARGETS ipcpd-unicast RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}) +ouroboros_target_debug_definitions(${IPCP_UNICAST_TARGET}) -add_subdirectory(pff/tests) -add_subdirectory(routing/tests) +install(TARGETS ${IPCP_UNICAST_TARGET} RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}) -if (NOT GNU) +if(BUILD_TESTS) add_subdirectory(dir/tests) -endif () + add_subdirectory(pff/tests) + add_subdirectory(routing/tests) +endif() diff --git a/src/ipcpd/unicast/addr-auth.c b/src/ipcpd/unicast/addr-auth.c index 01c2188f..908a4aa1 100644 --- a/src/ipcpd/unicast/addr-auth.c +++ b/src/ipcpd/unicast/addr-auth.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Address authority * diff --git a/src/ipcpd/unicast/addr-auth.h b/src/ipcpd/unicast/addr-auth.h index d26d3eb7..0d2cd4c0 100644 --- a/src/ipcpd/unicast/addr-auth.h +++ b/src/ipcpd/unicast/addr-auth.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Address authority * @@ -27,6 +27,14 @@ #include <stdint.h> +#define ADDR_FMT32 "%02x.%02x.%02x.%02x" +#define ADDR_VAL32(a) \ + ((uint8_t *) a)[0], ((uint8_t *) a)[1], \ + ((uint8_t *) a)[2], ((uint8_t *) a)[3] + +#define ADDR_FMT64 ADDR_FMT32 "." ADDR_FMT32 +#define ADDR_VAL64(a) ADDR_VAL32(a), ADDR_VAL32(a + 4) + int addr_auth_init(enum pol_addr_auth type, const void * info); diff --git a/src/ipcpd/unicast/addr-auth/flat.c b/src/ipcpd/unicast/addr-auth/flat.c index af245a5d..34ca1cef 100644 --- a/src/ipcpd/unicast/addr-auth/flat.c +++ b/src/ipcpd/unicast/addr-auth/flat.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Policy for flat addresses in a distributed way * @@ -29,27 +29,20 @@ #define OUROBOROS_PREFIX "flat-addr-auth" #include <ouroboros/logs.h> -#include <ouroboros/errno.h> -#include <ouroboros/time_utils.h> -#include <ouroboros/utils.h> +#include <ouroboros/random.h> +#include "addr-auth.h" #include "ipcp.h" #include "flat.h" -#include <time.h> -#include <stdlib.h> -#include <math.h> -#include <string.h> -#include <assert.h> - -#define NAME_LEN 8 +#define NAME_LEN 8 +#define INVALID_ADDRESS 0 struct { - uint8_t addr_size; + uint8_t addr_size; + uint32_t addr; } flat; -#define INVALID_ADDRESS 0 - struct addr_auth_ops flat_ops = { .init = flat_init, .fini = flat_fini, @@ -65,6 +58,15 @@ int flat_init(const void * info) return -1; } +#if defined (CONFIG_OUROBOROS_DEBUG) && defined (IPCP_DEBUG_LOCAL) + flat.addr = getpid(); +#else + while (flat.addr == INVALID_ADDRESS) + random_buffer(&flat.addr,sizeof(flat.addr)); +#endif + log_dbg("Flat address initialized to " ADDR_FMT32 ".", + ADDR_VAL32((uint8_t *) &flat.addr)); + return 0; } @@ -75,13 +77,5 @@ int flat_fini(void) uint64_t flat_address(void) { - struct timespec t; - uint32_t addr; - - clock_gettime(CLOCK_REALTIME, &t); - srand(t.tv_nsec); - - addr = (rand() % (RAND_MAX - 1) + 1) & 0xFFFFFFFF; - - return addr; + return (uint64_t) flat.addr; } diff --git a/src/ipcpd/unicast/addr-auth/flat.h b/src/ipcpd/unicast/addr-auth/flat.h index 96642dcc..d4b672c7 100644 --- a/src/ipcpd/unicast/addr-auth/flat.h +++ b/src/ipcpd/unicast/addr-auth/flat.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Policy for flat addresses in a distributed way * diff --git a/src/ipcpd/unicast/addr-auth/ops.h b/src/ipcpd/unicast/addr-auth/ops.h index e1069706..06b24cec 100644 --- a/src/ipcpd/unicast/addr-auth/ops.h +++ b/src/ipcpd/unicast/addr-auth/ops.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Address authority policy ops * diff --git a/src/ipcpd/unicast/addr-auth/pol.h b/src/ipcpd/unicast/addr-auth/pol.h index 70747af9..844308c6 100644 --- a/src/ipcpd/unicast/addr-auth/pol.h +++ b/src/ipcpd/unicast/addr-auth/pol.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Address Authority policies * diff --git a/src/ipcpd/unicast/ca.c b/src/ipcpd/unicast/ca.c index 3dbc93d5..1fcc9bb2 100644 --- a/src/ipcpd/unicast/ca.c +++ b/src/ipcpd/unicast/ca.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Congestion Avoidance * @@ -49,7 +49,6 @@ int ca_init(enum pol_cong_avoid pol) return 0; } - void ca_fini(void) { ca.ops = NULL; diff --git a/src/ipcpd/unicast/ca.h b/src/ipcpd/unicast/ca.h index 8b221790..ea803e17 100644 --- a/src/ipcpd/unicast/ca.h +++ b/src/ipcpd/unicast/ca.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Congestion avoidance * diff --git a/src/ipcpd/unicast/ca/mb-ecn.c b/src/ipcpd/unicast/ca/mb-ecn.c index 38305a39..d9a204b0 100644 --- a/src/ipcpd/unicast/ca/mb-ecn.c +++ b/src/ipcpd/unicast/ca/mb-ecn.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Multi-bit ECN Congestion Avoidance * @@ -29,7 +29,7 @@ #include "config.h" #include <ouroboros/ipcp-dev.h> -#include <ouroboros/time_utils.h> +#include <ouroboros/time.h> #include "mb-ecn.h" @@ -187,7 +187,7 @@ ca_wnd_t mb_ecn_ctx_update_snd(void * _ctx, void mb_ecn_wnd_wait(ca_wnd_t wnd) { if (wnd.wait > 0) { - struct timespec s = {0, 0}; + struct timespec s = TIMESPEC_INIT_S(0); if (wnd.wait > BILLION) /* Don't care throttling < 1s */ s.tv_sec = 1; else diff --git a/src/ipcpd/unicast/ca/mb-ecn.h b/src/ipcpd/unicast/ca/mb-ecn.h index 53f23179..9a2c8b49 100644 --- a/src/ipcpd/unicast/ca/mb-ecn.h +++ b/src/ipcpd/unicast/ca/mb-ecn.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Multi-bit ECN Congestion Avoidance * diff --git a/src/ipcpd/unicast/ca/nop.c b/src/ipcpd/unicast/ca/nop.c index 5be826d4..617fc15b 100644 --- a/src/ipcpd/unicast/ca/nop.c +++ b/src/ipcpd/unicast/ca/nop.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Dummy Congestion Avoidance * diff --git a/src/ipcpd/unicast/ca/nop.h b/src/ipcpd/unicast/ca/nop.h index 25996552..248b198d 100644 --- a/src/ipcpd/unicast/ca/nop.h +++ b/src/ipcpd/unicast/ca/nop.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Dummy Congestion Avoidance * diff --git a/src/ipcpd/unicast/ca/ops.h b/src/ipcpd/unicast/ca/ops.h index ee0f028b..3a7b7248 100644 --- a/src/ipcpd/unicast/ca/ops.h +++ b/src/ipcpd/unicast/ca/ops.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Congestion avoidance policy ops * diff --git a/src/ipcpd/unicast/ca/pol.h b/src/ipcpd/unicast/ca/pol.h index 713c59b9..db0a1a11 100644 --- a/src/ipcpd/unicast/ca/pol.h +++ b/src/ipcpd/unicast/ca/pol.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Congestion avoidance policies * diff --git a/src/ipcpd/unicast/connmgr.c b/src/ipcpd/unicast/connmgr.c index 904deff8..07568fb5 100644 --- a/src/ipcpd/unicast/connmgr.c +++ b/src/ipcpd/unicast/connmgr.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Handles connections between components * @@ -32,8 +32,4 @@ #define BUILD_IPCP_UNICAST -#ifdef IPCP_CONN_WAIT_DIR - #include "dir.h" -#endif - #include "common/connmgr.c" diff --git a/src/ipcpd/unicast/dir.c b/src/ipcpd/unicast/dir.c index 870d0e10..2b305626 100644 --- a/src/ipcpd/unicast/dir.c +++ b/src/ipcpd/unicast/dir.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Directory Management * @@ -44,50 +44,57 @@ struct { struct dir_ops * ops; - void * dir; -} dirmgr; +} dir; -int dir_init(void) +int dir_init(struct dir_config * conf) { - dirmgr.ops = &dht_dir_ops; + void * cfg; - dirmgr.dir = dirmgr.ops->create(); - if (dirmgr.dir == NULL) { - dirmgr.ops = NULL; - return -ENOMEM; + assert(conf != NULL); + + switch (conf->pol) { + case DIR_DHT: + log_info("Using DHT policy."); + dir.ops = &dht_dir_ops; + cfg = &conf->dht; + break; + default: /* DIR_INVALID */ + log_err("Invalid directory policy %d.", conf->pol); + return -EINVAL; } - return 0; + assert(dir.ops->init != NULL); + + return dir.ops->init(cfg); } void dir_fini(void) { - dirmgr.ops->destroy(dirmgr.dir); - dirmgr.ops = NULL; - dirmgr.dir = NULL; + dir.ops->fini(); + dir.ops = NULL; } -int dir_bootstrap(void) +int dir_start(void) { - return dirmgr.ops->bootstrap(dirmgr.dir); + return dir.ops->start(); } -int dir_reg(const uint8_t * hash) +void dir_stop(void) { - return dirmgr.ops->reg(dirmgr.dir, hash); + dir.ops->stop(); } -int dir_unreg(const uint8_t * hash) +int dir_reg(const uint8_t * hash) { - return dirmgr.ops->unreg(dirmgr.dir, hash); + return dir.ops->reg(hash); } -uint64_t dir_query(const uint8_t * hash) +int dir_unreg(const uint8_t * hash) { - return dirmgr.ops->query(dirmgr.dir, hash); + return dir.ops->unreg(hash); } -int dir_wait_running(void) +uint64_t dir_query(const uint8_t * hash) { - return dirmgr.ops->wait_running(dirmgr.dir); + return dir.ops->query(hash); } diff --git a/src/ipcpd/unicast/dir.h b/src/ipcpd/unicast/dir.h index 8aa79638..dbfde19f 100644 --- a/src/ipcpd/unicast/dir.h +++ b/src/ipcpd/unicast/dir.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Directory * @@ -25,11 +25,14 @@ #include <inttypes.h> -int dir_init(void); +/* may update the config! */ +int dir_init(struct dir_config * conf); void dir_fini(void); -int dir_bootstrap(void); +int dir_start(void); + +void dir_stop(void); int dir_reg(const uint8_t * hash); @@ -37,6 +40,4 @@ int dir_unreg(const uint8_t * hash); uint64_t dir_query(const uint8_t * hash); -int dir_wait_running(void); - #endif /* OUROBOROS_IPCPD_UNICAST_DIR_H */ diff --git a/src/ipcpd/unicast/dir/dht.c b/src/ipcpd/unicast/dir/dht.c index 1742267b..69309091 100644 --- a/src/ipcpd/unicast/dir/dht.c +++ b/src/ipcpd/unicast/dir/dht.c @@ -1,10 +1,9 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Distributed Hash Table based on Kademlia * * Dimitri Staessens <dimitri@ouroboros.rocks> - * Sander Vrijders <sander@ouroboros.rocks> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License @@ -20,10 +19,12 @@ * Foundation, Inc., http://www.fsf.org/about/contact/. */ -#if defined(__linux__) || defined(__CYGWIN__) -#define _DEFAULT_SOURCE -#else -#define _POSIX_C_SOURCE 200112L +#if !defined (__DHT_TEST__) + #if defined(__linux__) || defined(__CYGWIN__) + #define _DEFAULT_SOURCE + #else + #define _POSIX_C_SOURCE 200112L + #endif #endif #include "config.h" @@ -31,19 +32,21 @@ #define DHT "dht" #define OUROBOROS_PREFIX DHT +#include <ouroboros/endian.h> #include <ouroboros/hash.h> #include <ouroboros/ipcp-dev.h> #include <ouroboros/bitmap.h> #include <ouroboros/errno.h> #include <ouroboros/logs.h> #include <ouroboros/list.h> -#include <ouroboros/notifier.h> #include <ouroboros/random.h> -#include <ouroboros/time_utils.h> +#include <ouroboros/rib.h> +#include <ouroboros/time.h> #include <ouroboros/tpm.h> #include <ouroboros/utils.h> #include <ouroboros/pthread.h> +#include "addr-auth.h" #include "common/connmgr.h" #include "dht.h" #include "dt.h" @@ -56,144 +59,154 @@ #include <inttypes.h> #include <limits.h> -#include "kademlia.pb-c.h" -typedef KadMsg kad_msg_t; -typedef KadContactMsg kad_contact_msg_t; +#include "dht.pb-c.h" +typedef DhtMsg dht_msg_t; +typedef DhtContactMsg dht_contact_msg_t; +typedef DhtStoreMsg dht_store_msg_t; +typedef DhtFindReqMsg dht_find_req_msg_t; +typedef DhtFindNodeRspMsg dht_find_node_rsp_msg_t; +typedef DhtFindValueRspMsg dht_find_value_rsp_msg_t; +typedef ProtobufCBinaryData binary_data_t; #ifndef CLOCK_REALTIME_COARSE #define CLOCK_REALTIME_COARSE CLOCK_REALTIME #endif -#define DHT_MAX_REQS 2048 /* KAD recommends rnd(), bmp can be changed. */ -#define KAD_ALPHA 3 /* Parallel factor, proven optimal value. */ -#define KAD_K 8 /* Replication factor, MDHT value. */ -#define KAD_T_REPL 900 /* Replication time, tied to k. MDHT value. */ -#define KAD_T_REFR 900 /* Refresh time stale bucket, MDHT value. */ -#define KAD_T_JOIN 8 /* Response time to wait for a join. */ -#define KAD_T_RESP 5 /* Response time to wait for a response. */ -#define KAD_R_PING 2 /* Ping retries before declaring peer dead. */ -#define KAD_QUEER 15 /* Time to declare peer questionable. */ -#define KAD_BETA 8 /* Bucket split factor, must be 1, 2, 4 or 8. */ -#define KAD_RESP_RETR 6 /* Number of retries on sending a response. */ -#define KAD_JOIN_RETR 8 /* Number of retries sending a join. */ -#define KAD_JOIN_INTV 1 /* Time (seconds) between join retries. */ +#define DHT_MAX_REQS 128 /* KAD recommends rnd(), bmp can be changed. */ +#define DHT_WARN_REQS 100 /* Warn if number of requests exceeds this. */ +#define DHT_MAX_VALS 8 /* Max number of values to return for a key. */ +#define DHT_T_CACHE 60 /* Max cache time for values (s) */ +#define DHT_T_RESP 2 /* Response time to wait for a response (s). */ +#define DHT_N_REPUB 5 /* Republish if expiry within n replications. */ +#define DHT_R_PING 2 /* Ping retries before declaring peer dead. */ +#define DHT_QUEER 15 /* Time to declare peer questionable. */ +#define DHT_BETA 8 /* Bucket split factor, must be 1, 2, 4 or 8. */ +#define DHT_RESP_RETR 6 /* Number of retries on sending a response. */ #define HANDLE_TIMEO 1000 /* Timeout for dht_handle_packet tpm check (ms) */ -#define DHT_RETR_ADDR 1 /* Number of addresses to return on retrieve */ +#define DHT_INVALID 0 /* Invalid cookie value. */ -enum dht_state { - DHT_INIT = 0, - DHT_SHUTDOWN, - DHT_JOINING, - DHT_RUNNING, -}; +#define KEY_FMT "K<" HASH_FMT64 ">" +#define KEY_VAL(key) HASH_VAL64(key) -enum kad_code { - KAD_JOIN = 0, - KAD_FIND_NODE, - KAD_FIND_VALUE, - /* Messages without a response below. */ - KAD_STORE, - KAD_RESPONSE -}; +#define VAL_FMT "V<" HASH_FMT64 ">" +#define VAL_VAL(val) HASH_VAL64((val).data) -enum kad_req_state { - REQ_NULL = 0, - REQ_INIT, - REQ_PENDING, - REQ_RESPONSE, - REQ_DONE, - REQ_DESTROY -}; +#define KV_FMT "<" HASH_FMT64 ", " HASH_FMT64 ">" +#define KV_VAL(key, val) HASH_VAL64(key), HASH_VAL64((val).data) -enum lookup_state { - LU_NULL = 0, - LU_INIT, - LU_PENDING, - LU_UPDATE, - LU_COMPLETE, - LU_DESTROY -}; +#define PEER_FMT "[" HASH_FMT64 "|" ADDR_FMT32 "]" +#define PEER_VAL(id, addr) HASH_VAL64(id), ADDR_VAL32(&(addr)) -struct kad_req { - struct list_head next; +#define DHT_CODE(msg) dht_code_str[(msg)->code] - uint32_t cookie; - enum kad_code code; - uint8_t * key; - uint64_t addr; +#define TX_HDR_FMT "%s --> " PEER_FMT +#define TX_HDR_VAL(msg, id, addr) DHT_CODE(msg), PEER_VAL(id, addr) - enum kad_req_state state; - pthread_cond_t cond; - pthread_mutex_t lock; +#define RX_HDR_FMT "%s <-- " PEER_FMT +#define RX_HDR_VAL(msg) DHT_CODE(msg), \ + PEER_VAL(msg->src->id.data, msg->src->addr) - time_t t_exp; +#define CK_FMT "|" HASH_FMT64 "|" +#define CK_VAL(cookie) HASH_VAL64(&(cookie)) + +#define IS_REQUEST(code) \ + (code == DHT_FIND_NODE_REQ || code == DHT_FIND_VALUE_REQ) + +enum dht_code { + DHT_STORE, + DHT_FIND_NODE_REQ, + DHT_FIND_NODE_RSP, + DHT_FIND_VALUE_REQ, + DHT_FIND_VALUE_RSP }; -struct cookie_el { - struct list_head next; +const char * dht_code_str[] = { + "DHT_STORE", + "DHT_FIND_NODE_REQ", + "DHT_FIND_NODE_RSP", + "DHT_FIND_VALUE_REQ", + "DHT_FIND_VALUE_RSP" +}; - uint32_t cookie; +enum dht_state { + DHT_NULL = 0, + DHT_INIT, + DHT_RUNNING }; -struct lookup { - struct list_head next; +struct val_entry { + struct list_head next; - struct list_head cookies; + buffer_t val; - uint8_t * key; + time_t t_exp; /* Expiry time */ + time_t t_repl; /* Last replication time */ +}; + +struct dht_entry { + struct list_head next; - struct list_head contacts; - size_t n_contacts; + uint8_t * key; - uint64_t * addrs; - size_t n_addrs; + struct { + struct list_head list; + size_t len; + } vals; /* We don't own these, only replicate */ - enum lookup_state state; - pthread_cond_t cond; - pthread_mutex_t lock; + struct { + struct list_head list; + size_t len; + } lvals; /* We own these, must be republished */ }; -struct val { +struct contact { struct list_head next; + uint8_t * id; uint64_t addr; - time_t t_exp; - time_t t_rep; + size_t fails; + time_t t_seen; }; -struct ref_entry { +struct peer_entry { struct list_head next; - uint8_t * key; + uint64_t cookie; + uint8_t * id; + uint64_t addr; + enum dht_code code; - time_t t_rep; + time_t t_sent; }; -struct dht_entry { +struct dht_req { struct list_head next; uint8_t * key; - size_t n_vals; - struct list_head vals; -}; - -struct contact { - struct list_head next; + time_t t_exp; - uint8_t * id; - uint64_t addr; + struct { + struct list_head list; + size_t len; + } peers; - size_t fails; - time_t t_seen; + struct { + struct list_head list; + size_t len; + } cache; }; struct bucket { - struct list_head contacts; - size_t n_contacts; + struct { + struct list_head list; + size_t len; + } contacts; - struct list_head alts; - size_t n_alts; + struct { + struct list_head list; + size_t len; + } alts; time_t t_refr; @@ -201,1081 +214,1464 @@ struct bucket { uint8_t mask; struct bucket * parent; - struct bucket * children[1L << KAD_BETA]; + struct bucket * children[1L << DHT_BETA]; }; struct cmd { - struct list_head next; - - struct shm_du_buff * sdb; + struct list_head next; + buffer_t cbuf; }; struct dir_ops dht_dir_ops = { - .create = dht_create, - .destroy = dht_destroy, - .bootstrap = dht_bootstrap, - .reg = dht_reg, - .unreg = dht_unreg, - .query = dht_query, - .wait_running = dht_wait_running + .init = (int (*)(void *)) dht_init, + .fini = dht_fini, + .start = dht_start, + .stop = dht_stop, + .reg = dht_reg, + .unreg = dht_unreg, + .query = dht_query }; -struct dht { - size_t alpha; - size_t b; - size_t k; +struct { + struct { /* Kademlia parameters */ + uint32_t alpha; /* Number of concurrent requests */ + size_t k; /* Number of replicas to store */ + time_t t_expire; /* Expiry time for values (s) */ + time_t t_refresh; /* Refresh time for contacts (s) */ + time_t t_repl; /* Replication time for values (s) */ + }; - time_t t_expire; - time_t t_refresh; - time_t t_replic; - time_t t_repub; + buffer_t id; - uint8_t * id; - uint64_t addr; + time_t t0; /* Creation time */ + uint64_t addr; /* Our own address */ + uint64_t peer; /* Enrollment peer address */ + uint64_t magic; /* Magic cookie for retransmit */ - struct bucket * buckets; + uint64_t eid; /* Entity ID */ - struct list_head entries; + struct tpm * tpm; + pthread_t worker; - struct list_head refs; + enum dht_state state; - struct list_head lookups; + struct { + struct { + struct bucket * root; + } contacts; + + struct { + struct list_head list; + size_t len; + size_t vals; + size_t lvals; + } kv; + + pthread_rwlock_t lock; + } db; + + struct { + struct list_head list; + size_t len; + pthread_cond_t cond; + pthread_mutex_t mtx; + } reqs; + + struct { + struct list_head list; + pthread_cond_t cond; + pthread_mutex_t mtx; + } cmds; +} dht; + + +/* DHT RIB */ + +static const char * dht_dir[] = { + "database", + "stats", + NULL +}; - struct list_head requests; - struct bmp * cookies; +const char * dht_stats = \ + "DHT: " HASH_FMT64 "\n" + " Created: %s\n" + " Address: " ADDR_FMT32 "\n" + " Kademlia parameters:\n" + " Number of concurrent requests (alpha): %10zu\n" + " Number of replicas (k): %10zu\n" + " Expiry time for values (s): %10ld\n" + " Refresh time for contacts (s): %10ld\n" + " Replication time for values (s): %10ld\n" + " Number of keys: %10zu\n" + " Number of local values: %10zu\n" + " Number of non-local values: %10zu\n"; - enum dht_state state; - struct list_head cmds; - pthread_cond_t cond; - pthread_mutex_t mtx; +static int dht_rib_statfile(char * buf, + size_t len) +{ + struct tm * tm; + char tmstr[RIB_TM_STRLEN]; + size_t keys; + size_t vals; + size_t lvals; - pthread_rwlock_t lock; + assert(buf != NULL); + assert(len > 0); - uint64_t eid; + pthread_rwlock_rdlock(&dht.db.lock); - struct tpm * tpm; + keys = dht.db.kv.len; + lvals = dht.db.kv.lvals; + vals = dht.db.kv.vals; - pthread_t worker; -}; + pthread_rwlock_unlock(&dht.db.lock); -struct join_info { - struct dht * dht; - uint64_t addr; -}; + tm = gmtime(&dht.t0); + strftime(tmstr, sizeof(tmstr), RIB_TM_FORMAT, tm); -struct packet_info { - struct dht * dht; - struct shm_du_buff * sdb; -}; + snprintf(buf, len, dht_stats, + HASH_VAL64(dht.id.data), + tmstr, + ADDR_VAL32(&dht.addr), + dht.alpha, dht.k, + dht.t_expire, dht.t_refresh, dht.t_repl, + keys, vals, lvals); -static uint8_t * dht_dup_key(const uint8_t * key, - size_t len) + return strlen(buf); +} + +static size_t dht_db_file_len(void) { - uint8_t * dup; + size_t sz; + size_t vals; - dup = malloc(sizeof(*dup) * len); - if (dup == NULL) - return NULL; + sz = 18; /* DHT database + 2 * \n */ - memcpy(dup, key, len); + pthread_rwlock_rdlock(&dht.db.lock); - return dup; -} + if (dht.db.kv.len == 0) { + pthread_rwlock_unlock(&dht.db.lock); + sz += 14; /* No entries */ + return sz; + } -static enum dht_state dht_get_state(struct dht * dht) -{ - enum dht_state state; + sz += 39 * 3 + 1; /* tally + extra newline */ + sz += dht.db.kv.len * (25 + 19 + 23 + 1); - pthread_mutex_lock(&dht->mtx); + vals = dht.db.kv.vals + dht.db.kv.lvals; - state = dht->state; + sz += vals * (48 + 2 * RIB_TM_STRLEN); - pthread_mutex_unlock(&dht->mtx); + pthread_rwlock_unlock(&dht.db.lock); - return state; + return sz; } -static int dht_set_state(struct dht * dht, - enum dht_state state) +static int dht_rib_dbfile(char * buf, + size_t len) { - pthread_mutex_lock(&dht->mtx); + struct tm * tm; + char tmstr[RIB_TM_STRLEN]; + char exstr[RIB_TM_STRLEN]; + size_t i = 0; + struct list_head * p; + + assert(buf != NULL); + assert(len > 0); + + pthread_rwlock_rdlock(&dht.db.lock); - if (state == DHT_JOINING && dht->state != DHT_INIT) { - pthread_mutex_unlock(&dht->mtx); - return -1; + if (dht.db.kv.len == 0) { + i += snprintf(buf, len, " No entries.\n"); + pthread_rwlock_unlock(&dht.db.lock); + return i; } - dht->state = state; + i += snprintf(buf + i, len - i, "DHT database:\n\n"); + i += snprintf(buf + i, len - i, + "Number of keys: %10zu\n" + "Number of local values: %10zu\n" + "Number of non-local values: %10zu\n\n", + dht.db.kv.len, dht.db.kv.vals, dht.db.kv.lvals); - pthread_cond_broadcast(&dht->cond); + list_for_each(p, &dht.db.kv.list) { + struct dht_entry * e = list_entry(p, struct dht_entry, next); + struct list_head * h; - pthread_mutex_unlock(&dht->mtx); + i += snprintf(buf + i, len - i, "Key: " KEY_FMT "\n", + KEY_VAL(e->key)); + i += snprintf(buf + i, len - i, " Local entries:\n"); - return 0; -} + list_for_each(h, &e->vals.list) { + struct val_entry * v; -int dht_wait_running(void * dir) -{ - struct dht * dht; - int ret = 0; + v = list_entry(h, struct val_entry, next); - dht = (struct dht *) dir; + tm = gmtime(&v->t_repl); + strftime(tmstr, sizeof(tmstr), RIB_TM_FORMAT, tm); - pthread_mutex_lock(&dht->mtx); + tm = gmtime(&v->t_exp); + strftime(exstr, sizeof(exstr), RIB_TM_FORMAT, tm); - pthread_cleanup_push(__cleanup_mutex_unlock, &dht->mtx); + i += snprintf(buf + i, len - i, + " " VAL_FMT + ", t_replicated=%.*s, t_expire=%.*s\n", + VAL_VAL(v->val), + RIB_TM_STRLEN, tmstr, + RIB_TM_STRLEN, exstr); + } - while (dht->state == DHT_JOINING) - pthread_cond_wait(&dht->cond, &dht->mtx); + i += snprintf(buf + i, len - i, "\n"); - if (dht->state != DHT_RUNNING) - ret = -1; + i += snprintf(buf + i, len - i, " Non-local entries:\n"); - pthread_cleanup_pop(true); + list_for_each(h, &e->lvals.list) { + struct val_entry * v; - return ret; + v= list_entry(h, struct val_entry, next); + + tm = gmtime(&v->t_repl); + strftime(tmstr, sizeof(tmstr), RIB_TM_FORMAT, tm); + + tm = gmtime(&v->t_exp); + strftime(exstr, sizeof(exstr), RIB_TM_FORMAT, tm); + + i += snprintf(buf + i, len - i, + " " VAL_FMT + ", t_replicated=%.*s, t_expire=%.*s\n", + VAL_VAL(v->val), + RIB_TM_STRLEN, tmstr, + RIB_TM_STRLEN, exstr); + + } + } + + pthread_rwlock_unlock(&dht.db.lock); + + printf("DHT RIB DB file generated (%zu bytes).\n", i); + + return i; } -static uint8_t * create_id(size_t len) +static int dht_rib_read(const char * path, + char * buf, + size_t len) { - uint8_t * id; + char * entry; - id = malloc(len); - if (id == NULL) - return NULL; + entry = strstr(path, RIB_SEPARATOR) + 1; - if (random_buffer(id, len) < 0) { - free(id); - return NULL; + if (strcmp(entry, "database") == 0) { + return dht_rib_dbfile(buf, len); + } else if (strcmp(entry, "stats") == 0) { + return dht_rib_statfile(buf, len); } - return id; + return 0; } -static void kad_req_create(struct dht * dht, - kad_msg_t * msg, - uint64_t addr) +static int dht_rib_readdir(char *** buf) { - struct kad_req * req; - pthread_condattr_t cattr; - struct timespec t; - size_t b; + int i = 0; - req = malloc(sizeof(*req)); - if (req == NULL) - return; + while (dht_dir[i++] != NULL); - list_head_init(&req->next); + *buf = malloc(sizeof(**buf) * i); + if (*buf == NULL) + goto fail_buf; - clock_gettime(CLOCK_REALTIME_COARSE, &t); + i = 0; - req->t_exp = t.tv_sec + KAD_T_RESP; - req->addr = addr; - req->state = REQ_INIT; - req->cookie = msg->cookie; - req->code = msg->code; - req->key = NULL; - - pthread_rwlock_rdlock(&dht->lock); - b = dht->b; - pthread_rwlock_unlock(&dht->lock); - - if (msg->has_key) { - req->key = dht_dup_key(msg->key.data, b); - if (req->key == NULL) { - free(req); - return; - } + while (dht_dir[i] != NULL) { + (*buf)[i] = strdup(dht_dir[i]); + if ((*buf)[i] == NULL) + goto fail_dup; + i++; } - if (pthread_mutex_init(&req->lock, NULL)) { - free(req->key); - free(req); - return; - } + return i; + fail_dup: + freepp(char, *buf, i); + fail_buf: + return -ENOMEM; +} - pthread_condattr_init(&cattr); -#ifndef __APPLE__ - pthread_condattr_setclock(&cattr, PTHREAD_COND_CLOCK); -#endif +static int dht_rib_getattr(const char * path, + struct rib_attr * attr) +{ + struct timespec now; + char * entry; - if (pthread_cond_init(&req->cond, &cattr)) { - pthread_condattr_destroy(&cattr); - pthread_mutex_destroy(&req->lock); - free(req->key); - free(req); - return; - } + clock_gettime(CLOCK_REALTIME_COARSE, &now); - pthread_condattr_destroy(&cattr); + attr->mtime = now.tv_sec; - pthread_rwlock_wrlock(&dht->lock); + entry = strstr(path, RIB_SEPARATOR) + 1; - list_add(&req->next, &dht->requests); + if (strcmp(entry, "database") == 0) { + attr->size = dht_db_file_len(); + } else if (strcmp(entry, "stats") == 0) { + attr->size = 545; + } - pthread_rwlock_unlock(&dht->lock); + return 0; } -static void cancel_req_destroy(void * o) +static struct rib_ops r_ops = { + .read = dht_rib_read, + .readdir = dht_rib_readdir, + .getattr = dht_rib_getattr +}; + +/* Helper functions */ + +static uint8_t * generate_id(void) { - struct kad_req * req = (struct kad_req *) o; + uint8_t * id; - pthread_mutex_unlock(&req->lock); + if(dht.id.len < sizeof(uint64_t)) { + log_err("DHT ID length is too short (%zu < %zu).", + dht.id.len, sizeof(uint64_t)); + return NULL; + } - pthread_cond_destroy(&req->cond); - pthread_mutex_destroy(&req->lock); + id = malloc(dht.id.len); + if (id == NULL) { + log_err("Failed to malloc ID."); + goto fail_id; + } - if (req->key != NULL) - free(req->key); + if (random_buffer(id, dht.id.len) < 0) { + log_err("Failed to generate random ID."); + goto fail_rnd; + } - free(req); + return id; + fail_rnd: + free(id); + fail_id: + return NULL; } -static void kad_req_destroy(struct kad_req * req) +static uint64_t generate_cookie(void) { - assert(req); + uint64_t cookie = DHT_INVALID; - pthread_mutex_lock(&req->lock); + while (cookie == DHT_INVALID) + random_buffer((uint8_t *) &cookie, sizeof(cookie)); - switch (req->state) { - case REQ_DESTROY: - pthread_mutex_unlock(&req->lock); - return; - case REQ_PENDING: - req->state = REQ_DESTROY; - pthread_cond_signal(&req->cond); - break; - case REQ_INIT: - case REQ_DONE: - req->state = REQ_NULL; - break; - case REQ_RESPONSE: - case REQ_NULL: - default: - break; - } - - pthread_cleanup_push(cancel_req_destroy, req); + return cookie; +} - while (req->state != REQ_NULL && req->state != REQ_DONE) - pthread_cond_wait(&req->cond, &req->lock); +/* + * If someone builds a network where the n (n > k) closest nodes all + * have IDs starting with the same 64 bits: by all means, change this. + */ +static uint64_t dist(const uint8_t * src, + const uint8_t * dst) +{ + assert(dht.id.len >= sizeof(uint64_t)); - pthread_cleanup_pop(true); + return betoh64(*((uint64_t *) src) ^ *((uint64_t *) dst)); } -static int kad_req_wait(struct kad_req * req, - time_t t) +#define IS_CLOSER(x, y) (dist((x), dht.id.data) < dist((y), dht.id.data)) + +static int addr_to_buf(const uint64_t addr, + buffer_t * buf) { - struct timespec timeo = {t, 0}; - struct timespec abs; - int ret = 0; + size_t len; + uint64_t _addr; - assert(req); + len = sizeof(addr); + _addr = hton64(addr); - clock_gettime(PTHREAD_COND_CLOCK, &abs); + assert(buf != NULL); - ts_add(&abs, &timeo, &abs); + buf->data = malloc(len); + if (buf->data == NULL) + goto fail_malloc; - pthread_mutex_lock(&req->lock); + buf->len = sizeof(_addr); + memcpy(buf->data, &_addr, sizeof(_addr)); - req->state = REQ_PENDING; + return 0; + fail_malloc: + return -ENOMEM; +} - pthread_cleanup_push(__cleanup_mutex_unlock, &req->lock); +static int buf_to_addr(const buffer_t buf, + uint64_t * addr) +{ + assert(addr != NULL); + assert(buf.data != NULL); - while (req->state == REQ_PENDING && ret != -ETIMEDOUT) - ret = -pthread_cond_timedwait(&req->cond, &req->lock, &abs); + if (buf.len != sizeof(*addr)) + return - EINVAL; - switch(req->state) { - case REQ_DESTROY: - ret = -1; - req->state = REQ_NULL; - pthread_cond_signal(&req->cond); - break; - case REQ_PENDING: /* ETIMEDOUT */ - case REQ_RESPONSE: - req->state = REQ_DONE; - pthread_cond_broadcast(&req->cond); - break; - default: - break; - } + *addr = ntoh64(*((uint64_t *) buf.data)); - pthread_cleanup_pop(true); + if (*addr == dht.addr) + *addr = INVALID_ADDR; - return ret; + return 0; } -static void kad_req_respond(struct kad_req * req) +static uint8_t * dht_dup_key(const uint8_t * key) { - pthread_mutex_lock(&req->lock); + uint8_t * dup; - req->state = REQ_RESPONSE; - pthread_cond_signal(&req->cond); + assert(key != NULL); + assert(dht.id.len != 0); - pthread_mutex_unlock(&req->lock); + dup = malloc(dht.id.len); + if (dup == NULL) + return NULL; + + memcpy(dup, key, dht.id.len); + + return dup; } -static struct contact * contact_create(const uint8_t * id, - size_t len, - uint64_t addr) +/* DHT */ + +static struct val_entry * val_entry_create(const buffer_t val, + time_t exp) { - struct contact * c; - struct timespec t; + struct val_entry * e; + struct timespec now; - c = malloc(sizeof(*c)); - if (c == NULL) - return NULL; + assert(val.data != NULL); + assert(val.len > 0); - list_head_init(&c->next); + clock_gettime(CLOCK_REALTIME_COARSE, &now); - clock_gettime(CLOCK_REALTIME_COARSE, &t); +#ifndef __DHT_TEST_ALLOW_EXPIRED__ + if (exp < now.tv_sec) + return NULL; /* Refuse to add expired values */ +#endif + e = malloc(sizeof(*e)); + if (e == NULL) + goto fail_entry; - c->addr = addr; - c->fails = 0; - c->t_seen = t.tv_sec; - c->id = dht_dup_key(id, len); - if (c->id == NULL) { - free(c); - return NULL; - } + list_head_init(&e->next); - return c; + e->val.len = val.len; + e->val.data = malloc(val.len); + if (e->val.data == NULL) + goto fail_val; + + memcpy(e->val.data, val.data, val.len); + + e->t_repl = 0; + e->t_exp = exp; + + return e; + + fail_val: + free(e); + fail_entry: + return NULL; } -static void contact_destroy(struct contact * c) +static void val_entry_destroy(struct val_entry * v) { - if (c != NULL) - free(c->id); + assert(v->val.data != NULL); - free(c); + freebuf(v->val); + free(v); } -static struct bucket * iter_bucket(struct bucket * b, - const uint8_t * id) +static struct dht_entry * dht_entry_create(const uint8_t * key) { - uint8_t byte; - uint8_t mask; + struct dht_entry * e; - assert(b); + assert(key != NULL); - if (b->children[0] == NULL) - return b; + e = malloc(sizeof(*e)); + if (e == NULL) + goto fail_entry; - byte = id[(b->depth * KAD_BETA) / CHAR_BIT]; + list_head_init(&e->next); + list_head_init(&e->vals.list); + list_head_init(&e->lvals.list); - mask = ((1L << KAD_BETA) - 1) & 0xFF; + e->vals.len = 0; + e->lvals.len = 0; - byte >>= (CHAR_BIT - KAD_BETA) - - (((b->depth) * KAD_BETA) & (CHAR_BIT - 1)); + e->key = dht_dup_key(key); + if (e->key == NULL) + goto fail_key; - return iter_bucket(b->children[(byte & mask)], id); + return e; + fail_key: + free(e); + fail_entry: + return NULL; } -static struct bucket * dht_get_bucket(struct dht * dht, - const uint8_t * id) +static void dht_entry_destroy(struct dht_entry * e) { - assert(dht->buckets); + struct list_head * p; + struct list_head * h; - return iter_bucket(dht->buckets, id); + assert(e != NULL); + + list_for_each_safe(p, h, &e->vals.list) { + struct val_entry * v = list_entry(p, struct val_entry, next); + list_del(&v->next); + val_entry_destroy(v); + --e->vals.len; + --dht.db.kv.vals; + } + + list_for_each_safe(p, h, &e->lvals.list) { + struct val_entry * v = list_entry(p, struct val_entry, next); + list_del(&v->next); + val_entry_destroy(v); + --e->lvals.len; + --dht.db.kv.lvals; + } + + free(e->key); + + assert(e->vals.len == 0 && e->lvals.len == 0); + + free(e); } -/* - * If someone builds a network where the n (n > k) closest nodes all - * have IDs starting with the same 64 bits: by all means, change this. - */ -static uint64_t dist(const uint8_t * src, - const uint8_t * dst) +static struct val_entry * dht_entry_get_lval(const struct dht_entry * e, + const buffer_t val) { - return betoh64(*((uint64_t *) src) ^ *((uint64_t *) dst)); + struct list_head * p; + + assert(e != NULL); + assert(val.data != NULL); + assert(val.len > 0); + + list_for_each(p, &e->lvals.list) { + struct val_entry * v = list_entry(p, struct val_entry, next); + if (bufcmp(&v->val, &val) == 0) + return v; + } + + return NULL; } -static size_t list_add_sorted(struct list_head * l, - struct contact * c, - const uint8_t * key) +static struct val_entry * dht_entry_get_val(const struct dht_entry * e, + const buffer_t val) { struct list_head * p; - assert(l); - assert(c); - assert(key); - assert(c->id); + assert(e != NULL); + assert(val.data != NULL); + assert(val.len > 0); - list_for_each(p, l) { - struct contact * e = list_entry(p, struct contact, next); - if (dist(c->id, key) > dist(e->id, key)) - break; - } + list_for_each(p, &e->vals.list) { + struct val_entry * v = list_entry(p, struct val_entry, next); + if (bufcmp(&v->val, &val) == 0) + return v; - list_add_tail(&c->next, p); + } - return 1; + return NULL; } -static size_t dht_contact_list(struct dht * dht, - struct list_head * l, - const uint8_t * key) +static int dht_entry_update_val(struct dht_entry * e, + buffer_t val, + time_t exp) { - struct list_head * p; - struct bucket * b; - size_t len = 0; - size_t i; - struct timespec t; + struct val_entry * v; + struct timespec now; - assert(l); - assert(dht); - assert(key); - assert(list_is_empty(l)); + assert(e != NULL); + assert(val.data != NULL); + assert(val.len > 0); - clock_gettime(CLOCK_REALTIME_COARSE, &t); + clock_gettime(CLOCK_REALTIME_COARSE, &now); - b = dht_get_bucket(dht, key); - if (b == NULL) - return 0; + if (exp < now.tv_sec) + return -EINVAL; /* Refuse to add expired values */ - b->t_refr = t.tv_sec + KAD_T_REFR; + if (dht_entry_get_lval(e, val) != NULL) { + log_dbg(KV_FMT " Val already in lvals.", KV_VAL(e->key, val)); + return 0; /* Refuse to add local values */ + } - if (b->n_contacts == dht->k || b->parent == NULL) { - list_for_each(p, &b->contacts) { - struct contact * c; - c = list_entry(p, struct contact, next); - c = contact_create(c->id, dht->b, c->addr); - if (list_add_sorted(l, c, key) == 1) - if (++len == dht->k) - break; - } - } else { - struct bucket * d = b->parent; - for (i = 0; i < (1L << KAD_BETA) && len < dht->k; ++i) { - list_for_each(p, &d->children[i]->contacts) { - struct contact * c; - c = list_entry(p, struct contact, next); - c = contact_create(c->id, dht->b, c->addr); - if (c == NULL) - continue; - if (list_add_sorted(l, c, key) == 1) - if (++len == dht->k) - break; - } - } + v = dht_entry_get_val(e, val); + if (v == NULL) { + v = val_entry_create(val, exp); + if (v == NULL) + return -ENOMEM; + + list_add_tail(&v->next, &e->vals.list); + ++e->vals.len; + ++dht.db.kv.vals; + + return 0; } - assert(len == dht->k || b->parent == NULL); + if (v->t_exp < exp) + v->t_exp = exp; - return len; + return 0; } -static struct lookup * lookup_create(struct dht * dht, - const uint8_t * id) +static int dht_entry_update_lval(struct dht_entry * e, + buffer_t val) { - struct lookup * lu; - pthread_condattr_t cattr; + struct val_entry * v; + struct timespec now; - assert(dht); - assert(id); + assert(e != NULL); + assert(val.data != NULL); + assert(val.len > 0); - lu = malloc(sizeof(*lu)); - if (lu == NULL) - goto fail_malloc; + clock_gettime(CLOCK_REALTIME_COARSE, &now); - list_head_init(&lu->contacts); - list_head_init(&lu->cookies); + v = dht_entry_get_lval(e, val); + if (v == NULL) { + log_dbg(KV_FMT " Adding lval.", KV_VAL(e->key, val)); + v = val_entry_create(val, now.tv_sec + dht.t_expire); + if (v == NULL) + return -ENOMEM; - lu->state = LU_INIT; - lu->addrs = NULL; - lu->n_addrs = 0; - lu->key = dht_dup_key(id, dht->b); - if (lu->key == NULL) - goto fail_id; + list_add_tail(&v->next, &e->lvals.list); + ++e->lvals.len; + ++dht.db.kv.lvals; - if (pthread_mutex_init(&lu->lock, NULL)) - goto fail_mutex; + return 0; + } - pthread_condattr_init(&cattr); -#ifndef __APPLE__ - pthread_condattr_setclock(&cattr, PTHREAD_COND_CLOCK); -#endif + return 0; +} - if (pthread_cond_init(&lu->cond, &cattr)) - goto fail_cond; +static int dht_entry_remove_lval(struct dht_entry * e, + buffer_t val) +{ + struct val_entry * v; - pthread_condattr_destroy(&cattr); + assert(e != NULL); + assert(val.data != NULL); + assert(val.len > 0); - pthread_rwlock_wrlock(&dht->lock); + v = dht_entry_get_lval(e, val); + if (v == NULL) + return -ENOENT; - list_add(&lu->next, &dht->lookups); + log_dbg(KV_FMT " Removing lval.", KV_VAL(e->key, val)); - lu->n_contacts = dht_contact_list(dht, &lu->contacts, id); + list_del(&v->next); + val_entry_destroy(v); + --e->lvals.len; + --dht.db.kv.lvals; - pthread_rwlock_unlock(&dht->lock); + return 0; +} - return lu; +#define IS_EXPIRED(v, now) ((now)->tv_sec > (v)->t_exp) +static void dht_entry_remove_expired_vals(struct dht_entry * e) +{ + struct list_head * p; + struct list_head * h; + struct timespec now; + + assert(e != NULL); + + clock_gettime(CLOCK_REALTIME_COARSE, &now); + + list_for_each_safe(p, h, &e->vals.list) { + struct val_entry * v = list_entry(p, struct val_entry, next); + if (!IS_EXPIRED(v, &now)) + continue; + + log_dbg(KV_FMT " Value expired." , KV_VAL(e->key, v->val)); + list_del(&v->next); + val_entry_destroy(v); + --e->vals.len; + --dht.db.kv.vals; + } +} + +static struct dht_entry * __dht_kv_find_entry(const uint8_t * key) +{ + struct list_head * p; + + assert(key != NULL); + + list_for_each(p, &dht.db.kv.list) { + struct dht_entry * e = list_entry(p, struct dht_entry, next); + if (!memcmp(key, e->key, dht.id.len)) + return e; + } - fail_cond: - pthread_condattr_destroy(&cattr); - pthread_mutex_destroy(&lu->lock); - fail_mutex: - free(lu->key); - fail_id: - free(lu); - fail_malloc: return NULL; } -static void cancel_lookup_destroy(void * o) +static void dht_kv_remove_expired_entries(void) { - struct lookup * lu; struct list_head * p; struct list_head * h; + struct timespec now; - lu = (struct lookup *) o; + clock_gettime(CLOCK_REALTIME_COARSE, &now); - if (lu->key != NULL) - free(lu->key); - if (lu->addrs != NULL) - free(lu->addrs); + pthread_rwlock_wrlock(&dht.db.lock); - list_for_each_safe(p, h, &lu->contacts) { - struct contact * c = list_entry(p, struct contact, next); - list_del(&c->next); - contact_destroy(c); + list_for_each_safe(p, h, &dht.db.kv.list) { + struct dht_entry * e = list_entry(p, struct dht_entry, next); + dht_entry_remove_expired_vals(e); + if (e->lvals.len > 0 || e->vals.len > 0) + continue; + + log_dbg(KEY_FMT " Entry removed. ", KEY_VAL(e->key)); + list_del(&e->next); + dht_entry_destroy(e); + --dht.db.kv.len; } - list_for_each_safe(p, h, &lu->cookies) { - struct cookie_el * c = list_entry(p, struct cookie_el, next); - list_del(&c->next); + pthread_rwlock_unlock(&dht.db.lock); +} + + +static struct contact * contact_create(const uint8_t * id, + uint64_t addr) +{ + struct contact * c; + struct timespec t; + + c = malloc(sizeof(*c)); + if (c == NULL) + return NULL; + + list_head_init(&c->next); + + clock_gettime(CLOCK_REALTIME_COARSE, &t); + + c->addr = addr; + c->fails = 0; + c->t_seen = t.tv_sec; + c->id = dht_dup_key(id); + if (c->id == NULL) { free(c); + return NULL; } - pthread_mutex_unlock(&lu->lock); + return c; +} - pthread_mutex_destroy(&lu->lock); +static void contact_destroy(struct contact * c) +{ + assert(c != NULL); + assert(list_is_empty(&c->next)); - free(lu); + free(c->id); + free(c); } -static void lookup_destroy(struct lookup * lu) +static struct dht_req * dht_req_create(const uint8_t * key) { - assert(lu); + struct dht_req * req; + struct timespec now; - pthread_mutex_lock(&lu->lock); + assert(key != NULL); - switch (lu->state) { - case LU_DESTROY: - pthread_mutex_unlock(&lu->lock); - return; - case LU_PENDING: - lu->state = LU_DESTROY; - pthread_cond_broadcast(&lu->cond); - break; - case LU_INIT: - case LU_UPDATE: - case LU_COMPLETE: - lu->state = LU_NULL; - break; - case LU_NULL: - default: - break; - } + clock_gettime(PTHREAD_COND_CLOCK, &now); - pthread_cleanup_push(cancel_lookup_destroy, lu); + req = malloc(sizeof(*req)); + if (req == NULL) + goto fail_malloc; - while (lu->state != LU_NULL) - pthread_cond_wait(&lu->cond, &lu->lock); + list_head_init(&req->next); - pthread_cleanup_pop(true); + req->t_exp = now.tv_sec + DHT_T_RESP; + + list_head_init(&req->peers.list); + req->peers.len = 0; + + req->key = dht_dup_key(key); + if (req->key == NULL) + goto fail_dup_key; + + list_head_init(&req->cache.list); + req->cache.len = 0; + + return req; + + fail_dup_key: + free(req); + fail_malloc: + return NULL; } -static void lookup_update(struct dht * dht, - struct lookup * lu, - kad_msg_t * msg) +static void dht_req_destroy(struct dht_req * req) { - struct list_head * p = NULL; + struct list_head * p; struct list_head * h; - struct contact * c = NULL; - size_t n; - size_t pos = 0; - bool mod = false; - - assert(lu); - assert(msg); - if (dht_get_state(dht) != DHT_RUNNING) - return; - - pthread_mutex_lock(&lu->lock); + assert(req); + assert(req->key); - list_for_each_safe(p, h, &lu->cookies) { - struct cookie_el * e = list_entry(p, struct cookie_el, next); - if (e->cookie == msg->cookie) { - list_del(&e->next); - free(e); - break; - } + list_for_each_safe(p, h, &req->peers.list) { + struct peer_entry * e = list_entry(p, struct peer_entry, next); + list_del(&e->next); + free(e->id); + free(e); + --req->peers.len; } - if (lu->state == LU_COMPLETE) { - pthread_mutex_unlock(&lu->lock); - return; + list_for_each_safe(p, h, &req->cache.list) { + struct val_entry * e = list_entry(p, struct val_entry, next); + list_del(&e->next); + val_entry_destroy(e); + --req->cache.len; } - if (msg->n_addrs > 0) { - if (lu->addrs == NULL) { - lu->addrs = malloc(sizeof(*lu->addrs) * msg->n_addrs); - for (n = 0; n < msg->n_addrs; ++n) - lu->addrs[n] = msg->addrs[n]; - lu->n_addrs = msg->n_addrs; - } + free(req->key); - lu->state = LU_COMPLETE; - pthread_cond_broadcast(&lu->cond); - pthread_mutex_unlock(&lu->lock); - return; - } + assert(req->peers.len == 0); - pthread_cleanup_push(__cleanup_mutex_unlock, &lu->lock); + free(req); +} - while (lu->state == LU_INIT) { - pthread_rwlock_unlock(&dht->lock); - pthread_cond_wait(&lu->cond, &lu->lock); - pthread_rwlock_rdlock(&dht->lock); - } +static struct peer_entry * dht_req_get_peer(struct dht_req * req, + struct peer_entry * e) +{ + struct list_head * p; - pthread_cleanup_pop(false); + list_for_each(p, &req->peers.list) { + struct peer_entry * x = list_entry(p, struct peer_entry, next); + if (x->addr == e->addr) + return x; + } - for (n = 0; n < msg->n_contacts; ++n) { - c = contact_create(msg->contacts[n]->id.data, - dht->b, msg->contacts[n]->addr); - if (c == NULL) - continue; + return NULL; +} - pos = 0; +#define IS_MAGIC(peer) ((peer)->cookie == dht.magic) +void dht_req_add_peer(struct dht_req * req, + struct peer_entry * e) +{ + struct peer_entry * x; /* existing */ + struct list_head * p; /* iterator */ + size_t pos = 0; - list_for_each(p, &lu->contacts) { - struct contact * e; - e = list_entry(p, struct contact, next); - if (!memcmp(e->id, c->id, dht->b)) { - contact_destroy(c); - c = NULL; - break; - } + assert(req != NULL); + assert(e != NULL); + assert(e->id != NULL); - if (dist(c->id, lu->key) > dist(e->id, lu->key)) - break; + /* + * Dedupe messages to the same peer, unless + * 1) The previous request was FIND_NODE and now it's FIND_VALUE + * 2) We urgently need contacts from emergency peer (magic cookie) + */ + x = dht_req_get_peer(req, e); + if (x != NULL && x->code >= e->code && !IS_MAGIC(e)) + goto skip; + /* Find how this contact ranks in distance to the key */ + list_for_each(p, &req->peers.list) { + struct peer_entry * y = list_entry(p, struct peer_entry, next); + if (IS_CLOSER(y->id, e->id)) { pos++; - } - - if (c == NULL) continue; - - if (lu->n_contacts < dht->k) { - list_add_tail(&c->next, p); - ++lu->n_contacts; - mod = true; - } else if (pos == dht->k) { - contact_destroy(c); - } else { - struct contact * d; - list_add_tail(&c->next, p); - d = list_last_entry(&lu->contacts, - struct contact, next); - list_del(&d->next); - assert(lu->contacts.prv != &d->next); - contact_destroy(d); - mod = true; } + break; } - if (list_is_empty(&lu->cookies) && !mod) - lu->state = LU_COMPLETE; - else - lu->state = LU_UPDATE; + /* Add a new peer to this request if we need to */ + if (pos < dht.alpha || !IS_MAGIC(e)) { + x = malloc(sizeof(*x)); + if (x == NULL) { + log_err("Failed to malloc peer entry."); + goto skip; + } - pthread_cond_broadcast(&lu->cond); - pthread_mutex_unlock(&lu->lock); - return; + x->cookie = e->cookie; + x->addr = e->addr; + x->code = e->code; + x->t_sent = e->t_sent; + x->id = dht_dup_key(e->id); + if (x->id == NULL) { + log_err("Failed to dup peer ID."); + free(x); + goto skip; + } + + if (IS_MAGIC(e)) + list_add(&x->next, p); + else + list_add_tail(&x->next, p); + ++req->peers.len; + return; + } + skip: + list_del(&e->next); + free(e->id); + free(e); } -static ssize_t lookup_get_addrs(struct lookup * lu, - uint64_t * addrs) +static size_t dht_req_add_peers(struct dht_req * req, + struct list_head * pl) { - ssize_t n; + struct list_head * p; + struct list_head * h; + size_t n = 0; - assert(lu); + assert(req != NULL); + assert(pl != NULL); - pthread_mutex_lock(&lu->lock); - - for (n = 0; (size_t) n < lu->n_addrs; ++n) - addrs[n] = lu->addrs[n]; - - assert((size_t) n == lu->n_addrs); - - pthread_mutex_unlock(&lu->lock); + list_for_each_safe(p, h, pl) { + struct peer_entry * e = list_entry(p, struct peer_entry, next); + dht_req_add_peer(req, e); + } return n; } -static ssize_t lookup_contact_addrs(struct lookup * lu, - uint64_t * addrs) +static bool dht_req_has_peer(struct dht_req * req, + uint64_t cookie) { struct list_head * p; - ssize_t n = 0; - - assert(lu); - assert(addrs); - pthread_mutex_lock(&lu->lock); + assert(req != NULL); - list_for_each(p, &lu->contacts) { - struct contact * c = list_entry(p, struct contact, next); - addrs[n] = c->addr; - n++; + list_for_each(p, &req->peers.list) { + struct peer_entry * e = list_entry(p, struct peer_entry, next); + if (e->cookie == cookie) + return true; } - pthread_mutex_unlock(&lu->lock); - - return n; + return false; } -static void lookup_new_addrs(struct lookup * lu, - uint64_t * addrs) +static void peer_list_destroy(struct list_head * pl) { struct list_head * p; - size_t n = 0; + struct list_head * h; - assert(lu); - assert(addrs); + assert(pl != NULL); - pthread_mutex_lock(&lu->lock); + list_for_each_safe(p, h, pl) { + struct peer_entry * e = list_entry(p, struct peer_entry, next); + list_del(&e->next); + free(e->id); + free(e); + } +} - /* Uses fails to check if the contact has been contacted. */ - list_for_each(p, &lu->contacts) { - struct contact * c = list_entry(p, struct contact, next); - if (c->fails == 0) { - c->fails = 1; - addrs[n] = c->addr; - n++; - } +static int dht_kv_create_peer_list(struct list_head * cl, + struct list_head * pl, + enum dht_code code) +{ + struct list_head * p; + struct list_head * h; + struct timespec now; + size_t len; + + assert(cl != NULL); + assert(pl != NULL); + assert(list_is_empty(pl)); + + clock_gettime(CLOCK_REALTIME_COARSE, &now); - if (n == KAD_ALPHA) + len = 0; + + list_for_each_safe(p, h, cl) { + struct contact * c = list_entry(p, struct contact, next); + struct peer_entry * e; + if (len++ == dht.alpha) break; - } - assert(n <= KAD_ALPHA); + e = malloc(sizeof(*e)); + if (e == NULL) + return -ENOMEM; - addrs[n] = 0; + e->cookie = generate_cookie(); + e->code = code; + e->addr = c->addr; + e->t_sent = now.tv_sec; - pthread_mutex_unlock(&lu->lock); -} + e->id = c->id; -static void lookup_set_state(struct lookup * lu, - enum lookup_state state) -{ - pthread_mutex_lock(&lu->lock); + list_add_tail(&e->next, pl); - lu->state = state; - pthread_cond_broadcast(&lu->cond); + list_del(&c->next); + c->id = NULL; /* we stole the id */ + contact_destroy(c); + } - pthread_mutex_unlock(&lu->lock); + return 0; } -static void cancel_lookup_wait(void * o) +static struct dht_req * __dht_kv_req_get_req(const uint8_t * key) { - struct lookup * lu = (struct lookup *) o; - lu->state = LU_NULL; - pthread_mutex_unlock(&lu->lock); - lookup_destroy(lu); + struct list_head * p; + + list_for_each(p, &dht.reqs.list) { + struct dht_req * r = list_entry(p, struct dht_req, next); + if (memcmp(r->key, key, dht.id.len) == 0) + return r; + } + + return NULL; } -static enum lookup_state lookup_wait(struct lookup * lu) +static struct dht_req * __dht_kv_get_req_cache(const uint8_t * key) { - struct timespec timeo = {KAD_T_RESP, 0}; - struct timespec abs; - enum lookup_state state; - int ret = 0; + struct dht_req * req; + + assert(key != NULL); - clock_gettime(PTHREAD_COND_CLOCK, &abs); + req = __dht_kv_req_get_req(key); + if (req == NULL) + return NULL; - ts_add(&abs, &timeo, &abs); + if (req->cache.len == 0) + return NULL; - pthread_mutex_lock(&lu->lock); + return req; +} - if (lu->state == LU_INIT || lu->state == LU_UPDATE) - lu->state = LU_PENDING; +static void __dht_kv_req_remove(const uint8_t * key) +{ + struct dht_req * req; - pthread_cleanup_push(cancel_lookup_wait, lu); + assert(key != NULL); - while (lu->state == LU_PENDING && ret != -ETIMEDOUT) - ret = -pthread_cond_timedwait(&lu->cond, &lu->lock, &abs); + req = __dht_kv_req_get_req(key); + if (req == NULL) + return; - pthread_cleanup_pop(false); + list_del(&req->next); + --dht.reqs.len; - if (ret == -ETIMEDOUT) - lu->state = LU_COMPLETE; + dht_req_destroy(req); +} - state = lu->state; +static struct dht_req * __dht_kv_get_req_peer(const uint8_t * key, + uint64_t cookie) +{ + struct dht_req * req; - pthread_mutex_unlock(&lu->lock); + assert(key != NULL); - return state; + req = __dht_kv_req_get_req(key); + if (req == NULL) + return NULL; + + if (!dht_req_has_peer(req, cookie)) + return NULL; + + return req; } -static struct kad_req * dht_find_request(struct dht * dht, - kad_msg_t * msg) +static bool dht_kv_has_req(const uint8_t * key, + uint64_t cookie) { - struct list_head * p; + bool found; - assert(dht); - assert(msg); + pthread_mutex_lock(&dht.reqs.mtx); - list_for_each(p, &dht->requests) { - struct kad_req * r = list_entry(p, struct kad_req, next); - if (r->cookie == msg->cookie) - return r; - } + found = __dht_kv_get_req_peer(key, cookie) != NULL; - return NULL; + pthread_mutex_unlock(&dht.reqs.mtx); + + return found; } -static struct lookup * dht_find_lookup(struct dht * dht, - uint32_t cookie) +/* + * This will filter the peer list for addresses that still need to be + * contacted. + */ +static int dht_kv_update_req(const uint8_t * key, + struct list_head * pl) { - struct list_head * p; - struct list_head * p2; - struct list_head * h2; - - assert(dht); - assert(cookie > 0); - - list_for_each(p, &dht->lookups) { - struct lookup * l = list_entry(p, struct lookup, next); - pthread_mutex_lock(&l->lock); - list_for_each_safe(p2, h2, &l->cookies) { - struct cookie_el * e; - e = list_entry(p2, struct cookie_el, next); - if (e->cookie == cookie) { - list_del(&e->next); - free(e); - pthread_mutex_unlock(&l->lock); - return l; - } + struct dht_req * req; + struct timespec now; + + assert(key != NULL); + assert(pl != NULL); + assert(!list_is_empty(pl)); + + clock_gettime(PTHREAD_COND_CLOCK, &now); + + pthread_mutex_lock(&dht.reqs.mtx); + + req = __dht_kv_req_get_req(key); + if (req == NULL) { + if (dht.reqs.len == DHT_MAX_REQS) { + log_err(KEY_FMT " Max reqs reached (%zu).", + KEY_VAL(key), dht.reqs.len); + peer_list_destroy(pl); + goto fail_req; + } + req = dht_req_create(key); + if (req == NULL) { + log_err(KEY_FMT "Failed to create req.", KEY_VAL(key)); + goto fail_req; } - pthread_mutex_unlock(&l->lock); + list_add_tail(&req->next, &dht.reqs.list); + ++dht.reqs.len; } - return NULL; + if (req->cache.len > 0) /* Already have values */ + peer_list_destroy(pl); + + dht_req_add_peers(req, pl); + req->t_exp = now.tv_sec + DHT_T_RESP; + + if (dht.reqs.len > DHT_WARN_REQS) { + log_warn("Number of outstanding requests (%zu) exceeds %u.", + dht.reqs.len, DHT_WARN_REQS); + } + + pthread_mutex_unlock(&dht.reqs.mtx); + + return 0; + fail_req: + pthread_mutex_unlock(&dht.reqs.mtx); + return -1; } -static struct val * val_create(uint64_t addr, - time_t exp) +static int dht_kv_respond_req(uint8_t * key, + binary_data_t * vals, + size_t len) { - struct val * v; - struct timespec t; + struct dht_req * req; + struct timespec now; + size_t i; - v = malloc(sizeof(*v)); - if (v == NULL) - return NULL; + assert(key != NULL); + assert(vals != NULL); + assert(len > 0); - list_head_init(&v->next); - v->addr = addr; + clock_gettime(CLOCK_REALTIME_COARSE, &now); - clock_gettime(CLOCK_REALTIME_COARSE, &t); + pthread_mutex_lock(&dht.reqs.mtx); + + req = __dht_kv_req_get_req(key); + if (req == NULL) { + log_dbg(KEY_FMT " Failed to find req.", KEY_VAL(key)); + goto fail_req; + } + + for (i = 0; i < len; ++i) { + struct val_entry * e; + buffer_t val; + val.data = vals[i].data; + val.len = vals[i].len; + e = val_entry_create(val, now.tv_sec + DHT_T_CACHE); + if (e == NULL) { + log_err(" Failed to create val_entry."); + continue; + } - v->t_exp = t.tv_sec + exp; - v->t_rep = t.tv_sec + KAD_T_REPL; + list_add_tail(&e->next, &req->cache.list); + ++req->cache.len; + } - return v; -} + pthread_cond_broadcast(&dht.reqs.cond); -static void val_destroy(struct val * v) -{ - assert(v); + pthread_mutex_unlock(&dht.reqs.mtx); - free(v); + return 0; + fail_req: + pthread_mutex_unlock(&dht.reqs.mtx); + return -1; } -static struct ref_entry * ref_entry_create(struct dht * dht, - const uint8_t * key) +static ssize_t dht_kv_wait_req(const uint8_t * key, + buffer_t ** vals) { - struct ref_entry * e; + struct list_head * p; + struct dht_req * req; struct timespec t; +#ifdef __DHT_TEST__ + struct timespec intv = TIMESPEC_INIT_MS(10); +#else + struct timespec intv = TIMESPEC_INIT_S(DHT_T_RESP); +#endif + size_t max; + size_t i = 0; + int ret = 0; - assert(dht); - assert(key); + assert(key != NULL); + assert(vals != NULL); - e = malloc(sizeof(*e)); - if (e == NULL) - return NULL; + clock_gettime(PTHREAD_COND_CLOCK, &t); - e->key = dht_dup_key(key, dht->b); - if (e->key == NULL) { - free(e); - return NULL; + ts_add(&t, &intv, &t); + + pthread_mutex_lock(&dht.reqs.mtx); + + pthread_cleanup_push(__cleanup_mutex_unlock, &dht.reqs.mtx); + + while ((req = __dht_kv_get_req_cache(key)) == NULL) { + ret = pthread_cond_timedwait(&dht.reqs.cond, &dht.reqs.mtx, &t); + if (ret == ETIMEDOUT) + break; } - clock_gettime(CLOCK_REALTIME_COARSE, &t); + pthread_cleanup_pop(false); - e->t_rep = t.tv_sec + dht->t_repub; + if (ret == ETIMEDOUT) { + log_warn(KEY_FMT " Req timed out.", KEY_VAL(key)); + __dht_kv_req_remove(key); + goto timedout; + } - return e; -} + max = MIN(req->cache.len, DHT_MAX_VALS); + if (max == 0) + goto no_vals; -static void ref_entry_destroy(struct ref_entry * e) -{ - free(e->key); - free(e); + *vals = malloc(max * sizeof(**vals)); + if (*vals == NULL) { + log_err(KEY_FMT "Failed to malloc val buffer.", KEY_VAL(key)); + goto fail_vals; + } + + memset(*vals, 0, max * sizeof(**vals)); + + list_for_each(p, &req->cache.list) { + struct val_entry * v; + if (i == max) + break; /* We have enough values */ + v = list_entry(p, struct val_entry, next); + (*vals)[i].data = malloc(v->val.len); + if ((*vals)[i].data == NULL) + goto fail_val_data; + + (*vals)[i].len = v->val.len; + memcpy((*vals)[i++].data, v->val.data, v->val.len); + } + + pthread_mutex_unlock(&dht.reqs.mtx); + + return i; + no_vals: + pthread_mutex_unlock(&dht.reqs.mtx); + *vals = NULL; + return 0; + fail_val_data: + freebufs(*vals, i); + fail_vals: + pthread_mutex_unlock(&dht.reqs.mtx); + return -ENOMEM; + timedout: + pthread_mutex_unlock(&dht.reqs.mtx); + return -ETIMEDOUT; } -static struct dht_entry * dht_entry_create(struct dht * dht, - const uint8_t * key) +static struct bucket * iter_bucket(struct bucket * b, + const uint8_t * id) { - struct dht_entry * e; + uint8_t byte; + uint8_t mask; - assert(dht); - assert(key); + assert(b != NULL); - e = malloc(sizeof(*e)); - if (e == NULL) - return NULL; + if (b->children[0] == NULL) + return b; - list_head_init(&e->next); - list_head_init(&e->vals); + byte = id[(b->depth * DHT_BETA) / CHAR_BIT]; - e->n_vals = 0; + mask = ((1L << DHT_BETA) - 1) & 0xFF; - e->key = dht_dup_key(key, dht->b); - if (e->key == NULL) { - free(e); - return NULL; - } + byte >>= (CHAR_BIT - DHT_BETA) - + (((b->depth) * DHT_BETA) & (CHAR_BIT - 1)); - return e; + return iter_bucket(b->children[(byte & mask)], id); } -static void dht_entry_destroy(struct dht_entry * e) +static struct bucket * __dht_kv_get_bucket(const uint8_t * id) +{ + assert(dht.db.contacts.root != NULL); + + return iter_bucket(dht.db.contacts.root, id); +} + +static void contact_list_add(struct list_head * l, + struct contact * c) { struct list_head * p; - struct list_head * h; - assert(e); + assert(l != NULL); + assert(c != NULL); - list_for_each_safe(p, h, &e->vals) { - struct val * v = list_entry(p, struct val, next); - list_del(&v->next); - val_destroy(v); + list_for_each(p, l) { + struct contact * e = list_entry(p, struct contact, next); + if (IS_CLOSER(e->id, c->id)) + continue; } - free(e->key); - - free(e); + list_add_tail(&c->next, p); } -static int dht_entry_add_addr(struct dht_entry * e, - uint64_t addr, - time_t exp) +static ssize_t dht_kv_contact_list(const uint8_t * key, + struct list_head * l, + size_t max) { struct list_head * p; - struct val * val; - struct timespec t; + struct bucket * b; + struct timespec t; + size_t i; + size_t len = 0; + + assert(l != NULL); + assert(key != NULL); + assert(list_is_empty(l)); clock_gettime(CLOCK_REALTIME_COARSE, &t); - list_for_each(p, &e->vals) { - struct val * v = list_entry(p, struct val, next); - if (v->addr == addr) { - if (v->t_exp < t.tv_sec + exp) { - v->t_exp = t.tv_sec + exp; - v->t_rep = t.tv_sec + KAD_T_REPL; - } + max = MIN(max, dht.k); - return 0; - } + pthread_rwlock_rdlock(&dht.db.lock); + + b = __dht_kv_get_bucket(key); + if (b == NULL) { + log_err(KEY_FMT " Failed to get bucket.", KEY_VAL(key)); + goto fail_bucket; } - val = val_create(addr, exp); - if (val == NULL) - return -ENOMEM; + b->t_refr = t.tv_sec + dht.t_refresh; + + if (b->contacts.len == dht.k || b->parent == NULL) { + list_for_each(p, &b->contacts.list) { + struct contact * c; + struct contact * d; + c = list_entry(p, struct contact, next); + if (c->addr == dht.addr) + continue; + d = contact_create(c->id, c->addr); + if (d == NULL) + continue; + contact_list_add(l, d); + if (++len == max) + break; + } + } else { + struct bucket * d = b->parent; + for (i = 0; i < (1L << DHT_BETA) && len < dht.k; ++i) { + list_for_each(p, &d->children[i]->contacts.list) { + struct contact * c; + struct contact * d; + c = list_entry(p, struct contact, next); + if (c->addr == dht.addr) + continue; + d = contact_create(c->id, c->addr); + if (d == NULL) + continue; + contact_list_add(l, d); + if (++len == max) + break; + } + } + } - list_add(&val->next, &e->vals); - ++e->n_vals; + pthread_rwlock_unlock(&dht.db.lock); - return 0; + return len; + fail_bucket: + pthread_rwlock_unlock(&dht.db.lock); + return -1; } - -static void dht_entry_del_addr(struct dht_entry * e, - uint64_t addr) +static void contact_list_destroy(struct list_head * l) { struct list_head * p; struct list_head * h; - assert(e); + assert(l != NULL); - list_for_each_safe(p, h, &e->vals) { - struct val * v = list_entry(p, struct val, next); - if (v->addr == addr) { - list_del(&v->next); - val_destroy(v); - --e->n_vals; - } - } - - if (e->n_vals == 0) { - list_del(&e->next); - dht_entry_destroy(e); + list_for_each_safe(p, h, l) { + struct contact * c = list_entry(p, struct contact, next); + list_del(&c->next); + contact_destroy(c); } } -static uint64_t dht_entry_get_addr(struct dht * dht, - struct dht_entry * e) +static ssize_t dht_kv_get_contacts(const uint8_t * key, + dht_contact_msg_t *** msgs) { + struct list_head cl; struct list_head * p; + struct list_head * h; + size_t len; + size_t i = 0; - assert(e); - assert(!list_is_empty(&e->vals)); + assert(key != NULL); + assert(msgs != NULL); - list_for_each(p, &e->vals) { - struct val * v = list_entry(p, struct val, next); - if (v->addr != dht->addr) - return v->addr; + list_head_init(&cl); + + len = dht_kv_contact_list(key, &cl, dht.k); + if (len == 0) { + *msgs = NULL; + return 0; } - return 0; -} + *msgs = malloc(len * sizeof(**msgs)); + if (*msgs == NULL) + goto fail_msgs; -/* Forward declaration. */ -static struct lookup * kad_lookup(struct dht * dht, - const uint8_t * key, - enum kad_code code); + list_for_each_safe(p, h, &cl) { + struct contact * c; + (*msgs)[i] = malloc(sizeof(***msgs)); + if ((*msgs)[i] == NULL) + goto fail_contact; + + dht_contact_msg__init((*msgs)[i]); + c = list_entry(p, struct contact, next); + list_del(&c->next); + (*msgs)[i]->id.data = c->id; + (*msgs)[i]->id.len = dht.id.len; + (*msgs)[i++]->addr = c->addr; + free(c); + } + return i; + fail_contact: + while (i-- > 0) + dht_contact_msg__free_unpacked((*msgs)[i], NULL); + free(*msgs); + *msgs = NULL; + fail_msgs: + contact_list_destroy(&cl); + return -ENOMEM; +} /* Build a refresh list. */ -static void bucket_refresh(struct dht * dht, - struct bucket * b, - time_t t, - struct list_head * r) +static void __dht_kv_bucket_refresh_list(struct bucket * b, + time_t t, + struct list_head * r) { - size_t i; + struct contact * c; + struct contact * d; - if (*b->children != NULL) - for (i = 0; i < (1L << KAD_BETA); ++i) - bucket_refresh(dht, b->children[i], t, r); + assert(b != NULL); - if (b->n_contacts == 0) + if (t < b->t_refr) return; - if (t > b->t_refr) { - struct contact * c; - struct contact * d; - c = list_first_entry(&b->contacts, struct contact, next); - d = contact_create(c->id, dht->b, c->addr); - if (c != NULL) - list_add(&d->next, r); + if (*b->children != NULL) { + size_t i; + for (i = 0; i < (1L << DHT_BETA); ++i) + __dht_kv_bucket_refresh_list(b->children[i], t, r); + } + + if (b->contacts.len == 0) return; + + c = list_first_entry(&b->contacts.list, struct contact, next); + if (t > c->t_seen + dht.t_refresh) { + d = contact_create(c->id, c->addr); + if (d != NULL) + list_add(&d->next, r); } } - static struct bucket * bucket_create(void) { struct bucket * b; @@ -1286,20 +1682,21 @@ static struct bucket * bucket_create(void) if (b == NULL) return NULL; - list_head_init(&b->contacts); - b->n_contacts = 0; + list_head_init(&b->contacts.list); + b->contacts.len = 0; - list_head_init(&b->alts); - b->n_alts = 0; + list_head_init(&b->alts.list); + b->alts.len = 0; clock_gettime(CLOCK_REALTIME_COARSE, &t); - b->t_refr = t.tv_sec + KAD_T_REFR; + b->t_refr = t.tv_sec + dht.t_refresh; - for (i = 0; i < (1L << KAD_BETA); ++i) + for (i = 0; i < (1L << DHT_BETA); ++i) b->children[i] = NULL; b->parent = NULL; b->depth = 0; + b->mask = 0; return b; } @@ -1310,24 +1707,24 @@ static void bucket_destroy(struct bucket * b) struct list_head * h; size_t i; - assert(b); + assert(b != NULL); - for (i = 0; i < (1L << KAD_BETA); ++i) + for (i = 0; i < (1L << DHT_BETA); ++i) if (b->children[i] != NULL) bucket_destroy(b->children[i]); - list_for_each_safe(p, h, &b->contacts) { + list_for_each_safe(p, h, &b->contacts.list) { struct contact * c = list_entry(p, struct contact, next); list_del(&c->next); contact_destroy(c); - --b->n_contacts; + --b->contacts.len; } - list_for_each_safe(p, h, &b->alts) { + list_for_each_safe(p, h, &b->alts.list) { struct contact * c = list_entry(p, struct contact, next); list_del(&c->next); contact_destroy(c); - --b->n_contacts; + --b->alts.len; } free(b); @@ -1342,1534 +1739,2316 @@ static bool bucket_has_id(struct bucket * b, if (b->depth == 0) return true; - byte = id[(b->depth * KAD_BETA) / CHAR_BIT]; + byte = id[(b->depth * DHT_BETA) / CHAR_BIT]; - mask = ((1L << KAD_BETA) - 1) & 0xFF; + mask = ((1L << DHT_BETA) - 1) & 0xFF; - byte >>= (CHAR_BIT - KAD_BETA) - - (((b->depth - 1) * KAD_BETA) & (CHAR_BIT - 1)); + byte >>= (CHAR_BIT - DHT_BETA) - + (((b->depth - 1) * DHT_BETA) & (CHAR_BIT - 1)); return ((byte & mask) == b->mask); } -static int split_bucket(struct bucket * b) +static int move_contacts(struct bucket * b, + struct bucket * c) { struct list_head * p; struct list_head * h; + struct contact * d; + + assert(b != NULL); + assert(c != NULL); + + list_for_each_safe(p, h, &b->contacts.list) { + d = list_entry(p, struct contact, next); + if (bucket_has_id(c, d->id)) { + list_del(&d->next); + --b->contacts.len; + list_add_tail(&d->next, &c->contacts.list); + ++c->contacts.len; + } + } + + return 0; +} + +static int split_bucket(struct bucket * b) +{ uint8_t mask = 0; size_t i; - size_t c; + size_t b_len; assert(b); - assert(b->n_alts == 0); - assert(b->n_contacts); + assert(b->alts.len == 0); + assert(b->contacts.len != 0); assert(b->children[0] == NULL); - c = b->n_contacts; + b_len = b->contacts.len; - for (i = 0; i < (1L << KAD_BETA); ++i) { + for (i = 0; i < (1L << DHT_BETA); ++i) { b->children[i] = bucket_create(); - if (b->children[i] == NULL) { - size_t j; - for (j = 0; j < i; ++j) - bucket_destroy(b->children[j]); - return -1; - } + if (b->children[i] == NULL) + goto fail_child; b->children[i]->depth = b->depth + 1; b->children[i]->mask = mask; b->children[i]->parent = b; - list_for_each_safe(p, h, &b->contacts) { - struct contact * c; - c = list_entry(p, struct contact, next); - if (bucket_has_id(b->children[i], c->id)) { - list_del(&c->next); - --b->n_contacts; - list_add(&c->next, &b->children[i]->contacts); - ++b->children[i]->n_contacts; - } - } + move_contacts(b, b->children[i]); mask++; } - for (i = 0; i < (1L << KAD_BETA); ++i) - if (b->children[i]->n_contacts == c) + for (i = 0; i < (1L << DHT_BETA); ++i) + if (b->children[i]->contacts.len == b_len) split_bucket(b->children[i]); return 0; + fail_child: + while (i-- > 0) + bucket_destroy(b->children[i]); + return -1; } -/* Locked externally to mandate update as (final) part of join transaction. */ -static int dht_update_bucket(struct dht * dht, - const uint8_t * id, - uint64_t addr) +static int dht_kv_update_contacts(const uint8_t * id, + uint64_t addr) { struct list_head * p; struct list_head * h; struct bucket * b; struct contact * c; - assert(dht); + assert(id != NULL); + assert(addr != INVALID_ADDR); - b = dht_get_bucket(dht, id); - if (b == NULL) - return -1; + pthread_rwlock_wrlock(&dht.db.lock); - c = contact_create(id, dht->b, addr); - if (c == NULL) - return -1; + b = __dht_kv_get_bucket(id); + if (b == NULL) { + log_err(PEER_FMT " Failed to get bucket.", PEER_VAL(id, addr)); + goto fail_update; + } + + c = contact_create(id, addr); + if (c == NULL) { + log_err(PEER_FMT " Failed to create contact.", + PEER_VAL(id, addr)); + goto fail_update; + } - list_for_each_safe(p, h, &b->contacts) { + list_for_each_safe(p, h, &b->contacts.list) { struct contact * d = list_entry(p, struct contact, next); if (d->addr == addr) { list_del(&d->next); contact_destroy(d); - --b->n_contacts; + --b->contacts.len; } } - if (b->n_contacts == dht->k) { - if (bucket_has_id(b, dht->id)) { - list_add_tail(&c->next, &b->contacts); - ++b->n_contacts; + if (b->contacts.len == dht.k) { + if (bucket_has_id(b, dht.id.data)) { + list_add_tail(&c->next, &b->contacts.list); + ++b->contacts.len; if (split_bucket(b)) { list_del(&c->next); contact_destroy(c); - --b->n_contacts; + --b->contacts.len; } - } else if (b->n_alts == dht->k) { + } else if (b->alts.len == dht.k) { struct contact * d; - d = list_first_entry(&b->alts, struct contact, next); + d = list_first_entry(&b->alts.list, + struct contact, next); list_del(&d->next); contact_destroy(d); - list_add_tail(&c->next, &b->alts); + list_add_tail(&c->next, &b->alts.list); + ++b->alts.len; } else { - list_add_tail(&c->next, &b->alts); - ++b->n_alts; + list_add_tail(&c->next, &b->alts.list); + ++b->alts.len; } } else { - list_add_tail(&c->next, &b->contacts); - ++b->n_contacts; + list_add_tail(&c->next, &b->contacts.list); + ++b->contacts.len; } + pthread_rwlock_unlock(&dht.db.lock); + return 0; + fail_update: + pthread_rwlock_unlock(&dht.db.lock); + return -1; } -static int send_msg(struct dht * dht, - kad_msg_t * msg, - uint64_t addr) +static time_t gcd(time_t a, + time_t b) { -#ifndef __DHT_TEST__ - struct shm_du_buff * sdb; - size_t len; -#endif - int retr = 0; + if (a == 0) + return b; - if (msg->code == KAD_RESPONSE) - retr = KAD_RESP_RETR; + return gcd(b % a, a); +} - pthread_rwlock_wrlock(&dht->lock); +static dht_contact_msg_t * dht_kv_src_contact_msg(void) +{ + dht_contact_msg_t * src; - if (dht->id != NULL) { - msg->has_s_id = true; - msg->s_id.data = dht->id; - msg->s_id.len = dht->b; - } + src = malloc(sizeof(*src)); + if (src == NULL) + goto fail_malloc; - msg->s_addr = dht->addr; + dht_contact_msg__init(src); - if (msg->code < KAD_STORE) { - msg->cookie = bmp_allocate(dht->cookies); - if (!bmp_is_id_valid(dht->cookies, msg->cookie)) { - pthread_rwlock_unlock(&dht->lock); - goto fail_bmp_alloc; - } - } + src->id.data = dht_dup_key(dht.id.data); + if (src->id.data == NULL) + goto fail_id; - pthread_rwlock_unlock(&dht->lock); + src->id.len = dht.id.len; + src->addr = dht.addr; -#ifndef __DHT_TEST__ - len = kad_msg__get_packed_size(msg); - if (len == 0) - goto fail_msg; + return src; + fail_id: + dht_contact_msg__free_unpacked(src, NULL); + fail_malloc: + return NULL; +} - while (true) { - if (ipcp_sdb_reserve(&sdb, len)) - goto fail_msg; +static dht_msg_t * dht_kv_find_req_msg(const uint8_t * key, + enum dht_code code) +{ + dht_msg_t * msg; - kad_msg__pack(msg, shm_du_buff_head(sdb)); + assert(key != NULL); - if (dt_write_packet(addr, QOS_CUBE_BE, dht->eid, sdb) == 0) - break; + msg = malloc(sizeof(*msg)); + if (msg == NULL) + goto fail_malloc; - ipcp_sdb_release(sdb); + dht_msg__init(msg); + msg->code = code; - sleep(1); + msg->src = dht_kv_src_contact_msg(); + if (msg->src == NULL) + goto fail_msg; - if (--retr < 0) - goto fail_msg; - } + msg->find = malloc(sizeof(*msg->find)); + if (msg->find == NULL) + goto fail_msg; -#else - (void) addr; - (void) retr; -#endif /* __DHT_TEST__ */ + dht_find_req_msg__init(msg->find); - if (msg->code < KAD_STORE && dht_get_state(dht) != DHT_SHUTDOWN) - kad_req_create(dht, msg, addr); + msg->find->key.data = dht_dup_key(key); + if (msg->find->key.data == NULL) + goto fail_msg; + + msg->find->key.len = dht.id.len; + msg->find->cookie = DHT_INVALID; + + return msg; - return msg->cookie; -#ifndef __DHT_TEST__ fail_msg: - pthread_rwlock_wrlock(&dht->lock); - bmp_release(dht->cookies, msg->cookie); - pthread_rwlock_unlock(&dht->lock); -#endif /* !__DHT_TEST__ */ - fail_bmp_alloc: - return -1; + dht_msg__free_unpacked(msg, NULL); + fail_malloc: + return NULL; } -static struct dht_entry * dht_find_entry(struct dht * dht, - const uint8_t * key) +static dht_msg_t * dht_kv_find_node_req_msg(const uint8_t * key) { - struct list_head * p; + return dht_kv_find_req_msg(key, DHT_FIND_NODE_REQ); +} - list_for_each(p, &dht->entries) { - struct dht_entry * e = list_entry(p, struct dht_entry, next); - if (!memcmp(key, e->key, dht->b)) - return e; +static dht_msg_t * dht_kv_find_value_req_msg(const uint8_t * key) +{ + return dht_kv_find_req_msg(key, DHT_FIND_VALUE_REQ); +} + +static dht_msg_t * dht_kv_find_node_rsp_msg(uint8_t * key, + uint64_t cookie, + dht_contact_msg_t *** contacts, + size_t len) +{ + dht_msg_t * msg; + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + goto fail_malloc; + + dht_msg__init(msg); + msg->code = DHT_FIND_NODE_RSP; + + msg->src = dht_kv_src_contact_msg(); + if (msg->src == NULL) + goto fail_msg; + + msg->node = malloc(sizeof(*msg->node)); + if (msg->node == NULL) + goto fail_msg; + + dht_find_node_rsp_msg__init(msg->node); + + msg->node->key.data = dht_dup_key(key); + if (msg->node->key.data == NULL) + goto fail_msg; + + msg->node->cookie = cookie; + msg->node->key.len = dht.id.len; + msg->node->n_contacts = len; + if (len != 0) { /* Steal the ptr */ + msg->node->contacts = *contacts; + *contacts = NULL; } + return msg; + + fail_msg: + dht_msg__free_unpacked(msg, NULL); + fail_malloc: return NULL; } -static int kad_add(struct dht * dht, - const kad_contact_msg_t * contacts, - ssize_t n, - time_t exp) +static dht_msg_t * dht_kv_find_value_rsp_msg(uint8_t * key, + uint64_t cookie, + dht_contact_msg_t *** contacts, + size_t n_contacts, + buffer_t ** vals, + size_t n_vals) { - struct dht_entry * e; + dht_msg_t * msg; - pthread_rwlock_wrlock(&dht->lock); + msg = dht_kv_find_node_rsp_msg(key, cookie, contacts, n_contacts); + if (msg == NULL) + goto fail_node_rsp; - while (n-- > 0) { - if (contacts[n].id.len != dht->b) - log_warn("Bad key length in contact data."); + msg->code = DHT_FIND_VALUE_RSP; - e = dht_find_entry(dht, contacts[n].id.data); - if (e != NULL) { - if (dht_entry_add_addr(e, contacts[n].addr, exp)) - goto fail; - } else { - e = dht_entry_create(dht, contacts[n].id.data); - if (e == NULL) - goto fail; + msg->val = malloc(sizeof(*msg->val)); + if (msg->val == NULL) + goto fail_msg; - if (dht_entry_add_addr(e, contacts[n].addr, exp)) { - dht_entry_destroy(e); - goto fail; - } + dht_find_value_rsp_msg__init(msg->val); - list_add(&e->next, &dht->entries); - } - } + msg->val->n_values = n_vals; + if (n_vals != 0) /* Steal the ptr */ + msg->val->values = (binary_data_t *) *vals; - pthread_rwlock_unlock(&dht->lock); - return 0; + return msg; - fail: - pthread_rwlock_unlock(&dht->lock); - return -ENOMEM; + fail_msg: + dht_msg__free_unpacked(msg, NULL); + fail_node_rsp: + return NULL; } -static int wait_resp(struct dht * dht, - kad_msg_t * msg, - time_t timeo) +static dht_msg_t * dht_kv_store_msg(const uint8_t * key, + const buffer_t val, + time_t exp) { - struct kad_req * req; + dht_msg_t * msg; - assert(dht); - assert(msg); + assert(key != NULL); + assert(val.data != NULL); + assert(val.len > 0); - pthread_rwlock_rdlock(&dht->lock); + msg = malloc(sizeof(*msg)); + if (msg == NULL) + goto fail_malloc; - req = dht_find_request(dht, msg); - if (req == NULL) { - pthread_rwlock_unlock(&dht->lock); - return -EPERM; - } + dht_msg__init(msg); + + msg->code = DHT_STORE; + + msg->src = dht_kv_src_contact_msg(); + if (msg->src == NULL) + goto fail_msg; + + msg->store = malloc(sizeof(*msg->store)); + if (msg->store == NULL) + goto fail_msg; - pthread_rwlock_unlock(&dht->lock); + dht_store_msg__init(msg->store); - return kad_req_wait(req, timeo); + msg->store->key.data = dht_dup_key(key); + if (msg->store->key.data == NULL) + goto fail_msg; + + msg->store->key.len = dht.id.len; + msg->store->val.data = malloc(val.len); + if (msg->store->val.data == NULL) + goto fail_msg; + + memcpy(msg->store->val.data, val.data, val.len); + + msg->store->val.len = val.len; + msg->store->exp = exp; + + return msg; + + fail_msg: + dht_msg__free_unpacked(msg, NULL); + fail_malloc: + return NULL; } -static int kad_store(struct dht * dht, - const uint8_t * key, - uint64_t addr, - uint64_t r_addr, - time_t ttl) +static ssize_t dht_kv_retrieve(const uint8_t * key, + buffer_t ** vals) { - kad_msg_t msg = KAD_MSG__INIT; - kad_contact_msg_t cmsg = KAD_CONTACT_MSG__INIT; - kad_contact_msg_t * cmsgp[1]; + struct dht_entry * e; + struct list_head * p; + size_t n; + size_t i; - cmsg.id.data = (uint8_t *) key; - cmsg.addr = addr; + assert(key != NULL); - pthread_rwlock_rdlock(&dht->lock); + pthread_rwlock_rdlock(&dht.db.lock); - cmsg.id.len = dht->b; + e = __dht_kv_find_entry(key); + if (e == NULL) + goto no_vals; - pthread_rwlock_unlock(&dht->lock); + n = MIN(DHT_MAX_VALS, e->vals.len + e->lvals.len); + if (n == 0) + goto no_vals; - cmsgp[0] = &cmsg; + *vals = malloc(n * sizeof(**vals)); + if (*vals == NULL) + goto fail_vals; - msg.code = KAD_STORE; - msg.has_t_expire = true; - msg.t_expire = ttl; - msg.n_contacts = 1; - msg.contacts = cmsgp; + memset(*vals, 0, n * sizeof(**vals)); - if (send_msg(dht, &msg, r_addr) < 0) - return -1; + i = 0; + + list_for_each(p, &e->vals.list) { + struct val_entry * v; + if (i == n) + break; /* We have enough values */ + v = list_entry(p, struct val_entry, next); + (*vals)[i].data = malloc(v->val.len); + if ((*vals)[i].data == NULL) + goto fail_val_data; + + (*vals)[i].len = v->val.len; + memcpy((*vals)[i++].data, v->val.data, v->val.len); + } + + list_for_each(p, &e->lvals.list) { + struct val_entry * v; + if (i == n) + break; /* We have enough values */ + v = list_entry(p, struct val_entry, next); + (*vals)[i].data = malloc(v->val.len); + if ((*vals)[i].data == NULL) + goto fail_val_data; + + (*vals)[i].len = v->val.len; + memcpy((*vals)[i++].data, v->val.data, v->val.len); + } + pthread_rwlock_unlock(&dht.db.lock); + + return (ssize_t) i; + + fail_val_data: + pthread_rwlock_unlock(&dht.db.lock); + freebufs(*vals, i); + *vals = NULL; + return -ENOMEM; + fail_vals: + pthread_rwlock_unlock(&dht.db.lock); + return -ENOMEM; + no_vals: + pthread_rwlock_unlock(&dht.db.lock); + *vals = NULL; return 0; } -static ssize_t kad_find(struct dht * dht, - struct lookup * lu, - const uint64_t * addrs, - enum kad_code code) +static void __cleanup_dht_msg(void * msg) +{ + dht_msg__free_unpacked((dht_msg_t *) msg, NULL); +} + +#ifdef DEBUG_PROTO_DHT +static void dht_kv_debug_msg(dht_msg_t * msg) +{ + struct tm * tm; + char tmstr[RIB_TM_STRLEN]; + time_t stamp; + size_t i; + + if (msg == NULL) + return; + + pthread_cleanup_push(__cleanup_dht_msg, msg); + + switch (msg->code) { + case DHT_STORE: + log_proto(" key: " HASH_FMT64 " [%zu bytes]", + HASH_VAL64(msg->store->key.data), + msg->store->key.len); + log_proto(" val: " HASH_FMT64 " [%zu bytes]", + HASH_VAL64(msg->store->val.data), + msg->store->val.len); + stamp = msg->store->exp; + tm = gmtime(&stamp); + strftime(tmstr, sizeof(tmstr), RIB_TM_FORMAT, tm); + log_proto(" exp: %s.", tmstr); + break; + case DHT_FIND_NODE_REQ: + /* FALLTHRU */ + case DHT_FIND_VALUE_REQ: + log_proto(" cookie: " HASH_FMT64, + HASH_VAL64(&msg->find->cookie)); + log_proto(" key: " HASH_FMT64 " [%zu bytes]", + HASH_VAL64(msg->find->key.data), + msg->find->key.len); + break; + case DHT_FIND_VALUE_RSP: + log_proto(" cookie: " HASH_FMT64, + HASH_VAL64(&msg->node->cookie)); + log_proto(" key: " HASH_FMT64 " [%zu bytes]", + HASH_VAL64(msg->node->key.data), + msg->node->key.len); + log_proto(" values: [%zd]", msg->val->n_values); + for (i = 0; i < msg->val->n_values; i++) + log_proto(" " HASH_FMT64 " [%zu bytes]", + HASH_VAL64(msg->val->values[i].data), + msg->val->values[i].len); + log_proto(" contacts: [%zd]", msg->node->n_contacts); + for (i = 0; i < msg->node->n_contacts; i++) { + dht_contact_msg_t * c = msg->node->contacts[i]; + log_proto(" " PEER_FMT, + PEER_VAL(c->id.data, c->addr)); + } + break; + case DHT_FIND_NODE_RSP: + log_proto(" cookie: " HASH_FMT64, + HASH_VAL64(&msg->node->cookie)); + log_proto(" key: " HASH_FMT64 " [%zu bytes]", + HASH_VAL64(msg->node->key.data), msg->node->key.len); + log_proto(" contacts: [%zd]", msg->node->n_contacts); + for (i = 0; i < msg->node->n_contacts; i++) { + dht_contact_msg_t * c = msg->node->contacts[i]; + log_proto(" " PEER_FMT, + PEER_VAL(c->id.data, c->addr)); + } + + break; + default: + break; + } + + pthread_cleanup_pop(false); +} + +static void dht_kv_debug_msg_snd(dht_msg_t * msg, + uint8_t * id, + uint64_t addr) { - kad_msg_t msg = KAD_MSG__INIT; - ssize_t sent = 0; + if (msg == NULL) + return; - assert(dht); - assert(lu->key); + log_proto(TX_HDR_FMT ".", TX_HDR_VAL(msg, id, addr)); - msg.code = code; + dht_kv_debug_msg(msg); +} - msg.has_key = true; - msg.key.data = (uint8_t *) lu->key; - msg.key.len = dht->b; +static void dht_kv_debug_msg_rcv(dht_msg_t * msg) +{ + if (msg == NULL) + return; - while (*addrs != 0) { - struct cookie_el * c; - int ret; + log_proto(RX_HDR_FMT ".", RX_HDR_VAL(msg)); - if (*addrs == dht->addr) { - ++addrs; - continue; - } + dht_kv_debug_msg(msg); +} +#endif - ret = send_msg(dht, &msg, *addrs); - if (ret < 0) - break; +#ifndef __DHT_TEST__ +static int dht_send_msg(dht_msg_t * msg, + uint64_t addr) +{ + size_t len; + struct ssm_pk_buff * spb; - c = malloc(sizeof(*c)); - if (c == NULL) - break; + if (msg == NULL) + return 0; - c->cookie = (uint32_t) ret; + assert(addr != INVALID_ADDR && addr != dht.addr); - pthread_mutex_lock(&lu->lock); + len = dht_msg__get_packed_size(msg); + if (len == 0) { + log_warn("%s failed to pack.", DHT_CODE(msg)); + goto fail_msg; + } - list_add_tail(&c->next, &lu->cookies); + if (ipcp_spb_reserve(&spb, len)) { + log_warn("%s failed to get spb.", DHT_CODE(msg)); + goto fail_msg; + } - pthread_mutex_unlock(&lu->lock); + dht_msg__pack(msg, ssm_pk_buff_head(spb)); - ++sent; - ++addrs; + if (dt_write_packet(addr, QOS_CUBE_BE, dht.eid, spb) < 0) { + log_warn("%s write failed", DHT_CODE(msg)); + goto fail_send; } - return sent; + return 0; + fail_send: + ipcp_spb_release(spb); + fail_msg: + return -1; } +#else /* funtion for testing */ +static int dht_send_msg(dht_msg_t * msg, + uint64_t addr) +{ + buffer_t buf; + + assert(msg != NULL); + assert(addr != INVALID_ADDR && addr != dht.addr); + + buf.len = dht_msg__get_packed_size(msg); + if (buf.len == 0) { + log_warn("%s failed to pack.", DHT_CODE(msg)); + goto fail_msg; + } + + buf.data = malloc(buf.len); + if (buf.data == NULL) { + log_warn("%s failed to malloc buf.", DHT_CODE(msg)); + goto fail_msg; + } + + dht_msg__pack(msg, buf.data); -static void lookup_detach(struct dht * dht, - struct lookup * lu) + if (sink_send_msg(&buf, addr) < 0) { + log_warn("%s write failed", DHT_CODE(msg)); + goto fail_send; + } + + return 0; + fail_send: + freebuf(buf); + fail_msg: + return -1; +} +#endif /* __DHT_TEST__ */ + +static void __cleanup_peer_list(void * pl) { - pthread_rwlock_wrlock(&dht->lock); + struct list_head * p; + struct list_head * h; - list_del(&lu->next); + assert(pl != NULL); - pthread_rwlock_unlock(&dht->lock); + list_for_each_safe(p, h, (struct list_head *) pl) { + struct peer_entry * e = list_entry(p, struct peer_entry, next); + list_del(&e->next); + free(e->id); + free(e); + } } -static struct lookup * kad_lookup(struct dht * dht, - const uint8_t * id, - enum kad_code code) + +static int dht_kv_send_msgs(dht_msg_t * msg, + struct list_head * pl) { - uint64_t addrs[KAD_ALPHA + 1]; - enum lookup_state state; - struct lookup * lu; + struct list_head * p; + struct list_head * h; - lu = lookup_create(dht, id); - if (lu == NULL) - return NULL; + pthread_cleanup_push(__cleanup_dht_msg, msg); + pthread_cleanup_push(__cleanup_peer_list, pl); - lookup_new_addrs(lu, addrs); + list_for_each_safe(p, h, pl) { + struct peer_entry * e = list_entry(p, struct peer_entry, next); + if (IS_REQUEST(msg->code)) { + msg->find->cookie = e->cookie; + assert(msg->find->cookie != DHT_INVALID); + } + if (dht_send_msg(msg, e->addr) < 0) + continue; - if (addrs[0] == 0) { - lookup_detach(dht, lu); - lookup_destroy(lu); - return NULL; +#ifdef DEBUG_PROTO_DHT + dht_kv_debug_msg_snd(msg, e->id, e->addr); +#endif + list_del(&e->next); + free(e->id); + free(e); } - if (kad_find(dht, lu, addrs, code) == 0) { - lookup_detach(dht, lu); - return lu; + pthread_cleanup_pop(false); + pthread_cleanup_pop(false); + + return list_is_empty(pl) ? 0 : -1; +} + +static int dht_kv_get_peer_list_for_msg(dht_msg_t * msg, + struct list_head * pl) +{ + struct list_head cl; /* contact list */ + uint8_t * key; /* key in the request */ + size_t max; + + assert(msg != NULL); + + assert(list_is_empty(pl)); + + max = msg->code == DHT_STORE ? dht.k : dht.alpha; + + switch (msg->code) { + case DHT_FIND_NODE_REQ: + /* FALLTHRU */ + case DHT_FIND_VALUE_REQ: + key = msg->find->key.data; + break; + case DHT_STORE: + key = msg->store->key.data; + break; + default: + log_err("Invalid DHT msg code (%d).", msg->code); + return -1; } - while ((state = lookup_wait(lu)) != LU_COMPLETE) { - switch (state) { - case LU_UPDATE: - lookup_new_addrs(lu, addrs); - if (addrs[0] == 0) - break; + list_head_init(&cl); - kad_find(dht, lu, addrs, code); - break; - case LU_DESTROY: - lookup_detach(dht, lu); - lookup_set_state(lu, LU_NULL); - return NULL; - default: - break; - } + if (dht_kv_contact_list(key, &cl, max) < 0) { + log_err(KEY_FMT " Failed to get contact list.", KEY_VAL(key)); + goto fail_contacts; } - assert(state == LU_COMPLETE); + if (list_is_empty(&cl)) { + log_warn(KEY_FMT " No available contacts.", KEY_VAL(key)); + goto fail_contacts; + } - lookup_detach(dht, lu); + if (dht_kv_create_peer_list(&cl, pl, msg->code) < 0) { + log_warn(KEY_FMT " Failed to get peer list.", KEY_VAL(key)); + goto fail_peers; + } - return lu; + contact_list_destroy(&cl); + return 0; + fail_peers: + contact_list_destroy(&cl); + fail_contacts: + return -1; } -static void kad_publish(struct dht * dht, - const uint8_t * key, - uint64_t addr, - time_t exp) +static int dht_kv_store_remote(const uint8_t * key, + const buffer_t val, + time_t exp) { - struct lookup * lu; - uint64_t * addrs; - ssize_t n; - size_t k; - time_t t_expire; + dht_msg_t * msg; + struct timespec now; + struct list_head pl; + assert(key != NULL); + assert(val.data != NULL); + assert(val.len > 0); - assert(dht); - assert(key); + clock_gettime(CLOCK_REALTIME_COARSE, &now); - pthread_rwlock_rdlock(&dht->lock); + msg = dht_kv_store_msg(key, val, exp); + if (msg == NULL) { + log_err(KV_FMT " Failed to create %s.", + KV_VAL(key, val), dht_code_str[DHT_STORE]); + goto fail_msg; + } - k = dht->k; - t_expire = dht->t_expire; + list_head_init(&pl); - pthread_rwlock_unlock(&dht->lock); + if (dht_kv_get_peer_list_for_msg(msg, &pl) < 0) { + log_dbg(KV_FMT " Failed to get peer list.", KV_VAL(key, val)); + goto fail_peer_list; + } - addrs = malloc(k * sizeof(*addrs)); - if (addrs == NULL) - return; + if (dht_kv_send_msgs(msg, &pl) < 0) { + log_warn(KV_FMT " Failed to send any %s msg.", + KV_VAL(key, val), DHT_CODE(msg)); + goto fail_msgs; + } - lu = kad_lookup(dht, key, KAD_FIND_NODE); - if (lu == NULL) { - free(addrs); - return; + dht_msg__free_unpacked(msg, NULL); + + return 0; + fail_msgs: + peer_list_destroy(&pl); + fail_peer_list: + dht_msg__free_unpacked(msg, NULL); + fail_msg: + return -1; +} + +/* recursive lookup, start with pl NULL */ +static int dht_kv_query_contacts(const uint8_t * key, + struct list_head * pl) +{ + struct list_head p; + + dht_msg_t * msg; + + assert(key != NULL); + + msg = dht_kv_find_node_req_msg(key); + if (msg == NULL) { + log_err(KEY_FMT " Failed to create %s msg.", + KEY_VAL(key), dht_code_str[DHT_FIND_NODE_REQ]); + goto fail_msg; } - n = lookup_contact_addrs(lu, addrs); + if (pl == NULL) { + list_head_init(&p); + pl = &p; + } - while (n-- > 0) { - if (addrs[n] == dht->addr) { - kad_contact_msg_t msg = KAD_CONTACT_MSG__INIT; - msg.id.data = (uint8_t *) key; - msg.id.len = dht->b; - msg.addr = addr; - kad_add(dht, &msg, 1, exp); - } else { - if (kad_store(dht, key, addr, addrs[n], t_expire)) - log_warn("Failed to send store message."); - } + if (list_is_empty(pl) && dht_kv_get_peer_list_for_msg(msg, pl) < 0) { + log_warn(KEY_FMT " Failed to get peer list.", KEY_VAL(key)); + goto fail_peer_list; + } + + if (dht_kv_update_req(key, pl) < 0) { + log_warn(KEY_FMT " Failed to update req.", KEY_VAL(key)); + goto fail_update; + } + + if (dht_kv_send_msgs(msg, pl)) { + log_warn(KEY_FMT " Failed to send any %s msg.", + KEY_VAL(key), DHT_CODE(msg)); + goto fail_update; } - lookup_destroy(lu); + dht_msg__free_unpacked(msg, NULL); - free(addrs); + return 0; + fail_update: + peer_list_destroy(pl); + fail_peer_list: + dht_msg__free_unpacked(msg, NULL); + fail_msg: + return -1; } -static int kad_join(struct dht * dht, - uint64_t addr) +/* recursive lookup, start with pl NULL */ +static ssize_t dht_kv_query_remote(const uint8_t * key, + buffer_t ** vals, + struct list_head * pl) { - kad_msg_t msg = KAD_MSG__INIT; + struct list_head p; + dht_msg_t * msg; - msg.code = KAD_JOIN; + assert(key != NULL); - msg.has_alpha = true; - msg.has_b = true; - msg.has_k = true; - msg.has_t_refresh = true; - msg.has_t_replicate = true; - msg.alpha = KAD_ALPHA; - msg.k = KAD_K; - msg.t_refresh = KAD_T_REFR; - msg.t_replicate = KAD_T_REPL; + msg = dht_kv_find_value_req_msg(key); + if (msg == NULL) { + log_err(KEY_FMT " Failed to create value req.", KEY_VAL(key)); + goto fail_msg; + } - pthread_rwlock_rdlock(&dht->lock); + if (pl == NULL) { + list_head_init(&p); + pl = &p; + } - msg.b = dht->b; + if (list_is_empty(pl) && dht_kv_get_peer_list_for_msg(msg, pl) < 0) { + log_warn(KEY_FMT " Failed to get peer list.", KEY_VAL(key)); + goto fail_peer_list; + } - pthread_rwlock_unlock(&dht->lock); + if (dht_kv_update_req(key, pl) < 0) { + log_err(KEY_FMT " Failed to update request.", KEY_VAL(key)); + goto fail_update; + } - if (send_msg(dht, &msg, addr) < 0) - return -1; + if (dht_kv_send_msgs(msg, pl)) { + log_warn(KEY_FMT " Failed to send %s msg.", + KEY_VAL(key), DHT_CODE(msg)); + goto fail_update; + } - if (wait_resp(dht, &msg, KAD_T_JOIN) < 0) - return -1; + dht_msg__free_unpacked(msg, NULL); - dht->id = create_id(dht->b); - if (dht->id == NULL) - return -1; + if (vals == NULL) /* recursive lookup, already waiting */ + return 0; - pthread_rwlock_wrlock(&dht->lock); + return dht_kv_wait_req(key, vals); + fail_update: + peer_list_destroy(pl); + fail_peer_list: + dht_msg__free_unpacked(msg, NULL); + fail_msg: + return -1; +} - dht_update_bucket(dht, dht->id, dht->addr); +static void __add_dht_kv_entry(struct dht_entry * e) +{ + struct list_head * p; - pthread_rwlock_unlock(&dht->lock); + assert(e != NULL); - return 0; + list_for_each(p, &dht.db.kv.list) { + struct dht_entry * d = list_entry(p, struct dht_entry, next); + if (IS_CLOSER(d->key, e->key)) + continue; + break; + } + + list_add_tail(&e->next, p); + ++dht.db.kv.len; } -static void dht_dead_peer(struct dht * dht, - uint8_t * key, - uint64_t addr) +/* incoming store message */ +static int dht_kv_store(const uint8_t * key, + const buffer_t val, + time_t exp) { - struct list_head * p; - struct list_head * h; - struct bucket * b; + struct dht_entry * e; + bool new = false; - b = dht_get_bucket(dht, key); + assert(key != NULL); + assert(val.data != NULL); + assert(val.len > 0); - list_for_each_safe(p, h, &b->contacts) { - struct contact * c = list_entry(p, struct contact, next); - if (b->n_contacts + b->n_alts <= dht->k) { - ++c->fails; - return; - } + pthread_rwlock_wrlock(&dht.db.lock); - if (c->addr == addr) { - list_del(&c->next); - contact_destroy(c); - --b->n_contacts; - break; - } + e = __dht_kv_find_entry(key); + if (e == NULL) { + log_dbg(KV_FMT " Adding entry (store).", KV_VAL(key, val)); + e = dht_entry_create(key); + if (e == NULL) + goto fail; + + new = true; + + __add_dht_kv_entry(e); } - while (b->n_contacts < dht->k && b->n_alts > 0) { - struct contact * c; - c = list_first_entry(&b->alts, struct contact, next); - list_del(&c->next); - --b->n_alts; - list_add(&c->next, &b->contacts); - ++b->n_contacts; + if (dht_entry_update_val(e, val, exp) < 0) + goto fail_add; + + pthread_rwlock_unlock(&dht.db.lock); + + return 0; + fail_add: + if (new) { + list_del(&e->next); + dht_entry_destroy(e); + --dht.db.kv.len; } + fail: + pthread_rwlock_unlock(&dht.db.lock); + return -1; } -static int dht_del(struct dht * dht, - const uint8_t * key, - uint64_t addr) +static int dht_kv_publish(const uint8_t * key, + const buffer_t val) { struct dht_entry * e; + struct timespec now; + bool new = false; - pthread_rwlock_wrlock(&dht->lock); + assert(key != NULL); + assert(val.data != NULL); + assert(val.len > 0); - e = dht_find_entry(dht, key); + clock_gettime(CLOCK_REALTIME_COARSE, &now); + + pthread_rwlock_wrlock(&dht.db.lock); + + e = __dht_kv_find_entry(key); if (e == NULL) { - pthread_rwlock_unlock(&dht->lock); - return -EPERM; + log_dbg(KV_FMT " Adding entry (publish).", KV_VAL(key, val)); + e = dht_entry_create(key); + if (e == NULL) + goto fail; + + __add_dht_kv_entry(e); + new = true; } - dht_entry_del_addr(e, addr); + if (dht_entry_update_lval(e, val) < 0) + goto fail_add; - pthread_rwlock_unlock(&dht->lock); + pthread_rwlock_unlock(&dht.db.lock); + + dht_kv_store_remote(key, val, now.tv_sec + dht.t_expire); return 0; + fail_add: + if (new) { + list_del(&e->next); + dht_entry_destroy(e); + --dht.db.kv.len; + } + fail: + pthread_rwlock_unlock(&dht.db.lock); + return -1; } -static buffer_t dht_retrieve(struct dht * dht, - const uint8_t * key) +static int dht_kv_unpublish(const uint8_t * key, + const buffer_t val) { struct dht_entry * e; - struct list_head * p; - buffer_t buf; - uint64_t * pos; - size_t addrs = 0; + int rc; + + assert(key != NULL); - pthread_rwlock_rdlock(&dht->lock); + pthread_rwlock_wrlock(&dht.db.lock); - e = dht_find_entry(dht, key); + e = __dht_kv_find_entry(key); if (e == NULL) - goto fail; + goto no_entry; - buf.len = MIN(DHT_RETR_ADDR, e->n_vals); - if (buf.len == 0) - goto fail; + rc = dht_entry_remove_lval(e, val); - pos = malloc(sizeof(dht->addr) * buf.len); - if (pos == NULL) - goto fail; + pthread_rwlock_unlock(&dht.db.lock); - buf.data = (uint8_t *) pos; + return rc; + no_entry: + pthread_rwlock_unlock(&dht.db.lock); + return -ENOENT; - list_for_each(p, &e->vals) { - struct val * v = list_entry(p, struct val, next); - *pos++ = v->addr; - if (++addrs >= buf.len) - break; +} + +/* message validation */ +static int dht_kv_validate_store_msg(const dht_store_msg_t * store) +{ + if (store == NULL) { + log_warn("Store in msg is NULL."); + return -EINVAL; } - pthread_rwlock_unlock(&dht->lock); + if (store->key.data == NULL || store->key.len == 0) { + log_warn("Invalid key in DHT store msg."); + return -EINVAL; + } - return buf; + if (store->key.len != dht.id.len) { + log_warn("Invalid key length in DHT store msg."); + return -EINVAL; + } - fail: - pthread_rwlock_unlock(&dht->lock); - buf.len = 0; + if (store->val.data == NULL || store->val.len == 0) { + log_warn("Invalid value in DHT store msg."); + return -EINVAL; + } - return buf; + return 0; } -static ssize_t dht_get_contacts(struct dht * dht, - const uint8_t * key, - kad_contact_msg_t *** msgs) +static int validate_find_req_msg(const dht_find_req_msg_t * req) { - struct list_head l; - struct list_head * p; - struct list_head * h; - size_t len; - size_t i = 0; + if (req == NULL) { + log_warn("Request in msg is NULL."); + return -EINVAL; + } - list_head_init(&l); + if (req->key.data == NULL || req->key.len == 0) { + log_warn("Find request without key."); + return -EINVAL; + } - pthread_rwlock_wrlock(&dht->lock); + if (req->key.len != dht.id.len) { + log_warn("Invalid key length in request msg."); + return -EINVAL; + } - len = dht_contact_list(dht, &l, key); - if (len == 0) { - pthread_rwlock_unlock(&dht->lock); - *msgs = NULL; + return 0; +} + +static int validate_node_rsp_msg(const dht_find_node_rsp_msg_t * rsp) +{ + if (rsp == NULL) { + log_warn("Node rsp in msg is NULL."); + return -EINVAL; + } + + if (rsp->key.data == NULL) { + log_warn("Invalid key in DHT response msg."); + return -EINVAL; + } + + if (rsp->key.len != dht.id.len) { + log_warn("Invalid key length in DHT response msg."); + return -EINVAL; + } + + if (!dht_kv_has_req(rsp->key.data, rsp->cookie)) { + log_warn(KEY_FMT " No request " CK_FMT ".", + KEY_VAL(rsp->key.data), CK_VAL(rsp->cookie)); + + return -EINVAL; + } + + return 0; +} + +static int validate_value_rsp_msg(const dht_find_value_rsp_msg_t * rsp) +{ + if (rsp == NULL) { + log_warn("Invalid DHT find value response msg."); + return -EINVAL; + } + + if (rsp->values == NULL && rsp->n_values > 0) { + log_dbg("No values in DHT response msg."); return 0; } - *msgs = malloc(len * sizeof(**msgs)); - if (*msgs == NULL) { - pthread_rwlock_unlock(&dht->lock); + if (rsp->n_values == 0 && rsp->values != NULL) { + log_dbg("DHT response did not set values NULL."); return 0; } - list_for_each_safe(p, h, &l) { - struct contact * c = list_entry(p, struct contact, next); - (*msgs)[i] = malloc(sizeof(***msgs)); - if ((*msgs)[i] == NULL) { - pthread_rwlock_unlock(&dht->lock); - while (i > 0) - free(*msgs[--i]); - free(*msgs); - *msgs = NULL; - return 0; - } + return 0; +} - kad_contact_msg__init((*msgs)[i]); +static int dht_kv_validate_msg(dht_msg_t * msg) +{ - (*msgs)[i]->id.data = c->id; - (*msgs)[i]->id.len = dht->b; - (*msgs)[i++]->addr = c->addr; - list_del(&c->next); - free(c); + assert(msg != NULL); + + if (msg->src->id.len != dht.id.len) { + log_warn("%s Invalid source contact ID.", DHT_CODE(msg)); + return -EINVAL; } - pthread_rwlock_unlock(&dht->lock); + if (msg->src->addr == INVALID_ADDR) { + log_warn("%s Invalid source address.", DHT_CODE(msg)); + return -EINVAL; + } - return i; + switch (msg->code) { + case DHT_FIND_VALUE_REQ: + /* FALLTHRU */ + case DHT_FIND_NODE_REQ: + if (validate_find_req_msg(msg->find) < 0) + return -EINVAL; + break; + case DHT_FIND_VALUE_RSP: + if (validate_value_rsp_msg(msg->val) < 0) + return -EINVAL; + /* FALLTHRU */ + case DHT_FIND_NODE_RSP: + if (validate_node_rsp_msg(msg->node) < 0) + return -EINVAL; + break; + case DHT_STORE: + if (dht_kv_validate_store_msg(msg->store) < 0) + return -EINVAL; + break; + default: + log_warn("Invalid DHT msg code (%d).", msg->code); + return -ENOENT; + } + + return 0; } -static time_t gcd(time_t a, - time_t b) +static void do_dht_kv_store(const dht_store_msg_t * store) { - if (a == 0) - return b; + struct tm * tm; + char tmstr[RIB_TM_STRLEN]; + buffer_t val; + uint8_t * key; + time_t exp; - return gcd(b % a, a); + (void) key; /* Only in logs, not used with DISABLE_TEST_LOGGING */ + + assert(store != NULL); + + val.data = store->val.data; + val.len = store->val.len; + key = store->key.data; + exp = store->exp; + + if (dht_kv_store(store->key.data, val, store->exp) < 0) { + log_err(KV_FMT " Failed to store.", KV_VAL(key, val)); + return; + } + + tm = gmtime(&exp); + strftime(tmstr, sizeof(tmstr), RIB_TM_FORMAT, tm); + log_dbg(KV_FMT " Stored value until %s.", KV_VAL(key, val), tmstr); } -static void * work(void * o) +static dht_msg_t * do_dht_kv_find_node_req(const dht_find_req_msg_t * req) { - struct dht * dht; - struct timespec now; - struct list_head * p; - struct list_head * h; - struct list_head reflist; - time_t intv; - struct lookup * lu; + dht_contact_msg_t ** contacts; + dht_msg_t * rsp; + uint8_t * key; + uint64_t cookie; + ssize_t len; - dht = (struct dht *) o; + assert(req != NULL); - pthread_rwlock_rdlock(&dht->lock); + key = req->key.data; + cookie = req->cookie; - intv = gcd(dht->t_expire, dht->t_repub); - intv = gcd(intv, gcd(KAD_T_REPL, KAD_T_REFR)) / 2; + len = dht_kv_get_contacts(key, &contacts); + if (len < 0) { + log_warn(KEY_FMT " Failed to get contacts.", KEY_VAL(key)); + goto fail_contacts; + } - pthread_rwlock_unlock(&dht->lock); + rsp = dht_kv_find_node_rsp_msg(key, cookie, &contacts, len); + if (rsp == NULL) { + log_err(KEY_FMT " Failed to create %s.", KEY_VAL(key), + dht_code_str[DHT_FIND_NODE_RSP]); + goto fail_msg; + } - list_head_init(&reflist); + assert(rsp->code == DHT_FIND_NODE_RSP); - while (true) { - clock_gettime(CLOCK_REALTIME_COARSE, &now); - - pthread_rwlock_wrlock(&dht->lock); - - /* Republish registered hashes. */ - list_for_each(p, &dht->refs) { - struct ref_entry * e; - uint8_t * key; - uint64_t addr; - time_t t_expire; - e = list_entry(p, struct ref_entry, next); - if (now.tv_sec > e->t_rep) { - key = dht_dup_key(e->key, dht->b); - if (key == NULL) - continue; - addr = dht->addr; - t_expire = dht->t_expire; - e->t_rep = now.tv_sec + dht->t_repub; - - pthread_rwlock_unlock(&dht->lock); - kad_publish(dht, key, addr, t_expire); - pthread_rwlock_wrlock(&dht->lock); - free(key); - } - } + log_info(KEY_FMT " Responding with %zd contacts", KEY_VAL(key), len); - /* Remove stale entries and republish if necessary. */ - list_for_each_safe(p, h, &dht->entries) { - struct list_head * p1; - struct list_head * h1; - struct dht_entry * e; - uint8_t * key; - time_t t_expire; - e = list_entry (p, struct dht_entry, next); - list_for_each_safe(p1, h1, &e->vals) { - struct val * v; - uint64_t addr; - v = list_entry(p1, struct val, next); - if (now.tv_sec > v->t_exp) { - list_del(&v->next); - val_destroy(v); - continue; - } - - if (now.tv_sec > v->t_rep) { - key = dht_dup_key(e->key, dht->b); - addr = v->addr; - t_expire = dht->t_expire = now.tv_sec; - v->t_rep = now.tv_sec + dht->t_replic; - pthread_rwlock_unlock(&dht->lock); - kad_publish(dht, key, addr, t_expire); - pthread_rwlock_wrlock(&dht->lock); - free(key); - } - } - } + return rsp; + fail_msg: + while (len-- > 0) + dht_contact_msg__free_unpacked(contacts[len], NULL); + free(contacts); + fail_contacts: + return NULL; +} - /* Check the requests list for unresponsive nodes. */ - list_for_each_safe(p, h, &dht->requests) { - struct kad_req * r; - r = list_entry(p, struct kad_req, next); - if (now.tv_sec > r->t_exp) { - list_del(&r->next); - bmp_release(dht->cookies, r->cookie); - dht_dead_peer(dht, r->key, r->addr); - kad_req_destroy(r); - } - } +static void dht_kv_process_node_rsp(dht_contact_msg_t ** contacts, + size_t len, + struct list_head * pl, + enum dht_code code) +{ + struct timespec now; + size_t i; - /* Refresh unaccessed buckets. */ - bucket_refresh(dht, dht->buckets, now.tv_sec, &reflist); + assert(contacts != NULL); + assert(len > 0); + assert(pl != NULL); + assert(list_is_empty(pl)); - pthread_rwlock_unlock(&dht->lock); + clock_gettime(CLOCK_REALTIME_COARSE, &now); - list_for_each_safe(p, h, &reflist) { - struct contact * c; - c = list_entry(p, struct contact, next); - lu = kad_lookup(dht, c->id, KAD_FIND_NODE); - if (lu != NULL) - lookup_destroy(lu); - list_del(&c->next); - contact_destroy(c); + for (i = 0; i < len; i++) { + dht_contact_msg_t * c = contacts[i]; + struct peer_entry * e; + if (c->addr == dht.addr) + continue; + + if (dht_kv_update_contacts(c->id.data, c->addr) < 0) + log_warn(PEER_FMT " Failed to update contacts.", + PEER_VAL(c->id.data, c->addr)); + + e = malloc(sizeof(*e)); + if (e == NULL) { + log_err(PEER_FMT " Failed to malloc entry.", + PEER_VAL(c->id.data, c->addr)); + continue; } - sleep(intv); - } + e->id = dht_dup_key(c->id.data); + if (e->id == NULL) { + log_warn(PEER_FMT " Failed to duplicate id.", + PEER_VAL(c->id.data, c->addr)); + free(e); + continue; + } - return (void *) 0; + e->cookie = generate_cookie(); + e->code = code; + e->addr = c->addr; + e->t_sent = now.tv_sec; + + list_add_tail(&e->next, pl); + } } -static int kad_handle_join_resp(struct dht * dht, - struct kad_req * req, - kad_msg_t * msg) +static dht_msg_t * do_dht_kv_find_value_req(const dht_find_req_msg_t * req) { - assert(dht); - assert(req); - assert(msg); + dht_contact_msg_t ** contacts; + ssize_t n_contacts; + buffer_t * vals; + ssize_t n_vals; + dht_msg_t * rsp; + uint8_t * key; + uint64_t cookie; - /* We might send version numbers later to warn of updates if needed. */ - if (!(msg->has_alpha && msg->has_b && msg->has_k && msg->has_t_expire && - msg->has_t_refresh && msg->has_t_replicate)) { - log_warn("Join refused by remote."); - return -1; + assert(req != NULL); + + key = req->key.data; + cookie = req->cookie; + + n_contacts = dht_kv_get_contacts(key, &contacts); + if (n_contacts < 0) { + log_warn(KEY_FMT " Failed to get contacts.", KEY_VAL(key)); + goto fail_contacts; } - if (msg->b < sizeof(uint64_t)) { - log_err("Hash sizes less than 8 bytes unsupported."); - return -1; + assert(n_contacts > 0 || contacts == NULL); + + n_vals = dht_kv_retrieve(key, &vals); + if (n_vals < 0) { + log_dbg(KEY_FMT " Failed to get values.", KEY_VAL(key)); + goto fail_vals; } - pthread_rwlock_wrlock(&dht->lock); + if (n_vals == 0) + log_dbg(KEY_FMT " No values found.", KEY_VAL(key)); - dht->buckets = bucket_create(); - if (dht->buckets == NULL) { - pthread_rwlock_unlock(&dht->lock); - return -1; + rsp = dht_kv_find_value_rsp_msg(key, cookie, &contacts, n_contacts, + &vals, n_vals); + if (rsp == NULL) { + log_err(KEY_FMT " Failed to create %s msg.", + KEY_VAL(key), dht_code_str[DHT_FIND_VALUE_RSP]); + goto fail_msg; } - /* Likely corrupt packet. The member will refuse, we might here too. */ - if (msg->alpha != KAD_ALPHA || msg->k != KAD_K) - log_warn("Different kademlia parameters detected."); + log_info(KEY_FMT " Responding with %zd contacts, %zd values.", + KEY_VAL(req->key.data), n_contacts, n_vals); - if (msg->t_replicate != KAD_T_REPL) - log_warn("Different kademlia replication time detected."); + return rsp; - if (msg->t_refresh != KAD_T_REFR) - log_warn("Different kademlia refresh time detected."); + fail_msg: + freebufs(vals, n_vals); + fail_vals: + while (n_contacts-- > 0) + dht_contact_msg__free_unpacked(contacts[n_contacts], NULL); + free(contacts); + fail_contacts: + return NULL; +} - dht->k = msg->k; - dht->b = msg->b; - dht->t_expire = msg->t_expire; - dht->t_repub = MAX(1, dht->t_expire - 10); +static void do_dht_kv_find_node_rsp(const dht_find_node_rsp_msg_t * rsp) +{ + struct list_head pl; - if (pthread_create(&dht->worker, NULL, work, dht)) { - bucket_destroy(dht->buckets); - pthread_rwlock_unlock(&dht->lock); - return -1; - } + assert(rsp != NULL); + + list_head_init(&pl); - kad_req_respond(req); + dht_kv_process_node_rsp(rsp->contacts, rsp->n_contacts, &pl, + DHT_FIND_NODE_REQ); - dht_update_bucket(dht, msg->s_id.data, msg->s_addr); + if (list_is_empty(&pl)) + goto no_contacts; - pthread_rwlock_unlock(&dht->lock); + if (dht_kv_update_req(rsp->key.data, &pl) < 0) { + log_err(KEY_FMT " Failed to update request.", + KEY_VAL(rsp->key.data)); + goto fail_update; + } - log_dbg("Enrollment of DHT completed."); + dht_kv_query_contacts(rsp->key.data, &pl); - return 0; + return; + + fail_update: + peer_list_destroy(&pl); + no_contacts: + return; } -static int kad_handle_find_resp(struct dht * dht, - struct kad_req * req, - kad_msg_t * msg) +static void do_dht_kv_find_value_rsp(const dht_find_node_rsp_msg_t * node, + const dht_find_value_rsp_msg_t * val) { - struct lookup * lu; + struct list_head pl; + uint8_t * key; - assert(dht); - assert(req); - assert(msg); + assert(node != NULL); + assert(val != NULL); - pthread_rwlock_rdlock(&dht->lock); + list_head_init(&pl); - lu = dht_find_lookup(dht, req->cookie); - if (lu == NULL) { - pthread_rwlock_unlock(&dht->lock); - return -1; + key = node->key.data; + + dht_kv_process_node_rsp(node->contacts, node->n_contacts, &pl, + DHT_FIND_VALUE_REQ); + + if (val->n_values > 0) { + log_dbg(KEY_FMT " %zd new values received.", + KEY_VAL(key), val->n_values); + if (dht_kv_respond_req(key, val->values, val->n_values) < 0) + log_warn(KEY_FMT " Failed to respond to request.", + KEY_VAL(key)); + peer_list_destroy(&pl); + return; /* done! */ } - lookup_update(dht, lu, msg); + if (list_is_empty(&pl)) + goto no_contacts; + + if (dht_kv_update_req(key, &pl) < 0) { + log_err(KEY_FMT " Failed to update request.", KEY_VAL(key)); + goto fail_update; + } - pthread_rwlock_unlock(&dht->lock); + dht_kv_query_remote(key, NULL, &pl); - return 0; + return; + fail_update: + peer_list_destroy(&pl); + no_contacts: + return; } -static void kad_handle_response(struct dht * dht, - kad_msg_t * msg) +static dht_msg_t * dht_wait_for_dht_msg(void) { - struct kad_req * req; + dht_msg_t * msg; + struct cmd * cmd; - assert(dht); - assert(msg); + pthread_mutex_lock(&dht.cmds.mtx); - pthread_rwlock_wrlock(&dht->lock); + pthread_cleanup_push(__cleanup_mutex_unlock, &dht.cmds.mtx); - req = dht_find_request(dht, msg); - if (req == NULL) { - pthread_rwlock_unlock(&dht->lock); + while (list_is_empty(&dht.cmds.list)) + pthread_cond_wait(&dht.cmds.cond, &dht.cmds.mtx); + + cmd = list_last_entry(&dht.cmds.list, struct cmd, next); + list_del(&cmd->next); + + pthread_cleanup_pop(true); + + msg = dht_msg__unpack(NULL, cmd->cbuf.len, cmd->cbuf.data); + if (msg == NULL) + log_warn("Failed to unpack DHT msg."); + + freebuf(cmd->cbuf); + free(cmd); + + return msg; +} + +static void do_dht_msg(dht_msg_t * msg) +{ + dht_msg_t * rsp = NULL; + uint8_t * id; + uint64_t addr; + +#ifdef DEBUG_PROTO_DHT + dht_kv_debug_msg_rcv(msg); +#endif + if (dht_kv_validate_msg(msg) == -EINVAL) { + log_warn("%s Validation failed.", DHT_CODE(msg)); + dht_msg__free_unpacked(msg, NULL); return; } - bmp_release(dht->cookies, req->cookie); - list_del(&req->next); + id = msg->src->id.data; + addr = msg->src->addr; + + if (dht_kv_update_contacts(id, addr) < 0) + log_warn(PEER_FMT " Failed to update contact from msg src.", + PEER_VAL(id, addr)); - pthread_rwlock_unlock(&dht->lock); + pthread_cleanup_push(__cleanup_dht_msg, msg); - switch(req->code) { - case KAD_JOIN: - if (kad_handle_join_resp(dht, req, msg)) - log_err("Enrollment of DHT failed."); + switch(msg->code) { + case DHT_FIND_VALUE_REQ: + rsp = do_dht_kv_find_value_req(msg->find); break; - case KAD_FIND_VALUE: - case KAD_FIND_NODE: - if (dht_get_state(dht) != DHT_RUNNING) - break; - kad_handle_find_resp(dht, req, msg); + case DHT_FIND_NODE_REQ: + rsp = do_dht_kv_find_node_req(msg->find); break; - default: + case DHT_STORE: + do_dht_kv_store(msg->store); + break; + case DHT_FIND_NODE_RSP: + do_dht_kv_find_node_rsp(msg->node); + break; + case DHT_FIND_VALUE_RSP: + do_dht_kv_find_value_rsp(msg->node, msg->val); break; + default: + assert(false); /* already validated */ } - kad_req_destroy(req); + pthread_cleanup_pop(true); + + if (rsp == NULL) + return; + + pthread_cleanup_push(__cleanup_dht_msg, rsp); + + dht_send_msg(rsp, addr); + + pthread_cleanup_pop(true); /* free rsp */ } -int dht_bootstrap(void * dir) +static void * dht_handle_packet(void * o) { - struct dht * dht; + (void) o; - dht = (struct dht *) dir; + while (true) { + dht_msg_t * msg; - assert(dht); + msg = dht_wait_for_dht_msg(); + if (msg == NULL) + continue; - pthread_rwlock_wrlock(&dht->lock); + tpm_begin_work(dht.tpm); - dht->id = create_id(dht->b); - if (dht->id == NULL) - goto fail_id; + do_dht_msg(msg); - dht->buckets = bucket_create(); - if (dht->buckets == NULL) - goto fail_buckets; + tpm_end_work(dht.tpm); + } - dht->buckets->depth = 0; - dht->buckets->mask = 0; + return (void *) 0; +} #ifndef __DHT_TEST__ - dht->b = hash_len(ipcpi.dir_hash_algo); -#else - dht->b = DHT_TEST_KEY_LEN; +static void dht_post_packet(void * comp, + struct ssm_pk_buff * spb) +{ + struct cmd * cmd; + + (void) comp; + + cmd = malloc(sizeof(*cmd)); + if (cmd == NULL) { + log_err("Command malloc failed."); + goto fail_cmd; + } + + cmd->cbuf.data = malloc(ssm_pk_buff_len(spb)); + if (cmd->cbuf.data == NULL) { + log_err("Command buffer malloc failed."); + goto fail_buf; + } + + cmd->cbuf.len = ssm_pk_buff_len(spb); + + memcpy(cmd->cbuf.data, ssm_pk_buff_head(spb), cmd->cbuf.len); + + ipcp_spb_release(spb); + + pthread_mutex_lock(&dht.cmds.mtx); + + list_add(&cmd->next, &dht.cmds.list); + + pthread_cond_signal(&dht.cmds.cond); + + pthread_mutex_unlock(&dht.cmds.mtx); + + return; + + fail_buf: + free(cmd); + fail_cmd: + ipcp_spb_release(spb); + return; +} #endif - dht->t_expire = 86400; /* 1 day */ - dht->t_repub = dht->t_expire - 10; - dht->k = KAD_K; - if (pthread_create(&dht->worker, NULL, work, dht)) - goto fail_pthread_create; +int dht_reg(const uint8_t * key) +{ + buffer_t val; - dht->state = DHT_RUNNING; + if (addr_to_buf(dht.addr, &val) < 0) { + log_err("Failed to convert address to buffer."); + goto fail_a2b; + } - dht_update_bucket(dht, dht->id, dht->addr); + if (dht_kv_publish(key, val)) { + log_err(KV_FMT " Failed to publish.", KV_VAL(key, val)); + goto fail_publish; + } - pthread_rwlock_unlock(&dht->lock); + freebuf(val); return 0; - - fail_pthread_create: - bucket_destroy(dht->buckets); - dht->buckets = NULL; - fail_buckets: - free(dht->id); - dht->id = NULL; - fail_id: - pthread_rwlock_unlock(&dht->lock); + fail_publish: + freebuf(val); + fail_a2b: return -1; } -static struct ref_entry * ref_entry_get(struct dht * dht, - const uint8_t * key) +int dht_unreg(const uint8_t * key) { - struct list_head * p; + buffer_t val; - list_for_each(p, &dht->refs) { - struct ref_entry * r = list_entry(p, struct ref_entry, next); - if (!memcmp(key, r->key, dht-> b) ) - return r; + if (addr_to_buf(dht.addr, &val) < 0) { + log_err("Failed to convert address to buffer."); + goto fail_a2b; } - return NULL; + if (dht_kv_unpublish(key, val)) { + log_err(KV_FMT " Failed to unpublish.", KV_VAL(key, val)); + goto fail_unpublish; + } + + freebuf(val); + + return 0; + fail_unpublish: + freebuf(val); + fail_a2b: + return -ENOMEM; } -int dht_reg(void * dir, - const uint8_t * key) +uint64_t dht_query(const uint8_t * key) { - struct dht * dht; - struct ref_entry * e; - uint64_t addr; - time_t t_expire; + buffer_t * vals; + ssize_t n; + uint64_t addr; - dht = (struct dht *) dir; + n = dht_kv_retrieve(key, &vals); + if (n < 0) { + log_err(KEY_FMT " Failed to query db.", KEY_VAL(key)); + goto fail_vals; + } - assert(dht); - assert(key); - assert(dht->addr != 0); + if (n == 0) { + assert(vals == NULL); - if (dht_wait_running(dht)) - return -1; + log_dbg(KEY_FMT " No local values.", KEY_VAL(key)); + n = dht_kv_query_remote(key, &vals, NULL); + if (n < 0) { + log_warn(KEY_FMT " Failed to query DHT.", KEY_VAL(key)); + goto fail_vals; + } + if (n == 0) { + log_dbg(KEY_FMT " No values.", KEY_VAL(key)); + goto no_vals; + } + } - pthread_rwlock_wrlock(&dht->lock); + if (buf_to_addr(vals[0], &addr) < 0) { + log_err(VAL_FMT " Failed addr conversion.", VAL_VAL(vals[0])); + goto fail_b2a; + } - if (ref_entry_get(dht, key) != NULL) { - log_dbg("Name already registered."); - pthread_rwlock_unlock(&dht->lock); - return 0; + if (n > 1 && addr == INVALID_ADDR && buf_to_addr(vals[1], &addr) < 0) { + log_err(VAL_FMT " Failed addr conversion.", VAL_VAL(vals[1])); + goto fail_b2a; } - e = ref_entry_create(dht, key); + freebufs(vals, n); + + return addr; + fail_b2a: + freebufs(vals, n); + return INVALID_ADDR; + no_vals: + free(vals); + fail_vals: + return INVALID_ADDR; +} + +static int emergency_peer(struct list_head * pl) +{ + struct peer_entry * e; + struct timespec now; + + assert(pl != NULL); + assert(list_is_empty(pl)); + + if (dht.peer == INVALID_ADDR) + return -1; + + clock_gettime(CLOCK_REALTIME_COARSE, &now); + + e = malloc(sizeof(*e)); if (e == NULL) { - pthread_rwlock_unlock(&dht->lock); - return -ENOMEM; + log_err("Failed to malloc emergency peer entry."); + goto fail_malloc; } - list_add(&e->next, &dht->refs); - - t_expire = dht->t_expire; - addr = dht->addr; + e->id = dht_dup_key(dht.id.data); + if (e->id == NULL) { + log_err("Failed to duplicate DHT ID for emergency peer."); + goto fail_id; + } - pthread_rwlock_unlock(&dht->lock); + e->addr = dht.peer; + e->cookie = dht.magic; + e->code = DHT_FIND_NODE_REQ; + e->t_sent = now.tv_sec; - kad_publish(dht, key, addr, t_expire); + list_add_tail(&e->next, pl); return 0; + fail_id: + free(e); + fail_malloc: + return -ENOMEM; } -int dht_unreg(void * dir, - const uint8_t * key) +static int dht_kv_seed_bootstrap_peer(void) { - struct dht * dht; - struct list_head * p; - struct list_head * h; + struct list_head pl; - dht = (struct dht *) dir; + list_head_init(&pl); - assert(dht); - assert(key); + if (dht.peer == INVALID_ADDR) { + log_dbg("No-one to contact."); + return 0; + } - if (dht_get_state(dht) != DHT_RUNNING) - return -1; + if (emergency_peer(&pl) < 0) { + log_err("Could not create emergency peer."); + goto fail_peer; + } - pthread_rwlock_wrlock(&dht->lock); + log_dbg("Pinging emergency peer " ADDR_FMT32 ".", + ADDR_VAL32(&dht.peer)); - list_for_each_safe(p, h, &dht->refs) { - struct ref_entry * r = list_entry(p, struct ref_entry, next); - if (!memcmp(key, r->key, dht-> b) ) { - list_del(&r->next); - ref_entry_destroy(r); - } + if (dht_kv_query_contacts(dht.id.data, &pl) < 0) { + log_warn("Failed to bootstrap peer."); + goto fail_query; } - dht_del(dht, key, dht->addr); - - pthread_rwlock_unlock(&dht->lock); + peer_list_destroy(&pl); return 0; + fail_query: + peer_list_destroy(&pl); + fail_peer: + return -EAGAIN; } -uint64_t dht_query(void * dir, - const uint8_t * key) +static void dht_kv_check_contacts(void) { - struct dht * dht; - struct dht_entry * e; - struct lookup * lu; - uint64_t addrs[KAD_K]; - size_t n; + struct list_head cl; + struct list_head pl; - dht = (struct dht *) dir; + list_head_init(&cl); - assert(dht); + dht_kv_contact_list(dht.id.data, &cl, dht.k); - addrs[0] = 0; + if (!list_is_empty(&cl)) + goto success; - if (dht_wait_running(dht)) - return 0; + contact_list_destroy(&cl); - pthread_rwlock_rdlock(&dht->lock); + list_head_init(&pl); - e = dht_find_entry(dht, key); - if (e != NULL) - addrs[0] = dht_entry_get_addr(dht, e); + if (dht.peer == INVALID_ADDR) { + log_dbg("No-one to contact."); + return; + } - pthread_rwlock_unlock(&dht->lock); + if (emergency_peer(&pl) < 0) { + log_err("Could not create emergency peer."); + goto fail_peer; + } - if (addrs[0] != 0) - return addrs[0]; + log_dbg("No contacts found, using emergency peer " ADDR_FMT32 ".", + ADDR_VAL32(&dht.peer)); - lu = kad_lookup(dht, key, KAD_FIND_VALUE); - if (lu == NULL) - return 0; + dht_kv_query_contacts(dht.id.data, &pl); - n = lookup_get_addrs(lu, addrs); - if (n == 0) { - lookup_destroy(lu); - return 0; + peer_list_destroy(&pl); + + return; + success: + contact_list_destroy(&cl); + return; + fail_peer: + return; +} + +static void dht_kv_remove_expired_reqs(void) +{ + struct list_head * p; + struct list_head * h; + struct timespec now; + + clock_gettime(PTHREAD_COND_CLOCK, &now); + + pthread_mutex_lock(&dht.reqs.mtx); + + list_for_each_safe(p, h, &dht.reqs.list) { + struct dht_req * e; + e = list_entry(p, struct dht_req, next); + if (IS_EXPIRED(e, &now)) { + log_dbg(KEY_FMT " Removing expired request.", + KEY_VAL(e->key)); + list_del(&e->next); + dht_req_destroy(e); + --dht.reqs.len; + } } - lookup_destroy(lu); + pthread_mutex_unlock(&dht.reqs.mtx); +} - /* Current behaviour is anycast and return the first peer address. */ - if (addrs[0] != dht->addr) - return addrs[0]; +static void value_list_destroy(struct list_head * vl) +{ + struct list_head * p; + struct list_head * h; - if (n > 1) - return addrs[1]; + assert(vl != NULL); - return 0; + list_for_each_safe(p, h, vl) { + struct val_entry * v = list_entry(p, struct val_entry, next); + list_del(&v->next); + val_entry_destroy(v); + } } -static void * dht_handle_packet(void * o) +#define MUST_REPLICATE(v, now) ((now)->tv_sec > (v)->t_repl + dht.t_repl) +#define MUST_REPUBLISH(v, now) /* Close to expiry deadline */ \ + (((v)->t_exp - (now)->tv_sec) < (DHT_N_REPUB * dht.t_repl)) +static void dht_entry_get_repl_lists(const struct dht_entry * e, + struct list_head * repl, + struct list_head * rebl, + struct timespec * now) { - struct dht * dht = (struct dht *) o; + struct list_head * p; + struct val_entry * n; - assert(dht); + list_for_each(p, &e->vals.list) { + struct val_entry * v = list_entry(p, struct val_entry, next); + if (MUST_REPLICATE(v, now) && !IS_EXPIRED(v, now)) { + n = val_entry_create(v->val, v->t_exp); + if (n == NULL) + continue; - while (true) { - kad_msg_t * msg; - kad_contact_msg_t ** cmsgs; - kad_msg_t resp_msg = KAD_MSG__INIT; - uint64_t addr; - buffer_t buf; - size_t i; - size_t b; - size_t t_expire; - struct cmd * cmd; + list_add_tail(&n->next, repl); + } + } - pthread_mutex_lock(&dht->mtx); + list_for_each(p, &e->lvals.list) { + struct val_entry * v = list_entry(p, struct val_entry, next); + if (MUST_REPLICATE(v, now) && MUST_REPUBLISH(v, now)) { + /* Add expire time here, to allow creating val_entry */ + n = val_entry_create(v->val, now->tv_sec + dht.t_expire); + if (n == NULL) + continue; - pthread_cleanup_push(__cleanup_mutex_unlock, &dht->mtx); + list_add_tail(&n->next, rebl); + } + } +} - while (list_is_empty(&dht->cmds)) - pthread_cond_wait(&dht->cond, &dht->mtx); +static int dht_kv_next_values(uint8_t * key, + struct list_head * repl, + struct list_head * rebl) +{ + struct timespec now; + struct list_head * p; + struct list_head * h; + struct dht_entry * e = NULL; - cmd = list_last_entry(&dht->cmds, struct cmd, next); - list_del(&cmd->next); + assert(key != NULL); + assert(repl != NULL); + assert(rebl != NULL); - pthread_cleanup_pop(true); + clock_gettime(CLOCK_REALTIME_COARSE, &now); - i = shm_du_buff_tail(cmd->sdb) - shm_du_buff_head(cmd->sdb); + assert(list_is_empty(repl)); + assert(list_is_empty(rebl)); - msg = kad_msg__unpack(NULL, i, shm_du_buff_head(cmd->sdb)); -#ifndef __DHT_TEST__ - ipcp_sdb_release(cmd->sdb); -#endif - free(cmd); + pthread_rwlock_rdlock(&dht.db.lock); - if (msg == NULL) { - log_err("Failed to unpack message."); - continue; - } + if (dht.db.kv.len == 0) + goto no_entries; - if (msg->code != KAD_RESPONSE && dht_wait_running(dht)) { - kad_msg__free_unpacked(msg, NULL); - log_dbg("Got a request message when not running."); - continue; - } + list_for_each_safe(p, h, &dht.db.kv.list) { + e = list_entry(p, struct dht_entry, next); + if (IS_CLOSER(e->key, key)) + continue; /* Already processed */ + } - pthread_rwlock_rdlock(&dht->lock); + if (e != NULL) { + memcpy(key, e->key, dht.id.len); + dht_entry_get_repl_lists(e, repl, rebl, &now); + } + no_entries: + pthread_rwlock_unlock(&dht.db.lock); - b = dht->b; - t_expire = dht->t_expire; + return list_is_empty(repl) && list_is_empty(rebl) ? -ENOENT : 0; +} - pthread_rwlock_unlock(&dht->lock); +static void dht_kv_replicate_value(const uint8_t * key, + struct val_entry * v, + const struct timespec * now) +{ + assert(MUST_REPLICATE(v, now)); - if (msg->has_key && msg->key.len != b) { - kad_msg__free_unpacked(msg, NULL); - log_warn("Bad key in message."); - continue; - } + (void) now; - if (msg->has_s_id && !msg->has_b && msg->s_id.len != b) { - kad_msg__free_unpacked(msg, NULL); - log_warn("Bad source ID in message of type %d.", - msg->code); - continue; - } + if (dht_kv_store_remote(key, v->val, v->t_exp) == 0) { + log_dbg(KV_FMT " Replicated.", KV_VAL(key, v->val)); + return; + } - tpm_dec(dht->tpm); + log_dbg(KV_FMT " Replication failed.", KV_VAL(key, v->val)); - addr = msg->s_addr; + list_del(&v->next); + val_entry_destroy(v); +} - resp_msg.code = KAD_RESPONSE; - resp_msg.cookie = msg->cookie; +static void dht_kv_republish_value(const uint8_t * key, + struct val_entry * v, + const struct timespec * now) +{ + assert(MUST_REPLICATE(v, now)); - switch(msg->code) { - case KAD_JOIN: - /* Refuse enrollee on check fails. */ - if (msg->alpha != KAD_ALPHA || msg->k != KAD_K) { - log_warn("Parameter mismatch. " - "DHT enrolment refused."); - break; - } + if (MUST_REPUBLISH(v, now)) + assert(v->t_exp >= now->tv_sec + dht.t_expire); - if (msg->t_replicate != KAD_T_REPL) { - log_warn("Replication time mismatch. " - "DHT enrolment refused."); + if (dht_kv_store_remote(key, v->val, v->t_exp) == 0) { + log_dbg(KV_FMT " Republished.", KV_VAL(key, v->val)); + return; + } - break; - } + if (MUST_REPUBLISH(v, now)) + log_warn(KV_FMT " Republish failed.", KV_VAL(key, v->val)); + else + log_dbg(KV_FMT " Replication failed.", KV_VAL(key, v->val)); - if (msg->t_refresh != KAD_T_REFR) { - log_warn("Refresh time mismatch. " - "DHT enrolment refused."); - break; - } + list_del(&v->next); + val_entry_destroy(v); +} - resp_msg.has_alpha = true; - resp_msg.has_b = true; - resp_msg.has_k = true; - resp_msg.has_t_expire = true; - resp_msg.has_t_refresh = true; - resp_msg.has_t_replicate = true; - resp_msg.alpha = KAD_ALPHA; - resp_msg.b = b; - resp_msg.k = KAD_K; - resp_msg.t_expire = t_expire; - resp_msg.t_refresh = KAD_T_REFR; - resp_msg.t_replicate = KAD_T_REPL; - break; - case KAD_FIND_VALUE: - buf = dht_retrieve(dht, msg->key.data); - if (buf.len != 0) { - resp_msg.n_addrs = buf.len; - resp_msg.addrs = (uint64_t *) buf.data; - break; - } - /* FALLTHRU */ - case KAD_FIND_NODE: - /* Return k closest contacts. */ - resp_msg.n_contacts = - dht_get_contacts(dht, msg->key.data, &cmsgs); - resp_msg.contacts = cmsgs; - break; - case KAD_STORE: - if (msg->n_contacts < 1) { - log_warn("No contacts in store message."); - break; - } +static void dht_kv_update_replication_times(const uint8_t * key, + struct list_head * repl, + struct list_head * rebl, + const struct timespec * now) +{ + struct dht_entry * e; + struct list_head * p; + struct list_head * h; + struct val_entry * v; - if (!msg->has_t_expire) { - log_warn("No expiry time in store message."); - break; - } + assert(key != NULL); + assert(repl != NULL); + assert(rebl != NULL); + assert(now != NULL); - kad_add(dht, *msg->contacts, msg->n_contacts, - msg->t_expire); - break; - case KAD_RESPONSE: - kad_handle_response(dht, msg); - break; - default: - assert(false); - break; - } + pthread_rwlock_wrlock(&dht.db.lock); - if (msg->code != KAD_JOIN) { - pthread_rwlock_wrlock(&dht->lock); - if (dht_get_state(dht) == DHT_JOINING && - dht->buckets == NULL) { - pthread_rwlock_unlock(&dht->lock); - goto finish; - } + e = __dht_kv_find_entry(key); + if (e == NULL) { + pthread_rwlock_unlock(&dht.db.lock); + return; + } - if (dht_update_bucket(dht, msg->s_id.data, addr)) - log_warn("Failed to update bucket."); - pthread_rwlock_unlock(&dht->lock); + list_for_each_safe(p, h, repl) { + struct val_entry * x; + v = list_entry(p, struct val_entry, next); + x = dht_entry_get_val(e, v->val); + if (x == NULL) { + log_err(KV_FMT " Not in vals.", KV_VAL(key, v->val)); + continue; } - if (msg->code < KAD_STORE && send_msg(dht, &resp_msg, addr) < 0) - log_warn("Failed to send response."); - - finish: - kad_msg__free_unpacked(msg, NULL); + x->t_repl = now->tv_sec; - if (resp_msg.n_addrs > 0) - free(resp_msg.addrs); + list_del(&v->next); + val_entry_destroy(v); + } - if (resp_msg.n_contacts == 0) { - tpm_inc(dht->tpm); + list_for_each_safe(p, h, rebl) { + struct val_entry * x; + v = list_entry(p, struct val_entry, next); + x = dht_entry_get_lval(e, v->val); + if (x == NULL) { + log_err(KV_FMT " Not in lvals.", KV_VAL(key, v->val)); continue; } - for (i = 0; i < resp_msg.n_contacts; ++i) - kad_contact_msg__free_unpacked(resp_msg.contacts[i], - NULL); - free(resp_msg.contacts); + x->t_repl = now->tv_sec; + if (v->t_exp > x->t_exp) { + x->t_exp = v->t_exp; /* update expiration time */ + } - tpm_inc(dht->tpm); + list_del(&v->next); + val_entry_destroy(v); } - return (void *) 0; + pthread_rwlock_unlock(&dht.db.lock); } -static void dht_post_packet(void * comp, - struct shm_du_buff * sdb) +static void __cleanup_value_list(void * o) { - struct cmd * cmd; - struct dht * dht = (struct dht *) comp; + return value_list_destroy((struct list_head *) o); +} - if (dht_get_state(dht) == DHT_SHUTDOWN) { -#ifndef __DHT_TEST__ - ipcp_sdb_release(sdb); -#endif - return; +static void dht_kv_replicate_values(const uint8_t * key, + struct list_head * repl, + struct list_head * rebl) +{ + struct timespec now; + struct list_head * p; + struct list_head * h; + + clock_gettime(CLOCK_REALTIME_COARSE, &now); + + pthread_cleanup_push(__cleanup_value_list, repl); + pthread_cleanup_push(__cleanup_value_list, rebl); + + list_for_each_safe(p, h, repl) { + struct val_entry * v; + v = list_entry(p, struct val_entry, next); + dht_kv_replicate_value(key, v, &now); } - cmd = malloc(sizeof(*cmd)); - if (cmd == NULL) { - log_err("Command failed. Out of memory."); + list_for_each_safe(p, h, rebl) { + struct val_entry * v; + v = list_entry(p, struct val_entry, next); + dht_kv_republish_value(key, v, &now); + } + + pthread_cleanup_pop(false); + pthread_cleanup_pop(false); + + /* removes non-replicated items from the list */ + dht_kv_update_replication_times(key, repl, rebl, &now); + + if (list_is_empty(repl) && list_is_empty(rebl)) + return; + + log_warn(KEY_FMT " Failed to update replication times.", KEY_VAL(key)); +} + +static void dht_kv_replicate(void) +{ + struct list_head repl; /* list of values to replicate */ + struct list_head rebl; /* list of local values to republish */ + uint8_t * key; + + key = dht_dup_key(dht.id.data); /* dist == 0 */ + if (key == NULL) { + log_err("Replicate: Failed to duplicate DHT ID."); return; } - cmd->sdb = sdb; + list_head_init(&repl); + list_head_init(&rebl); - pthread_mutex_lock(&dht->mtx); + pthread_cleanup_push(free, key); - list_add(&cmd->next, &dht->cmds); + while (dht_kv_next_values(key, &repl, &rebl) == 0) { + dht_kv_replicate_values(key, &repl, &rebl); + if (!list_is_empty(&repl)) { + log_warn(KEY_FMT " Replication items left.", + KEY_VAL(key)); + value_list_destroy(&repl); + } - pthread_cond_signal(&dht->cond); + if (!list_is_empty(&rebl)) { + log_warn(KEY_FMT " Republish items left.", + KEY_VAL(key)); + value_list_destroy(&rebl); + } + } - pthread_mutex_unlock(&dht->mtx); + pthread_cleanup_pop(true); } -void dht_destroy(void * dir) +static void dht_kv_refresh_contacts(void) { - struct dht * dht; struct list_head * p; struct list_head * h; + struct list_head rl; /* refresh list */ + struct timespec now; - dht = (struct dht *) dir; - if (dht == NULL) - return; + list_head_init(&rl); -#ifndef __DHT_TEST__ - tpm_stop(dht->tpm); + clock_gettime(CLOCK_REALTIME_COARSE, &now); - tpm_destroy(dht->tpm); -#endif - if (dht_get_state(dht) == DHT_RUNNING) { - dht_set_state(dht, DHT_SHUTDOWN); - pthread_cancel(dht->worker); - pthread_join(dht->worker, NULL); - } + pthread_rwlock_rdlock(&dht.db.lock); - pthread_rwlock_wrlock(&dht->lock); + __dht_kv_bucket_refresh_list(dht.db.contacts.root, now.tv_sec, &rl); - list_for_each_safe(p, h, &dht->cmds) { - struct cmd * c = list_entry(p, struct cmd, next); + pthread_rwlock_unlock(&dht.db.lock); + + list_for_each_safe(p, h, &rl) { + struct contact * c; + c = list_entry(p, struct contact, next); + log_dbg(PEER_FMT " Refreshing contact.", + PEER_VAL(c->id, c->addr)); + dht_kv_query_contacts(c->id, NULL); list_del(&c->next); -#ifndef __DHT_TEST__ - ipcp_sdb_release(c->sdb); -#endif - free(c); + contact_destroy(c); } - list_for_each_safe(p, h, &dht->entries) { - struct dht_entry * e = list_entry(p, struct dht_entry, next); - list_del(&e->next); - dht_entry_destroy(e); - } + assert(list_is_empty(&rl)); +} - list_for_each_safe(p, h, &dht->requests) { - struct kad_req * r = list_entry(p, struct kad_req, next); - list_del(&r->next); - kad_req_destroy(r); - } +static void (*tasks[])(void) = { + dht_kv_check_contacts, + dht_kv_remove_expired_entries, + dht_kv_remove_expired_reqs, + dht_kv_replicate, + dht_kv_refresh_contacts, + NULL +}; - list_for_each_safe(p, h, &dht->refs) { - struct ref_entry * e = list_entry(p, struct ref_entry, next); - list_del(&e->next); - ref_entry_destroy(e); +static void * work(void * o) +{ + struct timespec now = TIMESPEC_INIT_MS(1); + time_t intv; + size_t n; /* number of tasks */ + + n = sizeof(tasks) / sizeof(tasks[0]) - 1; /* last is NULL */ + + (void) o; + + while (dht_kv_seed_bootstrap_peer() == -EAGAIN) { + ts_add(&now, &now, &now); /* exponential backoff */ + if (now.tv_sec > 1) /* cap at 1 second */ + now.tv_sec = 1; + nanosleep(&now, NULL); } - list_for_each_safe(p, h, &dht->lookups) { - struct lookup * l = list_entry(p, struct lookup, next); - list_del(&l->next); - lookup_destroy(l); + intv = gcd(dht.t_expire, (dht.t_expire - DHT_N_REPUB * dht.t_repl)); + intv = gcd(intv, gcd(dht.t_repl, dht.t_refresh)) / 2; + intv = MAX(1, intv / n); + + log_dbg("DHT worker starting %ld seconds interval.", intv * n); + + while (true) { + int i = 0; + while (tasks[i] != NULL) { + tasks[i++](); + sleep(intv); + } } - pthread_rwlock_unlock(&dht->lock); + return (void *) 0; +} - if (dht->buckets != NULL) - bucket_destroy(dht->buckets); +int dht_start(void) +{ + dht.state = DHT_RUNNING; + + if (tpm_start(dht.tpm)) + goto fail_tpm_start; + +#ifndef __DHT_TEST__ + if (pthread_create(&dht.worker, NULL, work, NULL)) { + log_err("Failed to create DHT worker thread."); + goto fail_worker; + } - bmp_destroy(dht->cookies); + dht.eid = dt_reg_comp(&dht, &dht_post_packet, DHT); + if ((int) dht.eid < 0) { + log_err("Failed to register DHT component."); + goto fail_reg; + } +#else + (void) work; +#endif + return 0; +#ifndef __DHT_TEST__ + fail_reg: + pthread_cancel(dht.worker); + pthread_join(dht.worker, NULL); + fail_worker: + tpm_stop(dht.tpm); +#endif + fail_tpm_start: + dht.state = DHT_INIT; + return -1; +} - pthread_mutex_destroy(&dht->mtx); +void dht_stop(void) +{ + assert(dht.state == DHT_RUNNING); - pthread_rwlock_destroy(&dht->lock); +#ifndef __DHT_TEST__ + dt_unreg_comp(dht.eid); - free(dht->id); + pthread_cancel(dht.worker); + pthread_join(dht.worker, NULL); +#endif + tpm_stop(dht.tpm); - free(dht); + dht.state = DHT_INIT; } -static void * join_thr(void * o) +int dht_init(struct dir_dht_config * conf) { - struct join_info * info = (struct join_info *) o; - struct lookup * lu; - size_t retr = 0; + struct timespec now; + pthread_condattr_t cattr; - assert(info); + assert(conf != NULL); - while (kad_join(info->dht, info->addr)) { - if (dht_get_state(info->dht) == DHT_SHUTDOWN) { - log_dbg("DHT enrollment aborted."); - goto finish; - } + clock_gettime(CLOCK_REALTIME_COARSE, &now); - if (retr++ == KAD_JOIN_RETR) { - dht_set_state(info->dht, DHT_INIT); - log_warn("DHT enrollment attempt failed."); - goto finish; - } +#ifndef __DHT_TEST__ + dht.id.len = ipcp_dir_hash_len(); + dht.addr = addr_auth_address(); +#else + dht.id.len = DHT_TEST_KEY_LEN; + dht.addr = DHT_TEST_ADDR; +#endif + dht.t0 = now.tv_sec; + dht.alpha = conf->params.alpha; + dht.k = conf->params.k; + dht.t_expire = conf->params.t_expire; + dht.t_refresh = conf->params.t_refresh; + dht.t_repl = conf->params.t_replicate; + dht.peer = conf->peer; + + dht.magic = generate_cookie(); + + /* Send my address on enrollment */ + conf->peer = dht.addr; + + dht.id.data = generate_id(); + if (dht.id.data == NULL) { + log_err("Failed to create DHT ID."); + goto fail_id; + } + + list_head_init(&dht.cmds.list); - sleep(KAD_JOIN_INTV); + if (pthread_mutex_init(&dht.cmds.mtx, NULL)) { + log_err("Failed to initialize command mutex."); + goto fail_cmds_mutex; } - dht_set_state(info->dht, DHT_RUNNING); + if (pthread_cond_init(&dht.cmds.cond, NULL)) { + log_err("Failed to initialize command condvar."); + goto fail_cmds_cond; + } - lu = kad_lookup(info->dht, info->dht->id, KAD_FIND_NODE); - if (lu != NULL) - lookup_destroy(lu); + list_head_init(&dht.reqs.list); + dht.reqs.len = 0; - finish: - free(info); + if (pthread_mutex_init(&dht.reqs.mtx, NULL)) { + log_err("Failed to initialize request mutex."); + goto fail_reqs_mutex; + } - return (void *) 0; -} + if (pthread_condattr_init(&cattr)) { + log_err("Failed to initialize request condvar attributes."); + goto fail_cattr; + } +#ifndef __APPLE__ + if (pthread_condattr_setclock(&cattr, PTHREAD_COND_CLOCK)) { + log_err("Failed to set request condvar clock."); + goto fail_cattr; + } +#endif + if (pthread_cond_init(&dht.reqs.cond, &cattr)) { + log_err("Failed to initialize request condvar."); + goto fail_reqs_cond; + } -static void handle_event(void * self, - int event, - const void * o) -{ - struct dht * dht = (struct dht *) self; + list_head_init(&dht.db.kv.list); + dht.db.kv.len = 0; + dht.db.kv.vals = 0; + dht.db.kv.lvals = 0; - if (event == NOTIFY_DT_CONN_ADD) { - pthread_t thr; - struct join_info * inf; - struct conn * c = (struct conn *) o; - struct timespec slack = {0, DHT_ENROLL_SLACK * MILLION}; + if (pthread_rwlock_init(&dht.db.lock, NULL)) { + log_err("Failed to initialize store rwlock."); + goto fail_rwlock; + } - /* Give the pff some time to update for the new link. */ - nanosleep(&slack, NULL); + dht.db.contacts.root = bucket_create(); + if (dht.db.contacts.root == NULL) { + log_err("Failed to create DHT buckets."); + goto fail_buckets; + } - switch(dht_get_state(dht)) { - case DHT_INIT: - inf = malloc(sizeof(*inf)); - if (inf == NULL) - break; + if (rib_reg(DHT, &r_ops) < 0) { + log_err("Failed to register DHT RIB operations."); + goto fail_rib_reg; + } - inf->dht = dht; - inf->addr = c->conn_info.addr; - - if (dht_set_state(dht, DHT_JOINING) == 0 || - dht_wait_running(dht)) { - if (pthread_create(&thr, NULL, join_thr, inf)) { - dht_set_state(dht, DHT_INIT); - free(inf); - return; - } - pthread_detach(thr); - } else { - free(inf); - } - break; - case DHT_RUNNING: - /* - * FIXME: this lookup for effiency reasons - * causes a SEGV when stressed with rapid - * enrollments. - * lu = kad_lookup(dht, dht->id, KAD_FIND_NODE); - * if (lu != NULL) - * lookup_destroy(lu); - */ - break; - default: - break; - } + dht.tpm = tpm_create(2, 1, dht_handle_packet, NULL); + if (dht.tpm == NULL) { + log_err("Failed to create TPM for DHT."); + goto fail_tpm_create; } + + if (dht_kv_update_contacts(dht.id.data, dht.addr) < 0) + log_warn("Failed to update contacts with DHT ID."); + + pthread_condattr_destroy(&cattr); +#ifndef __DHT_TEST__ + log_info("DHT initialized."); + log_dbg(" ID: " HASH_FMT64 " [%zu bytes].", + HASH_VAL64(dht.id.data), dht.id.len); + log_dbg(" address: " ADDR_FMT32 ".", ADDR_VAL32(&dht.addr)); + log_dbg(" peer: " ADDR_FMT32 ".", ADDR_VAL32(&dht.peer)); + log_dbg(" magic cookie: " HASH_FMT64 ".", HASH_VAL64(&dht.magic)); + log_info(" parameters: alpha=%u, k=%zu, t_expire=%ld, " + "t_refresh=%ld, t_replicate=%ld.", + dht.alpha, dht.k, dht.t_expire, dht.t_refresh, dht.t_repl); +#endif + dht.state = DHT_INIT; + + return 0; + + fail_tpm_create: + rib_unreg(DHT); + fail_rib_reg: + bucket_destroy(dht.db.contacts.root); + fail_buckets: + pthread_rwlock_destroy(&dht.db.lock); + fail_rwlock: + pthread_cond_destroy(&dht.reqs.cond); + fail_reqs_cond: + pthread_condattr_destroy(&cattr); + fail_cattr: + pthread_mutex_destroy(&dht.reqs.mtx); + fail_reqs_mutex: + pthread_cond_destroy(&dht.cmds.cond); + fail_cmds_cond: + pthread_mutex_destroy(&dht.cmds.mtx); + fail_cmds_mutex: + freebuf(dht.id); + fail_id: + return -1; } -void * dht_create(void) +void dht_fini(void) { - struct dht * dht; + struct list_head * p; + struct list_head * h; - dht = malloc(sizeof(*dht)); - if (dht == NULL) - goto fail_malloc; + rib_unreg(DHT); - dht->buckets = NULL; + tpm_destroy(dht.tpm); - list_head_init(&dht->entries); - list_head_init(&dht->requests); - list_head_init(&dht->refs); - list_head_init(&dht->lookups); - list_head_init(&dht->cmds); + pthread_mutex_lock(&dht.cmds.mtx); - if (pthread_rwlock_init(&dht->lock, NULL)) - goto fail_rwlock; + list_for_each_safe(p, h, &dht.cmds.list) { + struct cmd * c = list_entry(p, struct cmd, next); + list_del(&c->next); + freebuf(c->cbuf); + free(c); + } - if (pthread_mutex_init(&dht->mtx, NULL)) - goto fail_mutex; + pthread_mutex_unlock(&dht.cmds.mtx); - if (pthread_cond_init(&dht->cond, NULL)) - goto fail_cond; + pthread_cond_destroy(&dht.cmds.cond); + pthread_mutex_destroy(&dht.cmds.mtx); - dht->cookies = bmp_create(DHT_MAX_REQS, 1); - if (dht->cookies == NULL) - goto fail_bmp; + pthread_mutex_lock(&dht.reqs.mtx); - dht->b = 0; - dht->id = NULL; -#ifndef __DHT_TEST__ - dht->addr = ipcpi.dt_addr; - dht->tpm = tpm_create(2, 1, dht_handle_packet, dht); - if (dht->tpm == NULL) - goto fail_tpm_create; + list_for_each_safe(p, h, &dht.reqs.list) { + struct dht_req * r = list_entry(p, struct dht_req, next); + list_del(&r->next); + dht_req_destroy(r); + dht.reqs.len--; + } - if (tpm_start(dht->tpm)) - goto fail_tpm_start; + pthread_mutex_unlock(&dht.reqs.mtx); - dht->eid = dt_reg_comp(dht, &dht_post_packet, DHT); - if ((int) dht->eid < 0) - goto fail_tpm_start; + pthread_cond_destroy(&dht.reqs.cond); + pthread_mutex_destroy(&dht.reqs.mtx); - notifier_reg(handle_event, dht); -#else - (void) handle_event; - (void) dht_handle_packet; - (void) dht_post_packet; -#endif - dht->state = DHT_INIT; + pthread_rwlock_wrlock(&dht.db.lock); - return (void *) dht; -#ifndef __DHT_TEST__ - fail_tpm_start: - tpm_destroy(dht->tpm); - fail_tpm_create: - bmp_destroy(dht->cookies); -#endif - fail_bmp: - pthread_cond_destroy(&dht->cond); - fail_cond: - pthread_mutex_destroy(&dht->mtx); - fail_mutex: - pthread_rwlock_destroy(&dht->lock); - fail_rwlock: - free(dht); - fail_malloc: - return NULL; + list_for_each_safe(p, h, &dht.db.kv.list) { + struct dht_entry * e = list_entry(p, struct dht_entry, next); + list_del(&e->next); + dht_entry_destroy(e); + dht.db.kv.len--; + } + + if (dht.db.contacts.root != NULL) + bucket_destroy(dht.db.contacts.root); + + pthread_rwlock_unlock(&dht.db.lock); + + pthread_rwlock_destroy(&dht.db.lock); + + assert(dht.db.kv.len == 0); + assert(dht.db.kv.vals == 0); + assert(dht.db.kv.lvals == 0); + assert(dht.reqs.len == 0); + + freebuf(dht.id); } diff --git a/src/ipcpd/unicast/dir/dht.h b/src/ipcpd/unicast/dir/dht.h index a6e9c2c8..852a5130 100644 --- a/src/ipcpd/unicast/dir/dht.h +++ b/src/ipcpd/unicast/dir/dht.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Distributed Hash Table based on Kademlia * @@ -30,22 +30,19 @@ #include <stdint.h> #include <sys/types.h> -void * dht_create(void); +int dht_init(struct dir_dht_config * conf); -void dht_destroy(void * dir); +void dht_fini(void); -int dht_bootstrap(void * dir); +int dht_start(void); -int dht_reg(void * dir, - const uint8_t * key); +void dht_stop(void); -int dht_unreg(void * dir, - const uint8_t * key); +int dht_reg(const uint8_t * key); -uint64_t dht_query(void * dir, - const uint8_t * key); +int dht_unreg(const uint8_t * key); -int dht_wait_running(void * dir); +uint64_t dht_query(const uint8_t * key); extern struct dir_ops dht_dir_ops; diff --git a/src/ipcpd/unicast/dir/dht.proto b/src/ipcpd/unicast/dir/dht.proto new file mode 100644 index 00000000..ea74805f --- /dev/null +++ b/src/ipcpd/unicast/dir/dht.proto @@ -0,0 +1,58 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * DHT protocol, based on Kademlia + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +syntax = "proto2"; + +message dht_contact_msg { + required bytes id = 1; + required uint64 addr = 2; +} + +message dht_find_req_msg { + required uint64 cookie = 1; + required bytes key = 2; +} + +message dht_find_node_rsp_msg { + required uint64 cookie = 1; + required bytes key = 2; + repeated dht_contact_msg contacts = 3; +} + +message dht_find_value_rsp_msg { + repeated bytes values = 1; +} + +message dht_store_msg { + required bytes key = 1; + required bytes val = 2; + required uint32 exp = 3; +} + +message dht_msg { + required uint32 code = 1; + required dht_contact_msg src = 2; + optional dht_store_msg store = 3; + optional dht_find_req_msg find = 4; + optional dht_find_node_rsp_msg node = 5; + optional dht_find_value_rsp_msg val = 6; +} diff --git a/src/ipcpd/unicast/dir/kademlia.proto b/src/ipcpd/unicast/dir/kademlia.proto deleted file mode 100644 index 58f5e787..00000000 --- a/src/ipcpd/unicast/dir/kademlia.proto +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2021 - * - * KAD protocol - * - * Dimitri Staessens <dimitri@ouroboros.rocks> - * Sander Vrijders <sander@ouroboros.rocks> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * version 2.1 as published by the Free Software Foundation. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., http://www.fsf.org/about/contact/. - */ - -syntax = "proto2"; - -message kad_contact_msg { - required bytes id = 1; - required uint64 addr = 2; -}; - -message kad_msg { - required uint32 code = 1; - required uint32 cookie = 2; - required uint64 s_addr = 3; - optional bytes s_id = 4; - optional bytes key = 5; - repeated uint64 addrs = 6; - repeated kad_contact_msg contacts = 7; - // enrolment parameters - optional uint32 alpha = 8; - optional uint32 b = 9; - optional uint32 k = 10; - optional uint32 t_expire = 11; - optional uint32 t_refresh = 12; - optional uint32 t_replicate = 13; -};
\ No newline at end of file diff --git a/src/ipcpd/unicast/dir/ops.h b/src/ipcpd/unicast/dir/ops.h index e74324da..8c6e5eb5 100644 --- a/src/ipcpd/unicast/dir/ops.h +++ b/src/ipcpd/unicast/dir/ops.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Directory policy ops * @@ -23,24 +23,20 @@ #ifndef OUROBOROS_IPCPD_UNICAST_DIR_OPS_H #define OUROBOROS_IPCPD_UNICAST_DIR_OPS_H - struct dir_ops { - void * (* create)(void); + int (* init)(void * config); - void (* destroy)(void * dir); + void (* fini)(void); - int (* bootstrap)(void * dir); + int (* start)(void); - int (* reg)(void * dir, - const uint8_t * hash); + void (* stop)(void); - int (* unreg)(void * dir, - const uint8_t * hash); + int (* reg)(const uint8_t * hash); - uint64_t (* query)(void * dir, - const uint8_t * hash); + int (* unreg)(const uint8_t * hash); - int (* wait_running)(void * dir); + uint64_t (* query)(const uint8_t * hash); }; #endif /* OUROBOROS_IPCPD_UNICAST_DIR_OPS_H */ diff --git a/src/ipcpd/unicast/dir/pol.h b/src/ipcpd/unicast/dir/pol.h index 3aa2d59f..eae4b2e7 100644 --- a/src/ipcpd/unicast/dir/pol.h +++ b/src/ipcpd/unicast/dir/pol.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Directory policies * diff --git a/src/ipcpd/unicast/dir/tests/CMakeLists.txt b/src/ipcpd/unicast/dir/tests/CMakeLists.txt index 482711d5..eded823f 100644 --- a/src/ipcpd/unicast/dir/tests/CMakeLists.txt +++ b/src/ipcpd/unicast/dir/tests/CMakeLists.txt @@ -3,35 +3,36 @@ get_filename_component(CURRENT_SOURCE_PARENT_DIR get_filename_component(CURRENT_BINARY_PARENT_DIR ${CMAKE_CURRENT_BINARY_DIR} DIRECTORY) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) - -include_directories(${CURRENT_SOURCE_PARENT_DIR}) -include_directories(${CURRENT_BINARY_PARENT_DIR}) - -include_directories(${CMAKE_SOURCE_DIR}/include) -include_directories(${CMAKE_BINARY_DIR}/include) - get_filename_component(PARENT_PATH ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) get_filename_component(PARENT_DIR ${PARENT_PATH} NAME) +compute_test_prefix() + create_test_sourcelist(${PARENT_DIR}_tests test_suite.c # Add new tests here dht_test.c - ) +) + +protobuf_generate_c(DHT_PROTO_SRCS KAD_PROTO_HDRS ${CURRENT_SOURCE_PARENT_DIR}/dht.proto) +add_executable(${PARENT_DIR}_test ${${PARENT_DIR}_tests} + ${DHT_PROTO_SRCS}) + +target_include_directories(${PARENT_DIR}_test PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CURRENT_SOURCE_PARENT_DIR} + ${CURRENT_BINARY_PARENT_DIR} + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_BINARY_DIR}/include + ${CMAKE_SOURCE_DIR}/src/ipcpd + ${CMAKE_BINARY_DIR}/src/ipcpd + ${CMAKE_SOURCE_DIR}/src/ipcpd/unicast + ${CMAKE_BINARY_DIR}/src/ipcpd/unicast +) + +disable_test_logging_for_target(${PARENT_DIR}_test) -protobuf_generate_c(KAD_PROTO_SRCS KAD_PROTO_HDRS ../kademlia.proto) - -add_executable(${PARENT_DIR}_test EXCLUDE_FROM_ALL ${${PARENT_DIR}_tests} - ${KAD_PROTO_SRCS}) target_link_libraries(${PARENT_DIR}_test ouroboros-common) +add_dependencies(build_tests ${PARENT_DIR}_test) -add_dependencies(check ${PARENT_DIR}_test) - -set(tests_to_run ${${PARENT_DIR}_tests}) -remove(tests_to_run test_suite.c) - -foreach (test ${tests_to_run}) - get_filename_component(test_name ${test} NAME_WE) - add_test(${test_name} ${C_TEST_PATH}/${PARENT_DIR}_test ${test_name}) -endforeach (test) +ouroboros_register_tests(TARGET ${PARENT_DIR}_test TESTS ${${PARENT_DIR}_tests}) diff --git a/src/ipcpd/unicast/dir/tests/dht_test.c b/src/ipcpd/unicast/dir/tests/dht_test.c index 3f4c3b87..4f0ee00a 100644 --- a/src/ipcpd/unicast/dir/tests/dht_test.c +++ b/src/ipcpd/unicast/dir/tests/dht_test.c @@ -1,10 +1,9 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Unit tests of the DHT * * Dimitri Staessens <dimitri@ouroboros.rocks> - * Sander Vrijders <sander@ouroboros.rocks> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -21,76 +20,1906 @@ */ #define __DHT_TEST__ -#define DHT_TEST_KEY_LEN 32 -#include "dht.c" +#if defined(__linux__) || defined(__CYGWIN__) +#define _DEFAULT_SOURCE +#else +#define _POSIX_C_SOURCE 200112L +#endif + +#include <test/test.h> +#include <ouroboros/list.h> +#include <ouroboros/utils.h> -#include <pthread.h> +#include "dht.pb-c.h" + +#include <assert.h> +#include <inttypes.h> #include <time.h> #include <stdlib.h> #include <stdio.h> -#define CONTACTS 1000 +#define DHT_MAX_RAND_SIZE 64 +#define DHT_TEST_KEY_LEN 32 +#define DHT_TEST_ADDR 0x1234567890abcdefULL -int dht_test(int argc, - char ** argv) +/* forward declare for use in the dht code */ +/* Packet sink for DHT tests */ +struct { + bool enabled; + + struct list_head list; + size_t len; +} sink; + +struct message { + struct list_head next; + void * msg; + uint64_t dst; +}; + +static int sink_send_msg(buffer_t * pkt, + uint64_t addr) { - struct dht * dht; - uint8_t key[DHT_TEST_KEY_LEN]; - size_t i; + struct message * m; - (void) argc; - (void) argv; + assert(pkt != NULL); + assert(addr != 0); + + assert(!list_is_empty(&sink.list) || sink.len == 0); + + if (!sink.enabled) + goto finish; + + m = malloc(sizeof(*m)); + if (m == NULL) { + printf("Failed to malloc message."); + goto fail_malloc; + } + + m->msg = dht_msg__unpack(NULL, pkt->len, pkt->data); + if (m->msg == NULL) + goto fail_unpack; + + m->dst = addr; + + list_add_tail(&m->next, &sink.list); + + ++sink.len; + finish: + freebuf(*pkt); + + return 0; + fail_unpack: + free(m); + fail_malloc: + freebuf(*pkt); + return -1; +} + +#include "dht.c" + +/* Test helpers */ + +static void sink_init(void) +{ + list_head_init(&sink.list); + sink.len = 0; + sink.enabled = true; +} + +static void sink_clear(void) +{ + struct list_head * p; + struct list_head * h; + + list_for_each_safe(p, h, &sink.list) { + struct message * m = list_entry(p, struct message, next); + list_del(&m->next); + dht_msg__free_unpacked((dht_msg_t *) m->msg, NULL); + free(m); + --sink.len; + } + + assert(list_is_empty(&sink.list)); +} + +static void sink_fini(void) +{ + sink_clear(); + + assert(list_is_empty(&sink.list) || sink.len != 0); +} + +static dht_msg_t * sink_read(void) +{ + struct message * m; + dht_msg_t * msg; + + assert(!list_is_empty(&sink.list) || sink.len == 0); + + if (list_is_empty(&sink.list)) + return NULL; + + m = list_first_entry(&sink.list, struct message, next); + + --sink.len; + + list_del(&m->next); + + msg = m->msg; + + free(m); + + return (dht_msg_t *) msg; +} + +static const buffer_t test_val = { + .data = (uint8_t *) "test_value", + .len = 10 +}; + +static const buffer_t test_val2 = { + .data = (uint8_t *) "test_value_2", + .len = 12 +}; + +static int random_value_len(buffer_t * b) +{ + assert(b != NULL); + assert(b->len > 0 && b->len <= DHT_MAX_RAND_SIZE); + + b->data = malloc(b->len); + if (b->data == NULL) + goto fail_malloc; + + random_buffer(b->data, b->len); + + return 0; + + fail_malloc: + return -ENOMEM; +} + +static int random_value(buffer_t * b) +{ + assert(b != NULL); + + b->len = rand() % DHT_MAX_RAND_SIZE + 1; + + return random_value_len(b); +} + +static int fill_dht_with_contacts(size_t n) +{ + size_t i; + uint8_t * id; + + for (i = 0; i < n; i++) { + uint64_t addr = generate_cookie(); + id = generate_id(); + if (id == NULL) + goto fail_id; + + if (dht_kv_update_contacts(id, addr) < 0) + goto fail_update; + free(id); + } + + return 0; + + fail_update: + free(id); + fail_id: + return -1; +} + +static int fill_store_with_random_values(const uint8_t * key, + size_t len, + size_t n_values) +{ + buffer_t val; + struct timespec now; + size_t i; + uint8_t * _key; + + clock_gettime(CLOCK_REALTIME_COARSE, &now); + + for (i = 0; i < n_values; ++i) { + if (key != NULL) + _key = (uint8_t *) key; + else { + _key = generate_id(); + if (_key == NULL) + goto fail_key; + } + + if (len == 0) + val.len = rand() % DHT_MAX_RAND_SIZE + 1; + else + val.len = len; + + if (random_value_len(&val) < 0) + goto fail_value; + + if (dht_kv_store(_key, val, now.tv_sec + 10) < 0) + goto fail_store; + + freebuf(val); + if (key == NULL) + free(_key); + } + + return 0; - dht = dht_create(); - if (dht == NULL) { + fail_store: + freebuf(val); + fail_value: + free(_key); + fail_key: + return -1; +} + +static int random_contact_list(dht_contact_msg_t *** contacts, + size_t max) +{ + size_t i; + + assert(contacts != NULL); + assert(max > 0); + + *contacts = malloc(max * sizeof(**contacts)); + if (*contacts == NULL) + goto fail_malloc; + + for (i = 0; i < max; i++) { + (*contacts)[i] = malloc(sizeof(*(*contacts)[i])); + if ((*contacts)[i] == NULL) + goto fail_contacts; + + dht_contact_msg__init((*contacts)[i]); + + (*contacts)[i]->id.data = generate_id(); + if ((*contacts)[i]->id.data == NULL) + goto fail_contact; + + (*contacts)[i]->id.len = dht.id.len; + (*contacts)[i]->addr = generate_cookie(); + } + + return 0; + + fail_contact: + dht_contact_msg__free_unpacked((*contacts)[i], NULL); + fail_contacts: + while (i-- > 0) + free((*contacts)[i]); + free(*contacts); + fail_malloc: + return -ENOMEM; +} + +static void clear_contacts(dht_contact_msg_t ** contacts, + size_t len) +{ + size_t i; + + assert(contacts != NULL); + if (*contacts == NULL) + return; + + for (i = 0; i < len; ++i) + dht_contact_msg__free_unpacked((contacts)[i], NULL); + + free(*contacts); + *contacts = NULL; +} + +/* Start of actual tests */ +static struct dir_dht_config test_dht_config = { + .params = { + .alpha = 3, + .k = 8, + .t_expire = 86400, + .t_refresh = 900, + .t_replicate = 900 + } +}; + +static int test_dht_init_fini(void) +{ + TEST_START(); + + if (dht_init(&test_dht_config) < 0) { + printf("Failed to create dht.\n"); + goto fail_init; + } + + dht_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_init: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_dht_start_stop(void) +{ + TEST_START(); + + if (dht_init(&test_dht_config) < 0) { + printf("Failed to create dht.\n"); + goto fail_init; + } + + if (dht_start() < 0) { + printf("Failed to start dht.\n"); + goto fail_start; + } + + dht_stop(); + + dht_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_start: + dht_fini(); + fail_init: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_val_entry_create_destroy(void) +{ + struct val_entry * e; + struct timespec now; + + TEST_START(); + + clock_gettime(CLOCK_REALTIME_COARSE, &now); + + if (dht_init(&test_dht_config) < 0) { printf("Failed to create dht.\n"); - return -1; + goto fail_init; } - dht_destroy(dht); + e = val_entry_create(test_val, now.tv_sec + 10); + if (e == NULL) { + printf("Failed to create val entry.\n"); + goto fail_entry; + } + + val_entry_destroy(e); + + dht_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; - dht = dht_create(); - if (dht == NULL) { - printf("Failed to re-create dht.\n"); - return -1; + fail_entry: + dht_fini(); + fail_init: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_dht_entry_create_destroy(void) +{ + struct dht_entry * e; + + TEST_START(); + + if (dht_init(&test_dht_config) < 0) { + printf("Failed to create dht.\n"); + goto fail_init; } - if (dht_bootstrap(dht)) { - printf("Failed to bootstrap dht.\n"); - dht_destroy(dht); - return -1; + e = dht_entry_create(dht.id.data); + if (e == NULL) { + printf("Failed to create dht entry.\n"); + goto fail_entry; } - dht_destroy(dht); + dht_entry_destroy(e); + + dht_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_entry: + dht_fini(); + fail_init: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_dht_entry_update_get_val(void) +{ + struct dht_entry * e; + struct val_entry * v; + struct timespec now; + + TEST_START(); + + clock_gettime(CLOCK_REALTIME_COARSE, &now); + + if (dht_init(&test_dht_config) < 0) { + printf("Failed to create dht.\n"); + goto fail_init; + } + + e = dht_entry_create(dht.id.data); + if (e == NULL) { + printf("Failed to create dht entry.\n"); + goto fail_entry; + } + + if (dht_entry_get_val(e, test_val) != NULL) { + printf("Found value in empty dht entry.\n"); + goto fail_get; + } + + if (dht_entry_update_val(e, test_val, now.tv_sec + 10) < 0) { + printf("Failed to update dht entry value.\n"); + goto fail_get; + } + + if (dht_entry_get_val(e, test_val2) != NULL) { + printf("Found value in dht entry with different key.\n"); + goto fail_get; + } + + v = dht_entry_get_val(e, test_val); + if (v == NULL) { + printf("Failed to get value from dht entry.\n"); + goto fail_get; + } + + if (v->val.len != test_val.len) { + printf("Length in dht entry does not match expected.\n"); + goto fail_get; + } + + if(memcmp(v->val.data, test_val.data, test_val.len) != 0) { + printf("Data in dht entry does not match expected.\n"); + goto fail_get; + } - dht = dht_create(); - if (dht == NULL) { - printf("Failed to re-create dht.\n"); - return -1; + if (dht_entry_update_val(e, test_val, now.tv_sec + 15) < 0) { + printf("Failed to update exsting dht entry value.\n"); + goto fail_get; } - if (dht_bootstrap(dht)) { - printf("Failed to bootstrap dht.\n"); - dht_destroy(dht); - return -1; + if (v->t_exp != now.tv_sec + 15) { + printf("Expiration time in dht entry value not updated.\n"); + goto fail_get; } - for (i = 0; i < CONTACTS; ++i) { - uint64_t addr; - random_buffer(&addr, sizeof(addr)); - random_buffer(key, DHT_TEST_KEY_LEN); - pthread_rwlock_wrlock(&dht->lock); - if (dht_update_bucket(dht, key, addr)) { - pthread_rwlock_unlock(&dht->lock); - printf("Failed to update bucket.\n"); - dht_destroy(dht); - return -1; + if (dht_entry_update_val(e, test_val, now.tv_sec + 5) < 0) { + printf("Failed to update existing dht entry value (5).\n"); + goto fail_get; + } + + if (v->t_exp != now.tv_sec + 15) { + printf("Expiration time in dht entry shortened.\n"); + goto fail_get; + } + + if (dht_entry_get_val(e, test_val) != v) { + printf("Wrong value in dht entry found after update.\n"); + goto fail_get; + } + + dht_entry_destroy(e); + + dht_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_get: + dht_entry_destroy(e); + fail_entry: + dht_fini(); + fail_init: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_dht_entry_update_get_lval(void) +{ + struct dht_entry * e; + struct val_entry * v; + struct timespec now; + + TEST_START(); + + clock_gettime(CLOCK_REALTIME_COARSE, &now); + + if (dht_init(&test_dht_config) < 0) { + printf("Failed to create dht.\n"); + goto fail_init; + } + + e = dht_entry_create(dht.id.data); + if (e == NULL) { + printf("Failed to create dht entry.\n"); + goto fail_entry; + } + + if (dht_entry_get_lval(e, test_val) != NULL) { + printf("Found value in empty dht entry.\n"); + goto fail_get; + } + + if (dht_entry_update_lval(e, test_val) < 0) { + printf("Failed to update dht entry value.\n"); + goto fail_get; + } + + v = dht_entry_get_lval(e, test_val); + if (v== NULL) { + printf("Failed to get value from dht entry.\n"); + goto fail_get; + } + + if (dht_entry_get_lval(e, test_val2) != NULL) { + printf("Found value in dht entry in vals.\n"); + goto fail_get; + } + + if (v->val.len != test_val.len) { + printf("Length in dht entry does not match expected.\n"); + goto fail_get; + } + + if(memcmp(v->val.data, test_val.data, test_val.len) != 0) { + printf("Data in dht entry does not match expected.\n"); + goto fail_get; + } + + if (dht_entry_update_lval(e, test_val) < 0) { + printf("Failed to update existing dht entry value.\n"); + goto fail_get; + } + + if (dht_entry_get_lval(e, test_val) != v) { + printf("Wrong value in dht entry found after update.\n"); + goto fail_get; + } + + dht_entry_destroy(e); + + dht_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_get: + dht_entry_destroy(e); + fail_entry: + dht_fini(); + fail_init: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_dht_kv_contact_create_destroy(void) +{ + struct contact * c; + + TEST_START(); + + if (dht_init(&test_dht_config) < 0) { + printf("Failed to create dht.\n"); + goto fail_init; + } + + c = contact_create(dht.id.data, dht.addr); + if (c == NULL) { + printf("Failed to create contact.\n"); + goto fail_contact; + } + + contact_destroy(c); + + dht_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_contact: + dht_fini(); + fail_init: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_dht_kv_update_bucket(void) +{ + TEST_START(); + + if (dht_init(&test_dht_config) < 0) { + printf("Failed to create dht.\n"); + goto fail_init; + } + + if (fill_dht_with_contacts(1000) < 0) { + printf("Failed to fill bucket with contacts.\n"); + goto fail_update; + } + + dht_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_update: + dht_fini(); + fail_init: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_dht_kv_contact_list(void) +{ + struct list_head cl; + ssize_t len; + ssize_t items; + + TEST_START(); + + list_head_init(&cl); + + if (dht_init(&test_dht_config) < 0) { + printf("Failed to create dht.\n"); + goto fail_init; + } + + items = 5; + + if (fill_dht_with_contacts(items) < 0) { + printf("Failed to fill bucket with contacts.\n"); + goto fail_fill; + } + + len = dht_kv_contact_list(dht.id.data, &cl, dht.k); + if (len < 0) { + printf("Failed to get contact list.\n"); + goto fail_fill; + } + + if (len != items) { + printf("Failed to get contacts (%zu != %zu).\n", len, items); + goto fail_contact_list; + } + + contact_list_destroy(&cl); + + items = 100; + + if (fill_dht_with_contacts(items) < 0) { + printf("Failed to fill bucket with contacts.\n"); + goto fail_fill; + } + + len = dht_kv_contact_list(dht.id.data, &cl, items); + if (len < 0) { + printf("Failed to get contact list.\n"); + goto fail_fill; + } + + if ((size_t) len < dht.k) { + printf("Failed to get contacts (%zu < %zu).\n", len, dht.k); + goto fail_contact_list; + } + + contact_list_destroy(&cl); + + dht_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_contact_list: + contact_list_destroy(&cl); + fail_fill: + dht_fini(); + fail_init: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_dht_kv_get_values(void) +{ + buffer_t * vals; + ssize_t len; + size_t n = sizeof(uint64_t); + + TEST_START(); + + if (dht_init(&test_dht_config) < 0) { + printf("Failed to create dht.\n"); + goto fail_init; + } + + if (fill_store_with_random_values(dht.id.data, n, 3) < 0) { + printf("Failed to fill store with random values.\n"); + goto fail_fill; + } + + len = dht_kv_retrieve(dht.id.data, &vals); + if (len < 0) { + printf("Failed to get values from store.\n"); + goto fail_fill; + } + + if (len != 3) { + printf("Failed to get %ld values (%zu).\n", 3L, len); + goto fail_get_values; + } + + freebufs(vals, len); + + if (fill_store_with_random_values(dht.id.data, n, 20) < 0) { + printf("Failed to fill store with random values.\n"); + goto fail_fill; + } + + len = dht_kv_retrieve(dht.id.data, &vals); + if (len < 0) { + printf("Failed to get values from store.\n"); + goto fail_fill; + } + + if (len != DHT_MAX_VALS) { + printf("Failed to get %d values.\n", DHT_MAX_VALS); + goto fail_get_values; + } + + freebufs(vals, len); + + dht_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_get_values: + freebufs(vals, len); + fail_fill: + dht_fini(); + fail_init: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_dht_kv_find_node_req_msg(void) +{ + dht_msg_t * msg; + dht_msg_t * upk; + size_t len; + uint8_t * buf; + + TEST_START(); + + if (dht_init(&test_dht_config) < 0) { + printf("Failed to create dht.\n"); + goto fail_init; + } + + msg = dht_kv_find_node_req_msg(dht.id.data); + if (msg == NULL) { + printf("Failed to get find node request message.\n"); + goto fail_msg; + } + + if (msg->code != DHT_FIND_NODE_REQ) { + printf("Wrong code in find_node_req message (%s != %s).\n", + dht_code_str[msg->code], + dht_code_str[DHT_FIND_NODE_REQ]); + goto fail_msg; + } + + len = dht_msg__get_packed_size(msg); + if (len == 0) { + printf("Failed to get packed length of find_node_req.\n"); + goto fail_msg; + } + + buf = malloc(len); + if (buf == NULL) { + printf("Failed to malloc find_node_req buf.\n"); + goto fail_msg; + } + + if (dht_msg__pack(msg, buf) != len) { + printf("Failed to pack find_node_req message.\n"); + goto fail_pack; + } + + upk = dht_msg__unpack(NULL, len, buf); + if (upk == NULL) { + printf("Failed to unpack find_value_req message.\n"); + goto fail_unpack; + } + + free(buf); + dht_msg__free_unpacked(msg, NULL); + dht_msg__free_unpacked(upk, NULL); + + dht_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_unpack: + dht_msg__free_unpacked(msg, NULL); + fail_pack: + free(buf); + fail_msg: + dht_fini(); + fail_init: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_dht_kv_find_node_rsp_msg(void) +{ + dht_contact_msg_t ** contacts; + dht_msg_t * msg; + dht_msg_t * upk; + size_t len; + uint8_t * buf; + + TEST_START(); + + if (dht_init(&test_dht_config) < 0) { + printf("Failed to create dht.\n"); + goto fail_init; + } + + msg = dht_kv_find_node_rsp_msg(dht.id.data, 0, &contacts, 0); + if (msg == NULL) { + printf("Failed to get find node response message.\n"); + goto fail_msg; + } + + if (msg->code != DHT_FIND_NODE_RSP) { + printf("Wrong code in find_node_rsp message (%s != %s).\n", + dht_code_str[msg->code], + dht_code_str[DHT_FIND_NODE_RSP]); + goto fail_msg; + } + + len = dht_msg__get_packed_size(msg); + if (len == 0) { + printf("Failed to get packed length of find_node_rsp.\n"); + goto fail_msg; + } + + buf = malloc(len); + if (buf == NULL) { + printf("Failed to malloc find_node_rsp buf.\n"); + goto fail_msg; + } + + if (dht_msg__pack(msg, buf) != len) { + printf("Failed to pack find_node_rsp message.\n"); + goto fail_pack; + } + + upk = dht_msg__unpack(NULL, len, buf); + if (upk == NULL) { + printf("Failed to unpack find_node_rsp message.\n"); + goto fail_unpack; + } + + free(buf); + dht_msg__free_unpacked(msg, NULL); + dht_msg__free_unpacked(upk, NULL); + + dht_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_unpack: + dht_msg__free_unpacked(msg, NULL); + fail_pack: + free(buf); + fail_msg: + dht_fini(); + fail_init: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_dht_kv_find_node_rsp_msg_contacts(void) +{ + dht_contact_msg_t ** contacts; + dht_msg_t * msg; + dht_msg_t * upk; + uint8_t * buf; + size_t len; + ssize_t n; + + TEST_START(); + + if (dht_init(&test_dht_config) < 0) { + printf("Failed to create dht.\n"); + goto fail_init; + } + + if (fill_dht_with_contacts(100) < 0) { + printf("Failed to fill bucket with contacts.\n"); + goto fail_fill; + } + + n = dht_kv_get_contacts(dht.id.data, &contacts); + if (n < 0) { + printf("Failed to get contacts.\n"); + goto fail_fill; + } + + if ((size_t) n < dht.k) { + printf("Failed to get enough contacts (%zu < %zu).\n", n, dht.k); + goto fail_fill; + } + + msg = dht_kv_find_node_rsp_msg(dht.id.data, 0, &contacts, n); + if (msg == NULL) { + printf("Failed to build find node response message.\n"); + goto fail_msg; + } + + len = dht_msg__get_packed_size(msg); + if (len == 0) { + printf("Failed to get packed length of find_node_rsp.\n"); + goto fail_msg; + } + + buf = malloc(len); + if (buf == NULL) { + printf("Failed to malloc find_node_rsp buf.\n"); + goto fail_msg; + } + + if (dht_msg__pack(msg, buf) != len) { + printf("Failed to pack find_node_rsp message.\n"); + goto fail_pack; + } + + upk = dht_msg__unpack(NULL, len, buf); + if (upk == NULL) { + printf("Failed to unpack find_node_rsp message.\n"); + goto fail_unpack; + } + + free(buf); + dht_msg__free_unpacked(msg, NULL); + dht_msg__free_unpacked(upk, NULL); + + dht_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_unpack: + dht_msg__free_unpacked(msg, NULL); + fail_pack: + free(buf); + fail_msg: + clear_contacts(contacts, n); + fail_fill: + dht_fini(); + fail_init: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_dht_kv_find_value_req_msg(void) +{ + dht_msg_t * msg; + dht_msg_t * upk; + size_t len; + uint8_t * buf; + + TEST_START(); + + if (dht_init(&test_dht_config) < 0) { + printf("Failed to create dht.\n"); + goto fail_init; + } + + msg = dht_kv_find_value_req_msg(dht.id.data); + if (msg == NULL) { + printf("Failed to build find value request message.\n"); + goto fail_msg; + } + + if (msg->code != DHT_FIND_VALUE_REQ) { + printf("Wrong code in find_value_req message (%s != %s).\n", + dht_code_str[msg->code], + dht_code_str[DHT_FIND_VALUE_REQ]); + goto fail_msg; + } + + len = dht_msg__get_packed_size(msg); + if (len == 0) { + printf("Failed to get packed length of find_value_req.\n"); + goto fail_msg; + } + + buf = malloc(len); + if (buf == NULL) { + printf("Failed to malloc find_node_req buf.\n"); + goto fail_msg; + } + + if (dht_msg__pack(msg, buf) != len) { + printf("Failed to pack find_value_req message.\n"); + goto fail_pack; + } + + upk = dht_msg__unpack(NULL, len, buf); + if (upk == NULL) { + printf("Failed to unpack find_value_req message.\n"); + goto fail_unpack; + } + + free(buf); + dht_msg__free_unpacked(msg, NULL); + dht_msg__free_unpacked(upk, NULL); + + dht_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_unpack: + dht_msg__free_unpacked(msg, NULL); + fail_pack: + free(buf); + fail_msg: + dht_fini(); + fail_init: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_dht_kv_find_value_rsp_msg(void) +{ + dht_msg_t * msg; + dht_msg_t * upk; + size_t len; + uint8_t * buf; + + TEST_START(); + + if (dht_init(&test_dht_config) < 0) { + printf("Failed to create dht.\n"); + goto fail_init; + } + + msg = dht_kv_find_value_rsp_msg(dht.id.data, 0, NULL, 0, NULL, 0); + if (msg == NULL) { + printf("Failed to build find value response message.\n"); + goto fail_msg; + } + + if (msg->code != DHT_FIND_VALUE_RSP) { + printf("Wrong code in find_value_rsp message (%s != %s).\n", + dht_code_str[msg->code], + dht_code_str[DHT_FIND_VALUE_RSP]); + goto fail_msg; + } + + len = dht_msg__get_packed_size(msg); + if (len == 0) { + printf("Failed to get packed length of find_value_rsp.\n"); + goto fail_msg; + } + + buf = malloc(len); + if (buf == NULL) { + printf("Failed to malloc find_value_rsp buf.\n"); + goto fail_msg; + } + + if (dht_msg__pack(msg, buf) != len) { + printf("Failed to pack find_value_rsp message.\n"); + goto fail_pack; + } + + upk = dht_msg__unpack(NULL, len, buf); + if (upk == NULL) { + printf("Failed to unpack find_value_rsp message.\n"); + goto fail_unpack; + } + + free(buf); + dht_msg__free_unpacked(msg, NULL); + dht_msg__free_unpacked(upk, NULL); + + dht_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_unpack: + dht_msg__free_unpacked(msg, NULL); + fail_pack: + free(buf); + fail_msg: + dht_fini(); + fail_init: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_dht_kv_find_value_rsp_msg_contacts(void) +{ + dht_msg_t * msg; + dht_msg_t * upk; + size_t len; + uint8_t * buf; + dht_contact_msg_t ** contacts; + ssize_t n; + + TEST_START(); + + if (dht_init(&test_dht_config) < 0) { + printf("Failed to create dht.\n"); + goto fail_init; + } + + if (fill_dht_with_contacts(100) < 0) { + printf("Failed to fill bucket with contacts.\n"); + goto fail_fill; + } + + n = dht_kv_get_contacts(dht.id.data, &contacts); + if (n < 0) { + printf("Failed to get contacts.\n"); + goto fail_fill; + } + + if ((size_t) n < dht.k) { + printf("Failed to get enough contacts (%zu < %zu).\n", n, dht.k); + goto fail_fill; + } + + msg = dht_kv_find_value_rsp_msg(dht.id.data, 0, &contacts, n, NULL, 0); + if (msg == NULL) { + printf("Failed to build find value response message.\n"); + goto fail_msg; + } + + len = dht_msg__get_packed_size(msg); + if (len == 0) { + printf("Failed to get packed length of find_value_rsp.\n"); + goto fail_msg; + } + + buf = malloc(len); + if (buf == NULL) { + printf("Failed to malloc find_value_rsp buf.\n"); + goto fail_msg; + } + + if (dht_msg__pack(msg, buf) != len) { + printf("Failed to pack find_value_rsp message.\n"); + goto fail_pack; + } + + upk = dht_msg__unpack(NULL, len, buf); + if (upk == NULL) { + printf("Failed to unpack find_value_rsp message.\n"); + goto fail_unpack; + } + + free(buf); + dht_msg__free_unpacked(msg, NULL); + dht_msg__free_unpacked(upk, NULL); + + dht_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_unpack: + dht_msg__free_unpacked(msg, NULL); + fail_pack: + free(buf); + fail_msg: + clear_contacts(contacts, n); + fail_fill: + dht_fini(); + fail_init: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_dht_kv_find_value_rsp_msg_values(void) +{ + dht_msg_t * msg; + dht_msg_t * upk; + size_t len; + uint8_t * buf; + buffer_t * values; + size_t i; + uint64_t ck; + + TEST_START(); + + ck = generate_cookie(); + + if (dht_init(&test_dht_config) < 0) { + printf("Failed to create dht.\n"); + goto fail_init; + } + + values = malloc(sizeof(*values) * 8); + if (values == NULL) { + printf("Failed to malloc values.\n"); + goto fail_values; + } + + for (i = 0; i < 8; i++) { + if (random_value(&values[i]) < 0) { + printf("Failed to create random value.\n"); + goto fail_fill; } - pthread_rwlock_unlock(&dht->lock); } - dht_destroy(dht); + msg = dht_kv_find_value_rsp_msg(dht.id.data, ck, NULL, 0, &values, 8); + if (msg == NULL) { + printf("Failed to build find value response message.\n"); + goto fail_msg; + } - return 0; + values = NULL; /* msg owns the values now */ + + len = dht_msg__get_packed_size(msg); + if (len == 0) { + printf("Failed to get packed length of find_value_rsp.\n"); + goto fail_msg; + } + + buf = malloc(len); + if (buf == NULL) { + printf("Failed to malloc find_value_rsp buf.\n"); + goto fail_msg; + } + + if (dht_msg__pack(msg, buf) != len) { + printf("Failed to pack find_value_rsp message.\n"); + goto fail_pack; + } + + upk = dht_msg__unpack(NULL, len, buf); + if (upk == NULL) { + printf("Failed to unpack find_value_rsp message.\n"); + goto fail_unpack; + } + + if (upk->code != DHT_FIND_VALUE_RSP) { + printf("Wrong code in find_value_rsp message (%s != %s).\n", + dht_code_str[upk->code], + dht_code_str[DHT_FIND_VALUE_RSP]); + goto fail_unpack; + } + + if (upk->val == NULL) { + printf("No values in find_value_rsp message.\n"); + goto fail_unpack; + } + + if (upk->val->n_values != 8) { + printf("Not enough values in find_value_rsp (%zu != %lu).\n", + upk->val->n_values, 8UL); + goto fail_unpack; + } + + free(buf); + dht_msg__free_unpacked(msg, NULL); + dht_msg__free_unpacked(upk, NULL); + + free(values); + + dht_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_unpack: + dht_msg__free_unpacked(msg, NULL); + fail_pack: + free(buf); + fail_msg: + fail_fill: + while((i--) > 0) + freebuf(values[i]); + free(values); + fail_values: + dht_fini(); + fail_init: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_dht_kv_store_msg(void) +{ + dht_msg_t * msg; + size_t len; + uint8_t * buf; + struct timespec now; + + TEST_START(); + + clock_gettime(CLOCK_REALTIME_COARSE, &now); + + if (dht_init(&test_dht_config) < 0) { + printf("Failed to create dht.\n"); + goto fail_init; + } + + msg = dht_kv_store_msg(dht.id.data, test_val, now.tv_sec + 10); + if (msg == NULL) { + printf("Failed to get store message.\n"); + goto fail_msg; + } + + if (msg->code != DHT_STORE) { + printf("Wrong code in store message (%s != %s).\n", + dht_code_str[msg->code], + dht_code_str[DHT_STORE]); + goto fail_store_msg; + } + + if (dht_kv_validate_msg(msg) < 0) { + printf("Failed to validate store message.\n"); + goto fail_store_msg; + } + + len = dht_msg__get_packed_size(msg); + if (len == 0) { + printf("Failed to get packed msg length.\n"); + goto fail_msg; + } + + buf = malloc(len); + if (buf == NULL) { + printf("Failed to malloc store msg buf.\n"); + goto fail_msg; + } + + if (dht_msg__pack(msg, buf) != len) { + printf("Failed to pack store message.\n"); + goto fail_pack; + } + + free(buf); + + dht_msg__free_unpacked(msg, NULL); + + dht_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_pack: + free(buf); + fail_store_msg: + dht_msg__free_unpacked(msg, NULL); + fail_msg: + dht_fini(); + fail_init: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_dht_kv_query_contacts_req_rsp(void) +{ + dht_msg_t * req; + dht_msg_t * rsp; + dht_contact_msg_t ** contacts; + size_t len = 2; + + uint8_t * key; + + TEST_START(); + + sink_init(); + + if (dht_init(&test_dht_config) < 0) { + printf("Failed to create dht.\n"); + goto fail_init; + } + + if (fill_dht_with_contacts(1) < 0) { + printf("Failed to fill bucket with contacts.\n"); + goto fail_prep; + } + + key = generate_id(); + if (key == NULL) { + printf("Failed to generate key.\n"); + goto fail_prep; + } + + if (dht_kv_query_contacts(key, NULL) < 0) { + printf("Failed to query contacts.\n"); + goto fail_query; + } + + req = sink_read(); + if (req == NULL) { + printf("Failed to read request from sink.\n"); + goto fail_query; + } + + if (dht_kv_validate_msg(req) < 0) { + printf("Failed to validate find node req.\n"); + goto fail_val_req; + } + + if (random_contact_list(&contacts, len) < 0) { + printf("Failed to create random contact.\n"); + goto fail_val_req; + } + + rsp = dht_kv_find_node_rsp_msg(key, req->find->cookie, &contacts, len); + if (rsp == NULL) { + printf("Failed to create find node response message.\n"); + goto fail_rsp; + } + + memcpy(rsp->src->id.data, dht.id.data, dht.id.len); + rsp->src->addr = generate_cookie(); + + if (dht_kv_validate_msg(rsp) < 0) { + printf("Failed to validate find node response message.\n"); + goto fail_val_rsp; + } + + do_dht_kv_find_node_rsp(rsp->node); + + /* dht_contact_msg__free_unpacked(contacts[0], NULL); set to NULL */ + + free(contacts); + + dht_msg__free_unpacked(rsp, NULL); + + free(key); + + dht_msg__free_unpacked(req, NULL); + + sink_fini(); + + dht_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_val_rsp: + dht_msg__free_unpacked(rsp, NULL); + fail_rsp: + while (len-- > 0) + dht_contact_msg__free_unpacked(contacts[len], NULL); + free(contacts); + fail_val_req: + dht_msg__free_unpacked(req, NULL); + fail_query: + free(key); + fail_prep: + dht_fini(); + fail_init: + sink_fini(); + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_dht_req_create_destroy(void) +{ + struct dht_req * req; + + TEST_START(); + + if (dht_init(&test_dht_config) < 0) { + printf("Failed to create dht.\n"); + goto fail_init; + } + + req = dht_req_create(dht.id.data); + if (req == NULL) { + printf("Failed to create kad request.\n"); + goto fail_req; + } + + dht_req_destroy(req); + + dht_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_req: + dht_fini(); + fail_init: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_dht_reg_unreg(void) +{ + TEST_START(); + + sink_init(); + + if (dht_init(&test_dht_config) < 0) { + printf("Failed to create dht.\n"); + goto fail_init; + } + + if (dht_reg(dht.id.data) < 0) { + printf("Failed to register own id.\n"); + goto fail_reg; + } + + if (sink.len != 0) { + printf("Packet sent without contacts!"); + goto fail_msg; + } + + if (dht_unreg(dht.id.data) < 0) { + printf("Failed to unregister own id.\n"); + goto fail_msg; + } + + dht_fini(); + + sink_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_msg: + dht_unreg(dht.id.data); + fail_reg: + dht_fini(); + fail_init: + sink_fini(); + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_dht_reg_unreg_contacts(void) +{ + dht_msg_t * msg; + + TEST_START(); + + sink_init(); + + if (dht_init(&test_dht_config) < 0) { + printf("Failed to create dht.\n"); + goto fail_init; + } + + if (fill_dht_with_contacts(4) < 0) { + printf("Failed to fill bucket with contacts.\n"); + goto fail_reg; + } + + if (dht_reg(dht.id.data) < 0) { + printf("Failed to register own id.\n"); + goto fail_reg; + } + + if (sink.len != dht.alpha) { + printf("Packet sent to too few contacts!\n"); + goto fail_msg; + } + + msg = sink_read(); + if (msg == NULL) { + printf("Failed to read message from sink.\n"); + goto fail_msg; + } + + if (msg->code != DHT_STORE) { + printf("Wrong code in dht reg message (%s != %s).\n", + dht_code_str[msg->code], + dht_code_str[DHT_STORE]); + goto fail_validation; + } + + if (dht_kv_validate_msg(msg) < 0) { + printf("Failed to validate dht message.\n"); + goto fail_validation; + } + + if (dht_unreg(dht.id.data) < 0) { + printf("Failed to unregister own id.\n"); + goto fail_validation; + } + + dht_msg__free_unpacked(msg, NULL); + + dht_fini(); + + sink_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_validation: + dht_msg__free_unpacked(msg, NULL); + fail_msg: + sink_clear(); + dht_unreg(dht.id.data); + fail_reg: + dht_fini(); + fail_init: + sink_fini(); + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_dht_reg_query_local(void) +{ + struct timespec now; + buffer_t test_addr; + + TEST_START(); + + clock_gettime(CLOCK_REALTIME_COARSE, &now); + + if (addr_to_buf(1234321, &test_addr) < 0) { + printf("Failed to convert test address to buffer.\n"); + goto fail_buf; + } + + if (dht_init(&test_dht_config) < 0) { + printf("Failed to create dht.\n"); + goto fail_init; + } + + if (dht_reg(dht.id.data) < 0) { + printf("Failed to register own id.\n"); + goto fail_reg; + } + + if (dht_query(dht.id.data) == dht.addr) { + printf("Succeeded to query own id.\n"); + goto fail_get; + } + + if (dht_kv_store(dht.id.data, test_addr, now.tv_sec + 5) < 0) { + printf("Failed to publish value.\n"); + goto fail_get; + } + + if (dht_query(dht.id.data) != 1234321) { + printf("Failed to return remote addr.\n"); + goto fail_get; + } + + if (dht_unreg(dht.id.data) < 0) { + printf("Failed to unregister own id.\n"); + goto fail_get; + } + + freebuf(test_addr); + + dht_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_get: + dht_unreg(dht.id.data); + fail_reg: + dht_fini(); + fail_init: + freebuf(test_addr); + fail_buf: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_dht_query(void) +{ + uint8_t * key; + struct dir_dht_config cfg; + + TEST_START(); + + sink_init(); + + cfg = test_dht_config; + cfg.peer = generate_cookie(); + + if (dht_init(&cfg)) { + printf("Failed to create dht.\n"); + goto fail_init; + } + + key = generate_id(); + if (key == NULL) { + printf("Failed to generate key.\n"); + goto fail_key; + } + + if (dht_query(key) != INVALID_ADDR) { + printf("Succeeded to get address without contacts.\n"); + goto fail_get; + } + + if (sink.len != 0) { + printf("Packet sent without contacts!"); + goto fail_test; + } + + free(key); + + dht_fini(); + + sink_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_test: + sink_clear(); + fail_get: + free(key); + fail_key: + dht_fini(); + fail_init: + sink_fini(); + return TEST_RC_FAIL; +} + +static int test_dht_query_contacts(void) +{ + dht_msg_t * msg; + uint8_t * key; + struct dir_dht_config cfg; + + + TEST_START(); + + sink_init(); + + cfg = test_dht_config; + cfg.peer = generate_cookie(); + + if (dht_init(&cfg)) { + printf("Failed to create dht.\n"); + goto fail_init; + } + + if (fill_dht_with_contacts(10) < 0) { + printf("Failed to fill with contacts!"); + goto fail_contacts; + } + + key = generate_id(); + if (key == NULL) { + printf("Failed to generate key."); + goto fail_contacts; + } + + if (dht_query(key) != INVALID_ADDR) { + printf("Succeeded to get address for random id.\n"); + goto fail_query; + } + + msg = sink_read(); + if (msg == NULL) { + printf("Failed to read message.!\n"); + goto fail_read; + } + + if (dht_kv_validate_msg(msg) < 0) { + printf("Failed to validate dht message.\n"); + goto fail_msg; + } + + if (msg->code != DHT_FIND_VALUE_REQ) { + printf("Failed to validate dht message.\n"); + goto fail_msg; + } + + dht_msg__free_unpacked(msg, NULL); + + free(key); + + sink_clear(); + + dht_fini(); + + sink_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_msg: + dht_msg__free_unpacked(msg, NULL); + fail_read: + sink_clear(); + fail_query: + free(key); + fail_contacts: + dht_fini(); + fail_init: + sink_fini(); + return TEST_RC_FAIL; +} + +int dht_test(int argc, + char ** argv) +{ + int rc = 0; + + (void) argc; + (void) argv; + + rc |= test_dht_init_fini(); + rc |= test_dht_start_stop(); + rc |= test_val_entry_create_destroy(); + rc |= test_dht_entry_create_destroy(); + rc |= test_dht_entry_update_get_val(); + rc |= test_dht_entry_update_get_lval(); + rc |= test_dht_kv_contact_create_destroy(); + rc |= test_dht_kv_contact_list(); + rc |= test_dht_kv_update_bucket(); + rc |= test_dht_kv_get_values(); + rc |= test_dht_kv_find_node_req_msg(); + rc |= test_dht_kv_find_node_rsp_msg(); + rc |= test_dht_kv_find_node_rsp_msg_contacts(); + rc |= test_dht_kv_query_contacts_req_rsp(); + rc |= test_dht_kv_find_value_req_msg(); + rc |= test_dht_kv_find_value_rsp_msg(); + rc |= test_dht_kv_find_value_rsp_msg_contacts(); + rc |= test_dht_kv_find_value_rsp_msg_values(); + rc |= test_dht_kv_store_msg(); + rc |= test_dht_req_create_destroy(); + rc |= test_dht_reg_unreg(); + rc |= test_dht_reg_unreg_contacts(); + rc |= test_dht_reg_query_local(); + rc |= test_dht_query(); + rc |= test_dht_query_contacts(); + + return rc; } diff --git a/src/ipcpd/unicast/dt.c b/src/ipcpd/unicast/dt.c index 0f504daa..9435350f 100644 --- a/src/ipcpd/unicast/dt.c +++ b/src/ipcpd/unicast/dt.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Data Transfer Component * @@ -41,6 +41,7 @@ #include <ouroboros/fccntl.h> #endif +#include "addr-auth.h" #include "common/comp.h" #include "common/connmgr.h" #include "ca.h" @@ -59,7 +60,7 @@ #include <assert.h> #define QOS_BLOCK_LEN 672 -#define RIB_FILE_STRLEN (189 + QOS_BLOCK_LEN * QOS_CUBE_MAX) +#define RIB_FILE_STRLEN (169 + RIB_TM_STRLEN + QOS_BLOCK_LEN * QOS_CUBE_MAX) #define RIB_NAME_STRLEN 256 #ifndef CLOCK_REALTIME_COARSE @@ -67,7 +68,7 @@ #endif struct comp_info { - void (* post_packet)(void * comp, struct shm_du_buff * sdb); + void (* post_packet)(void * comp, struct ssm_pk_buff * spb); void * comp; char * name; }; @@ -134,16 +135,18 @@ static void dt_pci_des(uint8_t * head, memcpy(&dt_pci->eid, head + dt_pci_info.eid_o, dt_pci_info.eid_size); } -static void dt_pci_shrink(struct shm_du_buff * sdb) +static void dt_pci_shrink(struct ssm_pk_buff * spb) { - assert(sdb); + assert(spb); - shm_du_buff_head_release(sdb, dt_pci_info.head_size); + ssm_pk_buff_head_release(spb, dt_pci_info.head_size); } struct { struct psched * psched; + uint64_t addr; + struct pff * pff[QOS_CUBE_MAX]; struct routing_i * routing[QOS_CUBE_MAX]; #ifdef IPCP_FLOW_STATS @@ -186,7 +189,7 @@ static int dt_rib_read(const char * path, char str[QOS_BLOCK_LEN + 1]; char addrstr[20]; char * entry; - char tmstr[20]; + char tmstr[RIB_TM_STRLEN]; size_t rxqlen = 0; size_t txqlen = 0; struct tm * tm; @@ -209,13 +212,13 @@ static int dt_rib_read(const char * path, return 0; } - if (dt.stat[fd].addr == ipcpi.dt_addr) + if (dt.stat[fd].addr == dt.addr) sprintf(addrstr, "%s", dt.comps[fd].name); else - sprintf(addrstr, "%" PRIu64, dt.stat[fd].addr); + sprintf(addrstr, ADDR_FMT32, ADDR_VAL32(&dt.stat[fd].addr)); - tm = localtime(&dt.stat[fd].stamp); - strftime(tmstr, sizeof(tmstr), "%F %T", tm); + tm = gmtime(&dt.stat[fd].stamp); + strftime(tmstr, sizeof(tmstr), RIB_TM_FORMAT, tm); if (fd >= PROG_RES_FDS) { fccntl(fd, FLOWGRXQLEN, &rxqlen); @@ -223,11 +226,11 @@ static int dt_rib_read(const char * path, } sprintf(buf, - "Flow established at: %20s\n" + "Flow established at: %.*s\n" "Endpoint address: %20s\n" "Queued packets (rx): %20zu\n" "Queued packets (tx): %20zu\n\n", - tmstr, addrstr, rxqlen, txqlen); + RIB_TM_STRLEN - 1, tmstr, addrstr, rxqlen, txqlen); for (i = 0; i < QOS_CUBE_MAX; ++i) { sprintf(str, "Qos cube %3d:\n" @@ -285,48 +288,45 @@ static int dt_rib_readdir(char *** buf) pthread_rwlock_rdlock(&dt.lock); if (dt.n_flows < 1) { - pthread_rwlock_unlock(&dt.lock); - return 0; + *buf = NULL; + goto no_flows; } *buf = malloc(sizeof(**buf) * dt.n_flows); - if (*buf == NULL) { - pthread_rwlock_unlock(&dt.lock); - return -ENOMEM; - } + if (*buf == NULL) + goto fail_entries; for (i = 0; i < PROG_MAX_FLOWS; ++i) { pthread_mutex_lock(&dt.stat[i].lock); if (dt.stat[i].stamp == 0) { pthread_mutex_unlock(&dt.stat[i].lock); - /* Optimization: skip unused res_fds. */ - if (i < PROG_RES_FDS) - i = PROG_RES_FDS; - continue; + break; } + pthread_mutex_unlock(&dt.stat[i].lock); + sprintf(entry, "%zu", i); (*buf)[idx] = malloc(strlen(entry) + 1); - if ((*buf)[idx] == NULL) { - while (idx-- > 0) - free((*buf)[idx]); - free(buf); - pthread_mutex_unlock(&dt.stat[i].lock); - pthread_rwlock_unlock(&dt.lock); - return -ENOMEM; - } + if ((*buf)[idx] == NULL) + goto fail_entry; strcpy((*buf)[idx++], entry); - pthread_mutex_unlock(&dt.stat[i].lock); } - assert((size_t) idx == dt.n_flows); - + no_flows: pthread_rwlock_unlock(&dt.lock); return idx; + + fail_entry: + while (idx-- > 0) + free((*buf)[idx]); + free(*buf); + fail_entries: + pthread_rwlock_unlock(&dt.lock); + return -ENOMEM; #else (void) buf; return 0; @@ -399,6 +399,7 @@ static void handle_event(void * self, const void * o) { struct conn * c; + int fd; (void) self; @@ -406,19 +407,20 @@ static void handle_event(void * self, switch (event) { case NOTIFY_DT_CONN_ADD: + fd = c->flow_info.fd; #ifdef IPCP_FLOW_STATS - stat_used(c->flow_info.fd, c->conn_info.addr); + stat_used(fd, c->conn_info.addr); #endif - psched_add(dt.psched, c->flow_info.fd); - log_dbg("Added fd %d to packet scheduler.", c->flow_info.fd); + psched_add(dt.psched, fd); + log_dbg("Added fd %d to packet scheduler.", fd); break; case NOTIFY_DT_CONN_DEL: + fd = c->flow_info.fd; #ifdef IPCP_FLOW_STATS - stat_used(c->flow_info.fd, INVALID_ADDR); + stat_used(fd, INVALID_ADDR); #endif - psched_del(dt.psched, c->flow_info.fd); - log_dbg("Removed fd %d from " - "packet scheduler.", c->flow_info.fd); + psched_del(dt.psched, fd); + log_dbg("Removed fd %d from packet scheduler.", fd); break; default: break; @@ -427,7 +429,7 @@ static void handle_event(void * self, static void packet_handler(int fd, qoscube_t qc, - struct shm_du_buff * sdb) + struct ssm_pk_buff * spb) { struct dt_pci dt_pci; int ret; @@ -435,7 +437,7 @@ static void packet_handler(int fd, uint8_t * head; size_t len; - len = shm_du_buff_tail(sdb) - shm_du_buff_head(sdb); + len = ssm_pk_buff_len(spb); #ifndef IPCP_FLOW_STATS (void) fd; @@ -449,13 +451,13 @@ static void packet_handler(int fd, #endif memset(&dt_pci, 0, sizeof(dt_pci)); - head = shm_du_buff_head(sdb); + head = ssm_pk_buff_head(spb); dt_pci_des(head, &dt_pci); - if (dt_pci.dst_addr != ipcpi.dt_addr) { + if (dt_pci.dst_addr != dt.addr) { if (dt_pci.ttl == 0) { log_dbg("TTL was zero."); - ipcp_sdb_release(sdb); + ipcp_spb_release(spb); #ifdef IPCP_FLOW_STATS pthread_mutex_lock(&dt.stat[fd].lock); @@ -472,7 +474,7 @@ static void packet_handler(int fd, if (ofd < 0) { log_dbg("No next hop for %" PRIu64 ".", dt_pci.dst_addr); - ipcp_sdb_release(sdb); + ipcp_spb_release(spb); #ifdef IPCP_FLOW_STATS pthread_mutex_lock(&dt.stat[fd].lock); @@ -486,12 +488,12 @@ static void packet_handler(int fd, (void) ca_calc_ecn(ofd, head + dt_pci_info.ecn_o, qc, len); - ret = ipcp_flow_write(ofd, sdb); + ret = ipcp_flow_write(ofd, spb); if (ret < 0) { log_dbg("Failed to write packet to fd %d.", ofd); if (ret == -EFLOWDOWN) notifier_event(NOTIFY_DT_FLOW_DOWN, &ofd); - ipcp_sdb_release(sdb); + ipcp_spb_release(spb); #ifdef IPCP_FLOW_STATS pthread_mutex_lock(&dt.stat[ofd].lock); @@ -511,17 +513,17 @@ static void packet_handler(int fd, pthread_mutex_unlock(&dt.stat[ofd].lock); #endif } else { - dt_pci_shrink(sdb); + dt_pci_shrink(spb); if (dt_pci.eid >= PROG_RES_FDS) { uint8_t ecn = *(head + dt_pci_info.ecn_o); - fa_np1_rcv(dt_pci.eid, ecn, sdb); + fa_np1_rcv(dt_pci.eid, ecn, spb); return; } if (dt.comps[dt_pci.eid].post_packet == NULL) { log_err("No registered component on eid %" PRIu64 ".", dt_pci.eid); - ipcp_sdb_release(sdb); + ipcp_spb_release(spb); return; } #ifdef IPCP_FLOW_STATS @@ -539,7 +541,7 @@ static void packet_handler(int fd, pthread_mutex_unlock(&dt.stat[dt_pci.eid].lock); #endif dt.comps[dt_pci.eid].post_packet(dt.comps[dt_pci.eid].comp, - sdb); + spb); } } @@ -563,33 +565,36 @@ static void * dt_conn_handle(void * o) return 0; } -int dt_init(enum pol_routing pr, - uint8_t addr_size, - uint8_t eid_size, - uint8_t max_ttl) +int dt_init(struct dt_config cfg) { int i; int j; char dtstr[RIB_NAME_STRLEN + 1]; - int pp; + enum pol_pff pp; struct conn_info info; memset(&info, 0, sizeof(info)); + dt.addr = addr_auth_address(); + if (dt.addr == INVALID_ADDR) { + log_err("Failed to get address"); + return -1; + } + strcpy(info.comp_name, DT_COMP); strcpy(info.protocol, DT_PROTO); info.pref_version = 1; info.pref_syntax = PROTO_FIXED; - info.addr = ipcpi.dt_addr; + info.addr = dt.addr; - if (eid_size != 8) { /* only support 64 bits from now */ + if (cfg.eid_size != 8) { /* only support 64 bits from now */ log_warn("Invalid EID size. Only 64 bit is supported."); - eid_size = 8; + cfg.eid_size = 8; } - dt_pci_info.addr_size = addr_size; - dt_pci_info.eid_size = eid_size; - dt_pci_info.max_ttl = max_ttl; + dt_pci_info.addr_size = cfg.addr_size; + dt_pci_info.eid_size = cfg.eid_size; + dt_pci_info.max_ttl = cfg.max_ttl; dt_pci_info.qc_o = dt_pci_info.addr_size; dt_pci_info.ttl_o = dt_pci_info.qc_o + QOS_LEN; @@ -597,18 +602,12 @@ int dt_init(enum pol_routing pr, dt_pci_info.eid_o = dt_pci_info.ecn_o + ECN_LEN; dt_pci_info.head_size = dt_pci_info.eid_o + dt_pci_info.eid_size; - if (notifier_reg(handle_event, NULL)) { - log_err("Failed to register with notifier."); - goto fail_notifier_reg; - } - if (connmgr_comp_init(COMPID_DT, &info)) { log_err("Failed to register with connmgr."); goto fail_connmgr_comp_init; } - pp = routing_init(pr); - if (pp < 0) { + if (routing_init(&cfg.routing, &pp) < 0) { log_err("Failed to init routing."); goto fail_routing; } @@ -645,6 +644,7 @@ int dt_init(enum pol_routing pr, for (i = 0; i < PROG_MAX_FLOWS; ++i) if (pthread_mutex_init(&dt.stat[i].lock, NULL)) { + log_err("Failed to init mutex for flow %d.", i); for (j = 0; j < i; ++j) pthread_mutex_destroy(&dt.stat[j].lock); goto fail_stat_lock; @@ -652,9 +652,11 @@ int dt_init(enum pol_routing pr, dt.n_flows = 0; #endif - sprintf(dtstr, "%s.%" PRIu64, DT, ipcpi.dt_addr); - if (rib_reg(dtstr, &r_ops)) + sprintf(dtstr, "%s." ADDR_FMT32, DT, ADDR_VAL32(&dt.addr)); + if (rib_reg(dtstr, &r_ops)) { + log_err("Failed to register RIB."); goto fail_rib_reg; + } return 0; @@ -678,8 +680,6 @@ int dt_init(enum pol_routing pr, fail_routing: connmgr_comp_fini(COMPID_DT); fail_connmgr_comp_init: - notifier_unreg(&handle_event); - fail_notifier_reg: return -1; } @@ -688,7 +688,7 @@ void dt_fini(void) char dtstr[RIB_NAME_STRLEN + 1]; int i; - sprintf(dtstr, "%s.%" PRIu64, DT, ipcpi.dt_addr); + sprintf(dtstr, "%s.%" PRIu64, DT, dt.addr); rib_unreg(dtstr); #ifdef IPCP_FLOW_STATS for (i = 0; i < PROG_MAX_FLOWS; ++i) @@ -707,47 +707,69 @@ void dt_fini(void) routing_fini(); connmgr_comp_fini(COMPID_DT); - - notifier_unreg(&handle_event); } int dt_start(void) { - dt.psched = psched_create(packet_handler); + dt.psched = psched_create(packet_handler, ipcp_flow_read); if (dt.psched == NULL) { log_err("Failed to create N-1 packet scheduler."); - return -1; + goto fail_psched; + } + + if (notifier_reg(handle_event, NULL)) { + log_err("Failed to register with notifier."); + goto fail_notifier_reg; } if (pthread_create(&dt.listener, NULL, dt_conn_handle, NULL)) { log_err("Failed to create listener thread."); - psched_destroy(dt.psched); - return -1; + goto fail_listener; + } + + if (routing_start() < 0) { + log_err("Failed to start routing."); + goto fail_routing; } return 0; + + fail_routing: + pthread_cancel(dt.listener); + pthread_join(dt.listener, NULL); + fail_listener: + notifier_unreg(&handle_event); + fail_notifier_reg: + psched_destroy(dt.psched); + fail_psched: + return -1; } void dt_stop(void) { + routing_stop(); + pthread_cancel(dt.listener); pthread_join(dt.listener, NULL); + + notifier_unreg(&handle_event); + psched_destroy(dt.psched); } int dt_reg_comp(void * comp, - void (* func)(void * func, struct shm_du_buff *), + void (* func)(void * func, struct ssm_pk_buff *), char * name) { int eid; - assert(func); + assert(func != NULL); pthread_rwlock_wrlock(&dt.lock); eid = bmp_allocate(dt.res_fds); if (!bmp_is_id_valid(dt.res_fds, eid)) { - log_warn("Reserved EIDs depleted."); + log_err("Cannot allocate EID."); pthread_rwlock_unlock(&dt.lock); return -EBADF; } @@ -762,15 +784,32 @@ int dt_reg_comp(void * comp, pthread_rwlock_unlock(&dt.lock); #ifdef IPCP_FLOW_STATS - stat_used(eid, ipcpi.dt_addr); + stat_used(eid, dt.addr); #endif return eid; } +void dt_unreg_comp(int eid) +{ + assert(eid >= 0 && eid < PROG_RES_FDS); + + pthread_rwlock_wrlock(&dt.lock); + + assert(dt.comps[eid].post_packet != NULL); + + dt.comps[eid].post_packet = NULL; + dt.comps[eid].comp = NULL; + dt.comps[eid].name = NULL; + + pthread_rwlock_unlock(&dt.lock); + + return; +} + int dt_write_packet(uint64_t dst_addr, qoscube_t qc, uint64_t eid, - struct shm_du_buff * sdb) + struct ssm_pk_buff * spb) { struct dt_pci dt_pci; int fd; @@ -778,10 +817,10 @@ int dt_write_packet(uint64_t dst_addr, uint8_t * head; size_t len; - assert(sdb); - assert(dst_addr != ipcpi.dt_addr); + assert(spb); + assert(dst_addr != dt.addr); - len = shm_du_buff_tail(sdb) - shm_du_buff_head(sdb); + len = ssm_pk_buff_len(spb); #ifdef IPCP_FLOW_STATS if (eid < PROG_RES_FDS) { @@ -795,7 +834,8 @@ int dt_write_packet(uint64_t dst_addr, #endif fd = pff_nhop(dt.pff[qc], dst_addr); if (fd < 0) { - log_dbg("Could not get nhop for addr %" PRIu64 ".", dst_addr); + log_dbg("Could not get nhop for " ADDR_FMT32 ".", + ADDR_VAL32(&dst_addr)); #ifdef IPCP_FLOW_STATS if (eid < PROG_RES_FDS) { pthread_mutex_lock(&dt.stat[eid].lock); @@ -809,13 +849,13 @@ int dt_write_packet(uint64_t dst_addr, return -EPERM; } - head = shm_du_buff_head_alloc(sdb, dt_pci_info.head_size); + head = ssm_pk_buff_head_alloc(spb, dt_pci_info.head_size); if (head == NULL) { log_dbg("Failed to allocate DT header."); goto fail_write; } - len = shm_du_buff_tail(sdb) - shm_du_buff_head(sdb); + len = ssm_pk_buff_len(spb); dt_pci.dst_addr = dst_addr; dt_pci.qc = qc; @@ -826,7 +866,7 @@ int dt_write_packet(uint64_t dst_addr, dt_pci_ser(head, &dt_pci); - ret = ipcp_flow_write(fd, sdb); + ret = ipcp_flow_write(fd, spb); if (ret < 0) { log_dbg("Failed to write packet to fd %d.", fd); if (ret == -EFLOWDOWN) diff --git a/src/ipcpd/unicast/dt.h b/src/ipcpd/unicast/dt.h index e1abbe28..2e713700 100644 --- a/src/ipcpd/unicast/dt.h +++ b/src/ipcpd/unicast/dt.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Data Transfer component * @@ -25,17 +25,13 @@ #include <ouroboros/ipcp.h> #include <ouroboros/qoscube.h> -#include <ouroboros/shm_rdrbuff.h> +#include <ouroboros/ssm_pool.h> #define DT_COMP "Data Transfer" #define DT_PROTO "dtp" #define INVALID_ADDR 0 -int dt_init(enum pol_routing pr, - uint8_t addr_size, - uint8_t eid_size, - uint8_t max_ttl -); +int dt_init(struct dt_config cfg); void dt_fini(void); @@ -43,13 +39,15 @@ int dt_start(void); void dt_stop(void); -int dt_reg_comp(void * comp, - void (* func)(void * comp, struct shm_du_buff * sdb), - char * name); +int dt_reg_comp(void * comp, + void (* func)(void * comp, struct ssm_pk_buff * spb), + char * name); + +void dt_unreg_comp(int eid); int dt_write_packet(uint64_t dst_addr, qoscube_t qc, uint64_t eid, - struct shm_du_buff * sdb); + struct ssm_pk_buff * spb); #endif /* OUROBOROS_IPCPD_UNICAST_DT_H */ diff --git a/src/ipcpd/unicast/enroll.c b/src/ipcpd/unicast/enroll.c deleted file mode 100644 index 500a3895..00000000 --- a/src/ipcpd/unicast/enroll.c +++ /dev/null @@ -1,3 +0,0 @@ -#define BUILD_IPCP_UNICAST - -#include "common/enroll.c" diff --git a/src/ipcpd/unicast/fa.c b/src/ipcpd/unicast/fa.c index 6e6d52f0..07f5b46c 100644 --- a/src/ipcpd/unicast/fa.c +++ b/src/ipcpd/unicast/fa.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Flow allocator of the IPC Process * @@ -31,6 +31,7 @@ #define FA "flow-allocator" #define OUROBOROS_PREFIX FA +#include <ouroboros/endian.h> #include <ouroboros/logs.h> #include <ouroboros/fqueue.h> #include <ouroboros/errno.h> @@ -40,12 +41,14 @@ #include <ouroboros/random.h> #include <ouroboros/pthread.h> +#include "addr-auth.h" #include "dir.h" #include "fa.h" #include "psched.h" #include "ipcp.h" #include "dt.h" #include "ca.h" +#include "np1.h" #include <inttypes.h> #include <stdlib.h> @@ -55,7 +58,7 @@ #define CLOCK_REALTIME_COARSE CLOCK_REALTIME #endif -#define TIMEOUT 10000 /* nanoseconds */ +#define TIMEOUT 10 * MILLION /* nanoseconds */ #define FLOW_REQ 0 #define FLOW_REPLY 1 @@ -68,23 +71,22 @@ struct fa_msg { uint64_t s_addr; uint64_t r_eid; uint64_t s_eid; - uint8_t code; - int8_t response; - uint16_t ece; - /* QoS parameters from spec, aligned */ - uint8_t availability; - uint8_t in_order; - uint32_t delay; uint64_t bandwidth; + int32_t response; + uint32_t delay; uint32_t loss; uint32_t ber; uint32_t max_gap; - uint16_t cypher_s; + uint32_t timeout; + uint16_t ece; + uint8_t code; + uint8_t availability; + uint8_t in_order; } __attribute__((packed)); struct cmd { struct list_head next; - struct shm_du_buff * sdb; + struct ssm_pk_buff * spb; }; struct fa_flow { @@ -133,7 +135,7 @@ static int fa_rib_read(const char * path, char r_addrstr[21]; char s_eidstr[21]; char r_eidstr[21]; - char tmstr[20]; + char tmstr[RIB_TM_STRLEN]; char castr[1024]; char * entry; struct tm * tm; @@ -143,7 +145,7 @@ static int fa_rib_read(const char * path, fd = atoi(entry); - if (fd < 0 || fd > PROG_MAX_FLOWS) + if (fd < 0 || fd >= PROG_MAX_FLOWS) return -1; if (len < 1536) @@ -164,8 +166,8 @@ static int fa_rib_read(const char * path, sprintf(s_eidstr, "%" PRIu64, flow->s_eid); sprintf(r_eidstr, "%" PRIu64, flow->r_eid); - tm = localtime(&flow->stamp); - strftime(tmstr, sizeof(tmstr), "%F %T", tm); + tm = gmtime(&flow->stamp); + strftime(tmstr, sizeof(tmstr), RIB_TM_FORMAT, tm); ca_print_stats(flow->ctx, castr, 1024); @@ -215,15 +217,13 @@ static int fa_rib_readdir(char *** buf) pthread_rwlock_rdlock(&fa.flows_lock); if (fa.n_flows < 1) { - pthread_rwlock_unlock(&fa.flows_lock); - return 0; + *buf = NULL; + goto no_flows; } *buf = malloc(sizeof(**buf) * fa.n_flows); - if (*buf == NULL) { - pthread_rwlock_unlock(&fa.flows_lock); - return -ENOMEM; - } + if (*buf == NULL) + goto fail_entries; for (i = 0; i < PROG_MAX_FLOWS; ++i) { struct fa_flow * flow; @@ -235,22 +235,25 @@ static int fa_rib_readdir(char *** buf) sprintf(entry, "%zu", i); (*buf)[idx] = malloc(strlen(entry) + 1); - if ((*buf)[idx] == NULL) { - while (idx-- > 0) - free((*buf)[idx]); - free(buf); - pthread_rwlock_unlock(&fa.flows_lock); - return -ENOMEM; - } + if ((*buf)[idx] == NULL) + goto fail_entry; strcpy((*buf)[idx++], entry); } assert((size_t) idx == fa.n_flows); - + no_flows: pthread_rwlock_unlock(&fa.flows_lock); return idx; + + fail_entry: + while (idx-- > 0) + free((*buf)[idx]); + free(*buf); + fail_entries: + pthread_rwlock_unlock(&fa.flows_lock); + return -ENOMEM; #else (void) buf; return 0; @@ -303,7 +306,7 @@ static int eid_to_fd(uint64_t eid) fd = eid & 0xFFFFFFFF; - if (fd < 0 || fd > PROG_MAX_FLOWS) + if (fd < 0 || fd >= PROG_MAX_FLOWS) return -1; flow = &fa.flows[fd]; @@ -328,7 +331,7 @@ static uint64_t gen_eid(int fd) static void packet_handler(int fd, qoscube_t qc, - struct shm_du_buff * sdb) + struct ssm_pk_buff * spb) { struct fa_flow * flow; uint64_t r_addr; @@ -340,7 +343,7 @@ static void packet_handler(int fd, pthread_rwlock_wrlock(&fa.flows_lock); - len = shm_du_buff_tail(sdb) - shm_du_buff_head(sdb); + len = ssm_pk_buff_len(spb); #ifdef IPCP_FLOW_STATS ++flow->p_snd; @@ -355,9 +358,9 @@ static void packet_handler(int fd, ca_wnd_wait(wnd); - if (dt_write_packet(r_addr, qc, r_eid, sdb)) { - ipcp_sdb_release(sdb); - log_warn("Failed to forward packet."); + if (dt_write_packet(r_addr, qc, r_eid, spb)) { + ipcp_spb_release(spb); + log_dbg("Failed to forward packet."); #ifdef IPCP_FLOW_STATS pthread_rwlock_wrlock(&fa.flows_lock); ++flow->p_snd_f; @@ -409,7 +412,7 @@ static void fa_flow_fini(struct fa_flow * flow) } static void fa_post_packet(void * comp, - struct shm_du_buff * sdb) + struct ssm_pk_buff * spb) { struct cmd * cmd; @@ -420,11 +423,11 @@ static void fa_post_packet(void * comp, cmd = malloc(sizeof(*cmd)); if (cmd == NULL) { log_err("Command failed. Out of memory."); - ipcp_sdb_release(sdb); + ipcp_spb_release(spb); return; } - cmd->sdb = sdb; + cmd->spb = spb; pthread_mutex_lock(&fa.mtx); @@ -435,167 +438,194 @@ static void fa_post_packet(void * comp, pthread_mutex_unlock(&fa.mtx); } -static void * fa_handle_packet(void * o) +static size_t fa_wait_for_fa_msg(struct fa_msg * msg) { - struct timespec ts = {0, TIMEOUT * 1000}; + struct cmd * cmd; + size_t len; - (void) o; + pthread_mutex_lock(&fa.mtx); - while (true) { - struct timespec abstime; - int fd; - uint8_t buf[MSGBUFSZ]; - struct fa_msg * msg; - qosspec_t qs; - struct cmd * cmd; - size_t len; - size_t msg_len; - struct fa_flow * flow; + pthread_cleanup_push(__cleanup_mutex_unlock, &fa.mtx); - pthread_mutex_lock(&fa.mtx); + while (list_is_empty(&fa.cmds)) + pthread_cond_wait(&fa.cond, &fa.mtx); - pthread_cleanup_push(__cleanup_mutex_unlock, &fa.mtx); + cmd = list_last_entry(&fa.cmds, struct cmd, next); + list_del(&cmd->next); - while (list_is_empty(&fa.cmds)) - pthread_cond_wait(&fa.cond, &fa.mtx); + pthread_cleanup_pop(true); - cmd = list_last_entry(&fa.cmds, struct cmd, next); - list_del(&cmd->next); + len = ssm_pk_buff_len(cmd->spb); + if (len > MSGBUFSZ || len < sizeof(*msg)) { + log_warn("Invalid flow allocation message (len: %zd).", len); + free(cmd); + return 0; /* No valid message */ + } - pthread_cleanup_pop(true); + memcpy(msg, ssm_pk_buff_head(cmd->spb), len); - len = shm_du_buff_tail(cmd->sdb) - shm_du_buff_head(cmd->sdb); + ipcp_spb_release(cmd->spb); - if (len > MSGBUFSZ) { - log_err("Message over buffer size."); - free(cmd); - continue; - } + free(cmd); - msg = (struct fa_msg *) buf; + return len; +} - /* Depending on the message call the function in ipcp-dev.h */ +static int fa_handle_flow_req(struct fa_msg * msg, + size_t len) +{ + size_t msg_len; + int fd; + qosspec_t qs; + struct fa_flow * flow; + uint8_t * dst; + buffer_t data; /* Piggbacked data on flow alloc request. */ - memcpy(msg, shm_du_buff_head(cmd->sdb), len); + msg_len = sizeof(*msg) + ipcp_dir_hash_len(); + if (len < msg_len) { + log_err("Invalid flow allocation request"); + return -EPERM; + } - ipcp_sdb_release(cmd->sdb); + dst = (uint8_t *)(msg + 1); + data.data = (uint8_t *) msg + msg_len; + data.len = len - msg_len; + + qs.delay = ntoh32(msg->delay); + qs.bandwidth = ntoh64(msg->bandwidth); + qs.availability = msg->availability; + qs.loss = ntoh32(msg->loss); + qs.ber = ntoh32(msg->ber); + qs.in_order = msg->in_order; + qs.max_gap = ntoh32(msg->max_gap); + qs.timeout = ntoh32(msg->timeout); + + fd = ipcp_wait_flow_req_arr(dst, qs, IPCP_UNICAST_MPL, &data); + if (fd < 0) + return fd; - free(cmd); + flow = &fa.flows[fd]; - switch (msg->code) { - case FLOW_REQ: - msg_len = sizeof(*msg) + ipcp_dir_hash_len(); + pthread_rwlock_wrlock(&fa.flows_lock); - assert(len >= msg_len); + fa_flow_init(flow); - clock_gettime(PTHREAD_COND_CLOCK, &abstime); + flow->s_eid = gen_eid(fd); + flow->r_eid = ntoh64(msg->s_eid); + flow->r_addr = ntoh64(msg->s_addr); - pthread_mutex_lock(&ipcpi.alloc_lock); + pthread_rwlock_unlock(&fa.flows_lock); - while (ipcpi.alloc_id != -1 && - ipcp_get_state() == IPCP_OPERATIONAL) { - ts_add(&abstime, &ts, &abstime); - pthread_cond_timedwait(&ipcpi.alloc_cond, - &ipcpi.alloc_lock, - &abstime); - } + return fd; +} - if (ipcp_get_state() != IPCP_OPERATIONAL) { - pthread_mutex_unlock(&ipcpi.alloc_lock); - log_dbg("Won't allocate over non-operational" - "IPCP."); - continue; - } +static int fa_handle_flow_reply(struct fa_msg * msg, + size_t len) +{ + int fd; + struct fa_flow * flow; + buffer_t data; /* Piggbacked data on flow alloc request. */ + time_t mpl = IPCP_UNICAST_MPL; + int response; - assert(ipcpi.alloc_id == -1); + assert(len >= sizeof(*msg)); - qs.delay = ntoh32(msg->delay); - qs.bandwidth = ntoh64(msg->bandwidth); - qs.availability = msg->availability; - qs.loss = ntoh32(msg->loss); - qs.ber = ntoh32(msg->ber); - qs.in_order = msg->in_order; - qs.max_gap = ntoh32(msg->max_gap); - qs.cypher_s = ntoh16(msg->cypher_s); + data.data = (uint8_t *) msg + sizeof(*msg); + data.len = len - sizeof(*msg); - fd = ipcp_flow_req_arr((uint8_t *) (msg + 1), - ipcp_dir_hash_len(), - qs, - buf + msg_len, - len - msg_len); - if (fd < 0) { - pthread_mutex_unlock(&ipcpi.alloc_lock); - log_err("Failed to get fd for flow."); - continue; - } + pthread_rwlock_wrlock(&fa.flows_lock); - flow = &fa.flows[fd]; + fd = eid_to_fd(ntoh64(msg->r_eid)); + if (fd < 0) { + pthread_rwlock_unlock(&fa.flows_lock); + log_err("Flow reply for unknown EID %" PRIu64 ".", + ntoh64(msg->r_eid)); + return -ENOTALLOC; + } - pthread_rwlock_wrlock(&fa.flows_lock); + flow = &fa.flows[fd]; - fa_flow_init(flow); + flow->r_eid = ntoh64(msg->s_eid); + response = ntoh32(msg->response); - flow->s_eid = gen_eid(fd); - flow->r_eid = ntoh64(msg->s_eid); - flow->r_addr = ntoh64(msg->s_addr); + log_dbg("IPCP received msg response %d for flow on fd %d.", + response, fd); - pthread_rwlock_unlock(&fa.flows_lock); + if (response < 0) + fa_flow_fini(flow); + else + psched_add(fa.psched, fd); - ipcpi.alloc_id = fd; - pthread_cond_broadcast(&ipcpi.alloc_cond); + pthread_rwlock_unlock(&fa.flows_lock); - pthread_mutex_unlock(&ipcpi.alloc_lock); + if (ipcp_flow_alloc_reply(fd, response, mpl, &data) < 0) { + log_err("Failed to reply for flow allocation on fd %d.", fd); + return -EIRMD; + } - break; - case FLOW_REPLY: - assert(len >= sizeof(*msg)); + return 0; +} - pthread_rwlock_wrlock(&fa.flows_lock); +static int fa_handle_flow_update(struct fa_msg * msg, + size_t len) +{ + struct fa_flow * flow; + int fd; - fd = eid_to_fd(ntoh64(msg->r_eid)); - if (fd < 0) { - pthread_rwlock_unlock(&fa.flows_lock); - break; - } + (void) len; + assert(len >= sizeof(*msg)); - flow = &fa.flows[fd]; + pthread_rwlock_wrlock(&fa.flows_lock); - flow->r_eid = ntoh64(msg->s_eid); + fd = eid_to_fd(ntoh64(msg->r_eid)); + if (fd < 0) { + pthread_rwlock_unlock(&fa.flows_lock); + log_err("Flow update for unknown EID %" PRIu64 ".", + ntoh64(msg->r_eid)); + return -EPERM; + } - if (msg->response < 0) - fa_flow_fini(flow); - else - psched_add(fa.psched, fd); + flow = &fa.flows[fd]; +#ifdef IPCP_FLOW_STATS + flow->u_rcv++; +#endif + ca_ctx_update_ece(flow->ctx, ntoh16(msg->ece)); - pthread_rwlock_unlock(&fa.flows_lock); + pthread_rwlock_unlock(&fa.flows_lock); - ipcp_flow_alloc_reply(fd, - msg->response, - buf + sizeof(*msg), - len - sizeof(*msg)); - break; - case FLOW_UPDATE: - assert(len >= sizeof(*msg)); + return 0; +} - pthread_rwlock_wrlock(&fa.flows_lock); +static void * fa_handle_packet(void * o) +{ + (void) o; - fd = eid_to_fd(ntoh64(msg->r_eid)); - if (fd < 0) { - pthread_rwlock_unlock(&fa.flows_lock); - break; - } + while (true) { + uint8_t buf[MSGBUFSZ]; + struct fa_msg * msg; + size_t len; - flow = &fa.flows[fd]; -#ifdef IPCP_FLOW_STATS - flow->u_rcv++; -#endif - ca_ctx_update_ece(flow->ctx, ntoh16(msg->ece)); + msg = (struct fa_msg *) buf; - pthread_rwlock_unlock(&fa.flows_lock); + len = fa_wait_for_fa_msg(msg); + if (len == 0) + continue; + switch (msg->code) { + case FLOW_REQ: + if (fa_handle_flow_req(msg, len) < 0) + log_err("Error handling flow alloc request."); + break; + case FLOW_REPLY: + if (fa_handle_flow_reply(msg, len) < 0) + log_err("Error handling flow reply."); + break; + case FLOW_UPDATE: + if (fa_handle_flow_update(msg, len) < 0) + log_err("Error handling flow update."); break; default: - log_err("Got an unknown flow allocation message."); + log_warn("Recieved unknown flow allocation message."); break; } } @@ -622,19 +652,21 @@ int fa_init(void) if (pthread_cond_init(&fa.cond, &cattr)) goto fail_cond; - pthread_condattr_destroy(&cattr); - - list_head_init(&fa.cmds); - if (rib_reg(FA, &r_ops)) goto fail_rib_reg; fa.eid = dt_reg_comp(&fa, &fa_post_packet, FA); if ((int) fa.eid < 0) - goto fail_rib_reg; + goto fail_dt_reg; + + list_head_init(&fa.cmds); + + pthread_condattr_destroy(&cattr); return 0; + fail_dt_reg: + rib_unreg(FA); fail_rib_reg: pthread_cond_destroy(&fa.cond); fail_cond: @@ -644,7 +676,6 @@ int fa_init(void) fail_mtx: pthread_rwlock_destroy(&fa.flows_lock); fail_rwlock: - log_err("Failed to initialize flow allocator."); return -1; } @@ -657,13 +688,21 @@ void fa_fini(void) pthread_rwlock_destroy(&fa.flows_lock); } +static int np1_flow_read_fa(int fd, + struct ssm_pk_buff ** spb) +{ + return np1_flow_read(fd, spb, NP1_GET_POOL(fd)); +} + int fa_start(void) { +#ifndef BUILD_CONTAINER struct sched_param par; int pol; int max; +#endif - fa.psched = psched_create(packet_handler); + fa.psched = psched_create(packet_handler, np1_flow_read_fa); if (fa.psched == NULL) { log_err("Failed to start packet scheduler."); goto fail_psched; @@ -674,6 +713,7 @@ int fa_start(void) goto fail_thread; } +#ifndef BUILD_CONTAINER if (pthread_getschedparam(fa.worker, &pol, &par)) { log_err("Failed to get worker thread scheduling parameters."); goto fail_sched; @@ -691,16 +731,18 @@ int fa_start(void) log_err("Failed to set scheduler priority to maximum."); goto fail_sched; } +#endif return 0; +#ifndef BUILD_CONTAINER fail_sched: pthread_cancel(fa.worker); pthread_join(fa.worker, NULL); +#endif fail_thread: psched_destroy(fa.psched); fail_psched: - log_err("Failed to start flow allocator."); return -1; } @@ -712,14 +754,13 @@ void fa_stop(void) psched_destroy(fa.psched); } -int fa_alloc(int fd, - const uint8_t * dst, - qosspec_t qs, - const void * data, - size_t dlen) +int fa_alloc(int fd, + const uint8_t * dst, + qosspec_t qs, + const buffer_t * data) { struct fa_msg * msg; - struct shm_du_buff * sdb; + struct ssm_pk_buff * spb; struct fa_flow * flow; uint64_t addr; qoscube_t qc = QOS_CUBE_BE; @@ -732,17 +773,17 @@ int fa_alloc(int fd, len = sizeof(*msg) + ipcp_dir_hash_len(); - if (ipcp_sdb_reserve(&sdb, len + dlen)) + if (ipcp_spb_reserve(&spb, len + data->len)) return -1; - msg = (struct fa_msg *) shm_du_buff_head(sdb); + msg = (struct fa_msg *) ssm_pk_buff_head(spb); memset(msg, 0, sizeof(*msg)); eid = gen_eid(fd); msg->code = FLOW_REQ; msg->s_eid = hton64(eid); - msg->s_addr = hton64(ipcpi.dt_addr); + msg->s_addr = hton64(addr_auth_address()); msg->delay = hton32(qs.delay); msg->bandwidth = hton64(qs.bandwidth); msg->availability = qs.availability; @@ -750,13 +791,15 @@ int fa_alloc(int fd, msg->ber = hton32(qs.ber); msg->in_order = qs.in_order; msg->max_gap = hton32(qs.max_gap); - msg->cypher_s = hton16(qs.cypher_s); + msg->timeout = hton32(qs.timeout); memcpy(msg + 1, dst, ipcp_dir_hash_len()); - memcpy(shm_du_buff_head(sdb) + len, data, dlen); + if (data->len > 0) + memcpy(ssm_pk_buff_head(spb) + len, data->data, data->len); - if (dt_write_packet(addr, qc, fa.eid, sdb)) { - ipcp_sdb_release(sdb); + if (dt_write_packet(addr, qc, fa.eid, spb)) { + log_err("Failed to send flow allocation request packet."); + ipcp_spb_release(spb); return -1; } @@ -773,75 +816,66 @@ int fa_alloc(int fd, return 0; } -int fa_alloc_resp(int fd, - int response, - const void * data, - size_t len) +int fa_alloc_resp(int fd, + int response, + const buffer_t * data) { - struct timespec ts = {0, TIMEOUT * 1000}; - struct timespec abstime; struct fa_msg * msg; - struct shm_du_buff * sdb; + struct ssm_pk_buff * spb; struct fa_flow * flow; qoscube_t qc = QOS_CUBE_BE; - clock_gettime(PTHREAD_COND_CLOCK, &abstime); - flow = &fa.flows[fd]; - pthread_mutex_lock(&ipcpi.alloc_lock); - - while (ipcpi.alloc_id != fd && ipcp_get_state() == IPCP_OPERATIONAL) { - ts_add(&abstime, &ts, &abstime); - pthread_cond_timedwait(&ipcpi.alloc_cond, - &ipcpi.alloc_lock, - &abstime); + if (ipcp_wait_flow_resp(fd) < 0) { + log_err("Failed to wait for flow response."); + goto fail_alloc_resp; } - if (ipcp_get_state() != IPCP_OPERATIONAL) { - pthread_mutex_unlock(&ipcpi.alloc_lock); - return -1; + if (ipcp_spb_reserve(&spb, sizeof(*msg) + data->len)) { + log_err("Failed to reserve spb (%zu bytes).", + sizeof(*msg) + data->len); + goto fail_reserve; } - ipcpi.alloc_id = -1; - pthread_cond_broadcast(&ipcpi.alloc_cond); - - pthread_mutex_unlock(&ipcpi.alloc_lock); - - if (ipcp_sdb_reserve(&sdb, sizeof(*msg) + len)) { - fa_flow_fini(flow); - return -1; - } - - msg = (struct fa_msg *) shm_du_buff_head(sdb); + msg = (struct fa_msg *) ssm_pk_buff_head(spb); memset(msg, 0, sizeof(*msg)); - pthread_rwlock_wrlock(&fa.flows_lock); - msg->code = FLOW_REPLY; + msg->response = hton32(response); + if (data->len > 0) + memcpy(msg + 1, data->data, data->len); + + pthread_rwlock_rdlock(&fa.flows_lock); + msg->r_eid = hton64(flow->r_eid); msg->s_eid = hton64(flow->s_eid); - msg->response = response; - memcpy(msg + 1, data, len); + pthread_rwlock_unlock(&fa.flows_lock); + + if (dt_write_packet(flow->r_addr, qc, fa.eid, spb)) { + log_err("Failed to send flow allocation response packet."); + goto fail_packet; + } if (response < 0) { + pthread_rwlock_wrlock(&fa.flows_lock); fa_flow_fini(flow); - ipcp_sdb_release(sdb); + pthread_rwlock_unlock(&fa.flows_lock); } else { psched_add(fa.psched, fd); } - if (dt_write_packet(flow->r_addr, qc, fa.eid, sdb)) { - fa_flow_fini(flow); - pthread_rwlock_unlock(&fa.flows_lock); - ipcp_sdb_release(sdb); - return -1; - } + return 0; + fail_packet: + ipcp_spb_release(spb); + fail_reserve: + pthread_rwlock_wrlock(&fa.flows_lock); + fa_flow_fini(flow); pthread_rwlock_unlock(&fa.flows_lock); - - return 0; + fail_alloc_resp: + return -1; } int fa_dealloc(int fd) @@ -857,7 +891,7 @@ int fa_dealloc(int fd) pthread_rwlock_unlock(&fa.flows_lock); - flow_dealloc(fd); + ipcp_flow_dealloc(fd); return 0; } @@ -866,16 +900,17 @@ static int fa_update_remote(int fd, uint16_t ece) { struct fa_msg * msg; - struct shm_du_buff * sdb; + struct ssm_pk_buff * spb; qoscube_t qc = QOS_CUBE_BE; struct fa_flow * flow; uint64_t r_addr; - if (ipcp_sdb_reserve(&sdb, sizeof(*msg))) { + if (ipcp_spb_reserve(&spb, sizeof(*msg))) { + log_err("Failed to reserve spb (%zu bytes).", sizeof(*msg)); return -1; } - msg = (struct fa_msg *) shm_du_buff_head(sdb); + msg = (struct fa_msg *) ssm_pk_buff_head(spb); memset(msg, 0, sizeof(*msg)); @@ -894,8 +929,9 @@ static int fa_update_remote(int fd, pthread_rwlock_unlock(&fa.flows_lock); - if (dt_write_packet(r_addr, qc, fa.eid, sdb)) { - ipcp_sdb_release(sdb); + if (dt_write_packet(r_addr, qc, fa.eid, spb)) { + log_err("Failed to send flow update packet."); + ipcp_spb_release(spb); return -1; } @@ -904,7 +940,7 @@ static int fa_update_remote(int fd, void fa_np1_rcv(uint64_t eid, uint8_t ecn, - struct shm_du_buff * sdb) + struct ssm_pk_buff * spb) { struct fa_flow * flow; bool update; @@ -912,14 +948,15 @@ void fa_np1_rcv(uint64_t eid, int fd; size_t len; - len = shm_du_buff_tail(sdb) - shm_du_buff_head(sdb); + len = ssm_pk_buff_len(spb); pthread_rwlock_wrlock(&fa.flows_lock); fd = eid_to_fd(eid); if (fd < 0) { pthread_rwlock_unlock(&fa.flows_lock); - ipcp_sdb_release(sdb); + log_dbg("Received packet for unknown EID %" PRIu64 ".", eid); + ipcp_spb_release(spb); return; } @@ -933,8 +970,9 @@ void fa_np1_rcv(uint64_t eid, pthread_rwlock_unlock(&fa.flows_lock); - if (ipcp_flow_write(fd, sdb) < 0) { - ipcp_sdb_release(sdb); + if (np1_flow_write(fd, spb, NP1_GET_POOL(fd)) < 0) { + log_dbg("Failed to write to flow %d.", fd); + ipcp_spb_release(spb); #ifdef IPCP_FLOW_STATS pthread_rwlock_wrlock(&fa.flows_lock); ++flow->p_rcv_f; diff --git a/src/ipcpd/unicast/fa.h b/src/ipcpd/unicast/fa.h index 376fdb20..6cd30995 100644 --- a/src/ipcpd/unicast/fa.h +++ b/src/ipcpd/unicast/fa.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Flow allocator of the IPC Process * @@ -34,21 +34,19 @@ int fa_start(void); void fa_stop(void); -int fa_alloc(int fd, - const uint8_t * dst, - qosspec_t qs, - const void * data, - size_t len); +int fa_alloc(int fd, + const uint8_t * dst, + qosspec_t qs, + const buffer_t * data); -int fa_alloc_resp(int fd, - int response, - const void * data, - size_t len); +int fa_alloc_resp(int fd, + int response, + const buffer_t * data); int fa_dealloc(int fd); void fa_np1_rcv(uint64_t eid, uint8_t ecn, - struct shm_du_buff * sdb); + struct ssm_pk_buff * spb); #endif /* OUROBOROS_IPCPD_UNICAST_FA_H */ diff --git a/src/ipcpd/unicast/main.c b/src/ipcpd/unicast/main.c index eafb7f2a..7989d3e1 100644 --- a/src/ipcpd/unicast/main.c +++ b/src/ipcpd/unicast/main.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Unicast IPC Process * @@ -32,12 +32,12 @@ #define THIS_TYPE IPCP_UNICAST #include <ouroboros/errno.h> -#include <ouroboros/hash.h> #include <ouroboros/ipcp-dev.h> #include <ouroboros/logs.h> #include <ouroboros/notifier.h> +#include <ouroboros/random.h> #include <ouroboros/rib.h> -#include <ouroboros/time_utils.h> +#include <ouroboros/time.h> #include "common/connmgr.h" #include "common/enroll.h" @@ -55,135 +55,114 @@ #include <assert.h> #include <inttypes.h> -struct ipcp ipcpi; - -static int initialize_components(const struct ipcp_config * conf) +static int initialize_components(struct ipcp_config * conf) { - ipcpi.layer_name = strdup(conf->layer_info.layer_name); - if (ipcpi.layer_name == NULL) { - log_err("Failed to set layer name."); - goto fail_layer_name; - } - - ipcpi.dir_hash_algo = conf->layer_info.dir_hash_algo; - assert(ipcp_dir_hash_len() != 0); - if (addr_auth_init(conf->addr_auth_type, - &conf->addr_size)) { + if (addr_auth_init(conf->unicast.addr_auth_type, + &conf->unicast.dt.addr_size)) { log_err("Failed to init address authority."); goto fail_addr_auth; } - ipcpi.dt_addr = addr_auth_address(); - if (ipcpi.dt_addr == 0) { - log_err("Failed to get a valid address."); - goto fail_addr_auth; - } + log_info("IPCP got address %" PRIu64 ".", addr_auth_address()); - log_dbg("IPCP got address %" PRIu64 ".", ipcpi.dt_addr); - - if (ca_init(conf->cong_avoid)) { + if (ca_init(conf->unicast.cong_avoid)) { log_err("Failed to initialize congestion avoidance."); goto fail_ca; } - if (dt_init(conf->routing_type, - conf->addr_size, - conf->eid_size, - conf->max_ttl)) { + if (dt_init(conf->unicast.dt)) { log_err("Failed to initialize data transfer component."); goto fail_dt; } - if (fa_init()) { - log_err("Failed to initialize flow allocator component."); - goto fail_fa; - } + ipcp_set_dir_hash_algo((enum hash_algo) conf->layer_info.dir_hash_algo); - if (dir_init()) { + if (dir_init(&conf->unicast.dir)) { log_err("Failed to initialize directory."); goto fail_dir; } + if (fa_init()) { + log_err("Failed to initialize flow allocator component."); + goto fail_fa; + } + ipcp_set_state(IPCP_INIT); return 0; - fail_dir: - fa_fini(); fail_fa: + dir_fini(); + fail_dir: dt_fini(); fail_dt: ca_fini(); fail_ca: addr_auth_fini(); fail_addr_auth: - free(ipcpi.layer_name); - fail_layer_name: return -1; } static void finalize_components(void) { - dir_fini(); - fa_fini(); + dir_fini(); + dt_fini(); ca_fini(); addr_auth_fini(); - - free(ipcpi.layer_name); } static int start_components(void) { - assert(ipcp_get_state() == IPCP_INIT); - - ipcp_set_state(IPCP_OPERATIONAL); + if (connmgr_start() < 0) { + log_err("Failed to start AP connection manager."); + goto fail_connmgr_start; + } - if (dt_start()) { + if (dt_start() < 0) { log_err("Failed to start data transfer."); goto fail_dt_start; } - if (fa_start()) { + if (fa_start() < 0) { log_err("Failed to start flow allocator."); goto fail_fa_start; } - if (enroll_start()) { + if (enroll_start() < 0) { log_err("Failed to start enrollment."); goto fail_enroll_start; } - if (connmgr_start()) { - log_err("Failed to start AP connection manager."); - goto fail_connmgr_start; + if (dir_start() < 0) { + log_err("Failed to start directory."); + goto fail_dir_start; } return 0; - fail_connmgr_start: + fail_dir_start: enroll_stop(); fail_enroll_start: fa_stop(); fail_fa_start: dt_stop(); fail_dt_start: + connmgr_stop(); + fail_connmgr_start: ipcp_set_state(IPCP_INIT); return -1; } static void stop_components(void) { - assert(ipcp_get_state() == IPCP_OPERATIONAL || - ipcp_get_state() == IPCP_SHUTDOWN); - - connmgr_stop(); + dir_stop(); enroll_stop(); @@ -191,55 +170,57 @@ static void stop_components(void) dt_stop(); - ipcp_set_state(IPCP_INIT); -} - -static int bootstrap_components(void) -{ - if (dir_bootstrap()) { - log_err("Failed to bootstrap directory."); - return -1; - } + connmgr_stop(); - return 0; + ipcp_set_state(IPCP_BOOT); } static int unicast_ipcp_enroll(const char * dst, struct layer_info * info) { - struct conn conn; + struct ipcp_config * conf; + struct conn conn; + uint8_t id[ENROLL_ID_LEN]; + + if (random_buffer(id, ENROLL_ID_LEN) < 0) { + log_err("Failed to generate enrollment ID."); + goto fail_id; + } + + log_info_id(id, "Requesting enrollment."); - if (connmgr_alloc(COMPID_ENROLL, dst, NULL, &conn)) { - log_err("Failed to get connection."); - goto fail_er_flow; + if (connmgr_alloc(COMPID_ENROLL, dst, NULL, &conn) < 0) { + log_err_id(id, "Failed to get connection."); + goto fail_id; } /* Get boot state from peer. */ - if (enroll_boot(&conn)) { - log_err("Failed to get boot information."); + if (enroll_boot(&conn, id) < 0) { + log_err_id(id, "Failed to get boot information."); goto fail_enroll_boot; } - if (initialize_components(enroll_get_conf())) { - log_err("Failed to initialize IPCP components."); + conf = enroll_get_conf(); + + *info = conf->layer_info; + + if (initialize_components(conf) < 0) { + log_err_id(id, "Failed to initialize components."); goto fail_enroll_boot; } - if (start_components()) { - log_err("Failed to start components."); + if (start_components() < 0) { + log_err_id(id, "Failed to start components."); goto fail_start_comp; } - if (enroll_done(&conn, 0)) - log_warn("Failed to confirm enrollment with peer."); + if (enroll_ack(&conn, id, 0) < 0) + log_err_id(id, "Failed to confirm enrollment."); - if (connmgr_dealloc(COMPID_ENROLL, &conn)) - log_warn("Failed to deallocate enrollment flow."); + if (connmgr_dealloc(COMPID_ENROLL, &conn) < 0) + log_warn_id(id, "Failed to dealloc enrollment flow."); - log_info("Enrolled with %s.", dst); - - info->dir_hash_algo = ipcpi.dir_hash_algo; - strcpy(info->layer_name, ipcpi.layer_name); + log_info_id(id, "Enrolled with %s.", dst); return 0; @@ -247,38 +228,29 @@ static int unicast_ipcp_enroll(const char * dst, finalize_components(); fail_enroll_boot: connmgr_dealloc(COMPID_ENROLL, &conn); - fail_er_flow: + fail_id: return -1; } -static int unicast_ipcp_bootstrap(const struct ipcp_config * conf) +static int unicast_ipcp_bootstrap(struct ipcp_config * conf) { assert(conf); assert(conf->type == THIS_TYPE); - enroll_bootstrap(conf); - - if (initialize_components(conf)) { + if (initialize_components(conf) < 0) { log_err("Failed to init IPCP components."); goto fail_init; } - if (start_components()) { + enroll_bootstrap(conf); + + if (start_components() < 0) { log_err("Failed to init IPCP components."); goto fail_start; } - if (bootstrap_components()) { - log_err("Failed to bootstrap IPCP components."); - goto fail_bootstrap; - } - - log_dbg("Bootstrapped in layer %s.", conf->layer_info.layer_name); - return 0; - fail_bootstrap: - stop_components(); fail_start: finalize_components(); fail_init: @@ -312,37 +284,34 @@ int main(int argc, goto fail_init; } - if (notifier_init()) { + if (notifier_init() < 0) { log_err("Failed to initialize notifier component."); goto fail_notifier_init; } - if (connmgr_init()) { + if (connmgr_init() < 0) { log_err("Failed to initialize connection manager."); goto fail_connmgr_init; } - if (enroll_init()) { + if (enroll_init() < 0) { log_err("Failed to initialize enrollment component."); goto fail_enroll_init; } - if (ipcp_boot() < 0) { - log_err("Failed to boot IPCP."); - goto fail_boot; - } - - if (ipcp_create_r(0)) { - log_err("Failed to notify IRMd we are initialized."); - ipcp_set_state(IPCP_NULL); - goto fail_create_r; + if (ipcp_start() < 0) { + log_err("Failed to start IPCP."); + goto fail_start; } - ipcp_shutdown(); + ipcp_sigwait(); if (ipcp_get_state() == IPCP_SHUTDOWN) { stop_components(); + ipcp_stop(); finalize_components(); + } else { + ipcp_stop(); } enroll_fini(); @@ -355,17 +324,14 @@ int main(int argc, exit(EXIT_SUCCESS); - fail_create_r: - ipcp_shutdown(); - fail_boot: + fail_start: enroll_fini(); fail_enroll_init: connmgr_fini(); fail_connmgr_init: notifier_fini(); fail_notifier_init: - ipcp_fini(); + ipcp_fini(); fail_init: - ipcp_create_r(-1); exit(EXIT_FAILURE); } diff --git a/src/ipcpd/unicast/pff.c b/src/ipcpd/unicast/pff.c index 3d36e6b0..9b2aa2b4 100644 --- a/src/ipcpd/unicast/pff.c +++ b/src/ipcpd/unicast/pff.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * PDU Forwarding Function * @@ -59,8 +59,10 @@ struct pff * pff_create(enum pol_pff pol) } pff->pff_i = pff->ops->create(); - if (pff->pff_i == NULL) + if (pff->pff_i == NULL) { + log_err("Failed to create PFF instance."); goto err; + } return pff; err: diff --git a/src/ipcpd/unicast/pff.h b/src/ipcpd/unicast/pff.h index 3ac9d5fb..f44e5531 100644 --- a/src/ipcpd/unicast/pff.h +++ b/src/ipcpd/unicast/pff.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * PDU Forwarding Function * diff --git a/src/ipcpd/unicast/pff/alternate.c b/src/ipcpd/unicast/pff/alternate.c index 9f0a6279..85e85914 100644 --- a/src/ipcpd/unicast/pff/alternate.c +++ b/src/ipcpd/unicast/pff/alternate.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Policy for PFF with alternate next hops * diff --git a/src/ipcpd/unicast/pff/alternate.h b/src/ipcpd/unicast/pff/alternate.h index 294f48d9..96207e74 100644 --- a/src/ipcpd/unicast/pff/alternate.h +++ b/src/ipcpd/unicast/pff/alternate.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Policy for PFF with alternate next hops * diff --git a/src/ipcpd/unicast/pff/multipath.c b/src/ipcpd/unicast/pff/multipath.c index b03ce32f..cbab0f5f 100644 --- a/src/ipcpd/unicast/pff/multipath.c +++ b/src/ipcpd/unicast/pff/multipath.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Policy for PFF supporting multipath routing * @@ -191,7 +191,7 @@ int multipath_pff_nhop(struct pff_i * pff_i, assert(len > 0); /* Rotate fds left. */ - memcpy(fds, fds + 1, (len - 1) * sizeof(*fds)); + memmove(fds, fds + 1, (len - 1) * sizeof(*fds)); fds[len - 1] = fd; pthread_rwlock_unlock(&pff_i->lock); diff --git a/src/ipcpd/unicast/pff/multipath.h b/src/ipcpd/unicast/pff/multipath.h index 4a5bcefb..0eb03476 100644 --- a/src/ipcpd/unicast/pff/multipath.h +++ b/src/ipcpd/unicast/pff/multipath.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Policy for PFF supporting multipath routing * diff --git a/src/ipcpd/unicast/pff/ops.h b/src/ipcpd/unicast/pff/ops.h index a46f3da8..16a31273 100644 --- a/src/ipcpd/unicast/pff/ops.h +++ b/src/ipcpd/unicast/pff/ops.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Pff policy ops * diff --git a/src/ipcpd/unicast/pff/pft.c b/src/ipcpd/unicast/pff/pft.c index e42b4a98..8c436113 100644 --- a/src/ipcpd/unicast/pff/pft.c +++ b/src/ipcpd/unicast/pff/pft.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Packet forwarding table (PFT) with chaining on collisions * @@ -115,19 +115,11 @@ void pft_flush(struct pft * pft) static uint64_t hash(uint64_t key) { - void * res; - uint64_t ret; - uint8_t keys[4]; + uint64_t res[2]; - memcpy(keys, &key, 4); + mem_hash(HASH_MD5, res, (uint8_t *) &key, sizeof(key)); - mem_hash(HASH_MD5, &res, keys, 4); - - ret = (* (uint64_t *) res); - - free(res); - - return ret; + return res[0]; } static uint64_t calc_key(struct pft * pft, diff --git a/src/ipcpd/unicast/pff/pft.h b/src/ipcpd/unicast/pff/pft.h index 011ad414..711dabcb 100644 --- a/src/ipcpd/unicast/pff/pft.h +++ b/src/ipcpd/unicast/pff/pft.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Packet forwarding table (PFT) with chaining on collisions * diff --git a/src/ipcpd/unicast/pff/pol.h b/src/ipcpd/unicast/pff/pol.h index 322d6dd6..245b03c4 100644 --- a/src/ipcpd/unicast/pff/pol.h +++ b/src/ipcpd/unicast/pff/pol.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * PDU Forwarding Function policies * diff --git a/src/ipcpd/unicast/pff/simple.c b/src/ipcpd/unicast/pff/simple.c index a007bcec..5f95e3ce 100644 --- a/src/ipcpd/unicast/pff/simple.c +++ b/src/ipcpd/unicast/pff/simple.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Simple PDU Forwarding Function * diff --git a/src/ipcpd/unicast/pff/simple.h b/src/ipcpd/unicast/pff/simple.h index e9083cf5..0966a186 100644 --- a/src/ipcpd/unicast/pff/simple.h +++ b/src/ipcpd/unicast/pff/simple.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Simple policy for PFF * diff --git a/src/ipcpd/unicast/pff/tests/CMakeLists.txt b/src/ipcpd/unicast/pff/tests/CMakeLists.txt index e7082372..8c0e3d51 100644 --- a/src/ipcpd/unicast/pff/tests/CMakeLists.txt +++ b/src/ipcpd/unicast/pff/tests/CMakeLists.txt @@ -3,32 +3,32 @@ get_filename_component(CURRENT_SOURCE_PARENT_DIR get_filename_component(CURRENT_BINARY_PARENT_DIR ${CMAKE_CURRENT_BINARY_DIR} DIRECTORY) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) - -include_directories(${CURRENT_SOURCE_PARENT_DIR}) -include_directories(${CURRENT_BINARY_PARENT_DIR}) - -include_directories(${CMAKE_SOURCE_DIR}/include) -include_directories(${CMAKE_BINARY_DIR}/include) - get_filename_component(PARENT_PATH ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) get_filename_component(PARENT_DIR ${PARENT_PATH} NAME) +compute_test_prefix() + create_test_sourcelist(${PARENT_DIR}_tests test_suite.c # Add new tests here pft_test.c ) -add_executable(${PARENT_DIR}_test EXCLUDE_FROM_ALL ${${PARENT_DIR}_tests}) -target_link_libraries(${PARENT_DIR}_test ouroboros-common) +add_executable(${PARENT_DIR}_test ${${PARENT_DIR}_tests}) + +target_include_directories(${PARENT_DIR}_test PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CURRENT_SOURCE_PARENT_DIR} + ${CURRENT_BINARY_PARENT_DIR} + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_BINARY_DIR}/include + ${CMAKE_SOURCE_DIR}/src/ipcpd + ${CMAKE_BINARY_DIR}/src/ipcpd +) -add_dependencies(check ${PARENT_DIR}_test) +disable_test_logging_for_target(${PARENT_DIR}_test) +target_link_libraries(${PARENT_DIR}_test PRIVATE ouroboros-common) -set(tests_to_run ${${PARENT_DIR}_tests}) -remove(tests_to_run test_suite.c) +add_dependencies(build_tests ${PARENT_DIR}_test) -foreach (test ${tests_to_run}) - get_filename_component(test_name ${test} NAME_WE) - add_test(${test_name} ${C_TEST_PATH}/${PARENT_DIR}_test ${test_name}) -endforeach (test) +ouroboros_register_tests(TARGET ${PARENT_DIR}_test TESTS ${${PARENT_DIR}_tests}) diff --git a/src/ipcpd/unicast/pff/tests/pft_test.c b/src/ipcpd/unicast/pff/tests/pft_test.c index c48267eb..18287fb8 100644 --- a/src/ipcpd/unicast/pff/tests/pft_test.c +++ b/src/ipcpd/unicast/pff/tests/pft_test.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Test of the hash table * diff --git a/src/ipcpd/unicast/psched.c b/src/ipcpd/unicast/psched.c index 33ac5afe..8c8caf19 100644 --- a/src/ipcpd/unicast/psched.c +++ b/src/ipcpd/unicast/psched.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Packet scheduler component * @@ -41,15 +41,18 @@ #include <stdlib.h> #include <string.h> +#ifndef BUILD_CONTAINER static int qos_prio [] = { QOS_PRIO_BE, QOS_PRIO_VIDEO, QOS_PRIO_VOICE, }; +#endif struct psched { fset_t * set[QOS_CUBE_MAX]; next_packet_fn_t callback; + read_fn_t read; pthread_t readers[QOS_CUBE_MAX * IPCP_SCHED_THR_MUL]; }; @@ -66,7 +69,7 @@ static void cleanup_reader(void * o) static void * packet_reader(void * o) { struct psched * sched; - struct shm_du_buff * sdb; + struct ssm_pk_buff * spb; int fd; fqueue_t * fq; qoscube_t qc; @@ -101,10 +104,10 @@ static void * packet_reader(void * o) notifier_event(NOTIFY_DT_FLOW_UP, &fd); break; case FLOW_PKT: - if (ipcp_flow_read(fd, &sdb)) + if (sched->read(fd, &spb) < 0) continue; - sched->callback(fd, qc, sdb); + sched->callback(fd, qc, spb); break; default: break; @@ -117,7 +120,8 @@ static void * packet_reader(void * o) return (void *) 0; } -struct psched * psched_create(next_packet_fn_t callback) +struct psched * psched_create(next_packet_fn_t callback, + read_fn_t read) { struct psched * psched; struct sched_info * infos[QOS_CUBE_MAX * IPCP_SCHED_THR_MUL]; @@ -131,6 +135,7 @@ struct psched * psched_create(next_packet_fn_t callback) goto fail_malloc; psched->callback = callback; + psched->read = read; for (i = 0; i < QOS_CUBE_MAX; ++i) { psched->set[i] = fset_create(); @@ -165,6 +170,7 @@ struct psched * psched_create(next_packet_fn_t callback) } } +#ifndef BUILD_CONTAINER for (i = 0; i < QOS_CUBE_MAX * IPCP_SCHED_THR_MUL; ++i) { struct sched_param par; int pol = SCHED_RR; @@ -182,14 +188,17 @@ struct psched * psched_create(next_packet_fn_t callback) if (pthread_setschedparam(psched->readers[i], pol, &par)) goto fail_sched; } +#endif return psched; +#ifndef BUILD_CONTAINER fail_sched: for (j = 0; j < QOS_CUBE_MAX * IPCP_SCHED_THR_MUL; ++j) pthread_cancel(psched->readers[j]); for (j = 0; j < QOS_CUBE_MAX * IPCP_SCHED_THR_MUL; ++j) pthread_join(psched->readers[j], NULL); +#endif fail_infos: for (j = 0; j < QOS_CUBE_MAX; ++j) fset_destroy(psched->set[j]); diff --git a/src/ipcpd/unicast/psched.h b/src/ipcpd/unicast/psched.h index 1f22b34b..be9c09e3 100644 --- a/src/ipcpd/unicast/psched.h +++ b/src/ipcpd/unicast/psched.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Packet scheduler component * @@ -28,9 +28,13 @@ typedef void (* next_packet_fn_t)(int fd, qoscube_t qc, - struct shm_du_buff * sdb); + struct ssm_pk_buff * spb); -struct psched * psched_create(next_packet_fn_t callback); +typedef int (* read_fn_t)(int fd, + struct ssm_pk_buff ** spb); + +struct psched * psched_create(next_packet_fn_t callback, + read_fn_t read); void psched_destroy(struct psched * psched); diff --git a/src/ipcpd/unicast/routing.c b/src/ipcpd/unicast/routing.c index 09f7d711..2ad7b234 100644 --- a/src/ipcpd/unicast/routing.c +++ b/src/ipcpd/unicast/routing.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Routing component of the IPCP * @@ -30,31 +30,26 @@ struct routing_ops * r_ops; -int routing_init(enum pol_routing pr) +int routing_init(struct routing_config * conf, + enum pol_pff * pff_type) { - enum pol_pff pff_type; + void * cfg; - switch (pr) { + switch (conf->pol) { case ROUTING_LINK_STATE: - pff_type = PFF_SIMPLE; - r_ops = &link_state_ops; - break; - case ROUTING_LINK_STATE_LFA: - pff_type = PFF_ALTERNATE; - r_ops = &link_state_ops; - break; - case ROUTING_LINK_STATE_ECMP: - pff_type=PFF_MULTIPATH; r_ops = &link_state_ops; + cfg = &conf->ls; break; default: return -ENOTSUP; } - if (r_ops->init(pr)) - return -1; + return r_ops->init(cfg, pff_type); +} - return pff_type; +int routing_start(void) +{ + return r_ops->start(); } struct routing_i * routing_i_create(struct pff * pff) @@ -67,6 +62,11 @@ void routing_i_destroy(struct routing_i * instance) return r_ops->routing_i_destroy(instance); } +void routing_stop(void) +{ + r_ops->stop(); +} + void routing_fini(void) { r_ops->fini(); diff --git a/src/ipcpd/unicast/routing.h b/src/ipcpd/unicast/routing.h index 2eaaeb68..e14960b5 100644 --- a/src/ipcpd/unicast/routing.h +++ b/src/ipcpd/unicast/routing.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Routing component of the IPCP * @@ -30,10 +30,15 @@ #include <stdint.h> -int routing_init(enum pol_routing pr); +int routing_init(struct routing_config * conf, + enum pol_pff * pff_type); void routing_fini(void); +int routing_start(void); + +void routing_stop(void); + struct routing_i * routing_i_create(struct pff * pff); void routing_i_destroy(struct routing_i * instance); diff --git a/src/ipcpd/unicast/routing/graph.c b/src/ipcpd/unicast/routing/graph.c index 6ea5c507..32442dad 100644 --- a/src/ipcpd/unicast/routing/graph.c +++ b/src/ipcpd/unicast/routing/graph.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Undirected graph structure * @@ -57,8 +57,11 @@ struct edge { }; struct graph { - size_t nr_vertices; - struct list_head vertices; + struct { + struct list_head list; + size_t len; + } vertices; + pthread_mutex_t lock; }; @@ -67,7 +70,7 @@ static struct edge * find_edge_by_addr(struct vertex * vertex, { struct list_head * p; - assert(vertex); + assert(vertex != NULL); list_for_each(p, &vertex->edges) { struct edge * e = list_entry(p, struct edge, next); @@ -85,7 +88,7 @@ static struct vertex * find_vertex_by_addr(struct graph * graph, assert(graph); - list_for_each(p, &graph->vertices) { + list_for_each(p, &graph->vertices.list) { struct vertex * e = list_entry(p, struct vertex, next); if (e->addr == addr) return e; @@ -99,8 +102,8 @@ static struct edge * add_edge(struct vertex * vertex, { struct edge * edge; - assert(vertex); - assert(nb); + assert(vertex != NULL); + assert(nb != NULL); edge = malloc(sizeof(*edge)); if (edge == NULL) @@ -139,7 +142,7 @@ static struct vertex * add_vertex(struct graph * graph, vertex->addr = addr; /* Keep them ordered on address. */ - list_for_each(p, &graph->vertices) { + list_for_each(p, &graph->vertices.list) { struct vertex * v = list_entry(p, struct vertex, next); if (v->addr > addr) break; @@ -151,13 +154,13 @@ static struct vertex * add_vertex(struct graph * graph, list_add_tail(&vertex->next, p); /* Increase the index of the vertices to the right. */ - list_for_each(p, &graph->vertices) { + list_for_each(p, &vertex->next) { struct vertex * v = list_entry(p, struct vertex, next); if (v->addr > addr) v->index++; } - graph->nr_vertices++; + ++graph->vertices.len; return vertex; } @@ -168,13 +171,13 @@ static void del_vertex(struct graph * graph, struct list_head * p; struct list_head * h; - assert(graph); - assert(vertex); + assert(graph != NULL); + assert(vertex != NULL); list_del(&vertex->next); /* Decrease the index of the vertices to the right. */ - list_for_each(p, &graph->vertices) { + list_for_each(p, &graph->vertices.list) { struct vertex * v = list_entry(p, struct vertex, next); if (v->addr > vertex->addr) v->index--; @@ -187,7 +190,7 @@ static void del_vertex(struct graph * graph, free(vertex); - graph->nr_vertices--; + --graph->vertices.len; } struct graph * graph_create(void) @@ -203,8 +206,8 @@ struct graph * graph_create(void) return NULL; } - graph->nr_vertices = 0; - list_head_init(&graph->vertices); + graph->vertices.len = 0; + list_head_init(&graph->vertices.list); return graph; } @@ -218,7 +221,7 @@ void graph_destroy(struct graph * graph) pthread_mutex_lock(&graph->lock); - list_for_each_safe(p, n, &graph->vertices) { + list_for_each_safe(p, n, &graph->vertices.list) { struct vertex * e = list_entry(p, struct vertex, next); del_vertex(graph, e); } @@ -227,6 +230,8 @@ void graph_destroy(struct graph * graph) pthread_mutex_destroy(&graph->lock); + assert(graph->vertices.len == 0); + free(graph); } @@ -240,63 +245,35 @@ int graph_update_edge(struct graph * graph, struct vertex * nb; struct edge * nb_e; - assert(graph); + assert(graph != NULL); pthread_mutex_lock(&graph->lock); v = find_vertex_by_addr(graph, s_addr); - if (v == NULL) { - v = add_vertex(graph, s_addr); - if (v == NULL) { - pthread_mutex_unlock(&graph->lock); - log_err("Failed to add vertex."); - return -ENOMEM; - } + if (v == NULL && ((v = add_vertex(graph, s_addr)) == NULL)) {; + log_err("Failed to add src vertex."); + goto fail_add_s; } nb = find_vertex_by_addr(graph, d_addr); - if (nb == NULL) { - nb = add_vertex(graph, d_addr); - if (nb == NULL) { - if (list_is_empty(&v->edges)) - del_vertex(graph, v); - pthread_mutex_unlock(&graph->lock); - log_err("Failed to add vertex."); - return -ENOMEM; - } + if (nb == NULL && ((nb = add_vertex(graph, d_addr)) == NULL)) { + log_err("Failed to add dst vertex."); + goto fail_add_d; } e = find_edge_by_addr(v, d_addr); - if (e == NULL) { - e = add_edge(v, nb); - if (e == NULL) { - if (list_is_empty(&v->edges)) - del_vertex(graph, v); - if (list_is_empty(&nb->edges)) - del_vertex(graph, nb); - pthread_mutex_unlock(&graph->lock); - log_err("Failed to add edge."); - return -ENOMEM; - } + if (e == NULL && ((e = add_edge(v, nb)) == NULL)) { + log_err("Failed to add edge to dst."); + goto fail_add_edge_d; } e->announced++; e->qs = qs; nb_e = find_edge_by_addr(nb, s_addr); - if (nb_e == NULL) { - nb_e = add_edge(nb, v); - if (nb_e == NULL) { - if (--e->announced == 0) - del_edge(e); - if (list_is_empty(&v->edges)) - del_vertex(graph, v); - if (list_is_empty(&nb->edges)) - del_vertex(graph, nb); - pthread_mutex_unlock(&graph->lock); - log_err("Failed to add edge."); - return -ENOMEM; - } + if (nb_e == NULL && ((nb_e = add_edge(nb, v)) == NULL)) {; + log_err("Failed to add edge to src."); + goto fail_add_edge_s; } nb_e->announced++; @@ -305,6 +282,19 @@ int graph_update_edge(struct graph * graph, pthread_mutex_unlock(&graph->lock); return 0; + fail_add_edge_s: + if (--e->announced == 0) + del_edge(e); + fail_add_edge_d: + if (list_is_empty(&nb->edges)) + del_vertex(graph, nb); + fail_add_d: + if (list_is_empty(&v->edges)) + del_vertex(graph, v); + fail_add_s: + pthread_mutex_unlock(&graph->lock); + return -ENOMEM; + } int graph_del_edge(struct graph * graph, @@ -322,30 +312,26 @@ int graph_del_edge(struct graph * graph, v = find_vertex_by_addr(graph, s_addr); if (v == NULL) { - pthread_mutex_unlock(&graph->lock); - log_err("No such source vertex."); - return -1; + log_err("Failed to find src vertex."); + goto fail; } nb = find_vertex_by_addr(graph, d_addr); if (nb == NULL) { - pthread_mutex_unlock(&graph->lock); log_err("No such destination vertex."); - return -1; + goto fail; } e = find_edge_by_addr(v, d_addr); if (e == NULL) { - pthread_mutex_unlock(&graph->lock); log_err("No such source edge."); - return -1; + goto fail; } nb_e = find_edge_by_addr(nb, s_addr); if (nb_e == NULL) { - pthread_mutex_unlock(&graph->lock); log_err("No such destination edge."); - return -1; + goto fail; } if (--e->announced == 0) @@ -362,6 +348,10 @@ int graph_del_edge(struct graph * graph, pthread_mutex_unlock(&graph->lock); return 0; + + fail: + pthread_mutex_unlock(&graph->lock); + return -1; } static int get_min_vertex(struct graph * graph, @@ -381,7 +371,7 @@ static int get_min_vertex(struct graph * graph, *v = NULL; - list_for_each(p, &graph->vertices) { + list_for_each(p, &graph->vertices.list) { if (!used[i] && dist[i] < min) { min = dist[i]; index = i; @@ -413,24 +403,24 @@ static int dijkstra(struct graph * graph, assert(nhops); assert(dist); - *nhops = malloc(sizeof(**nhops) * graph->nr_vertices); + *nhops = malloc(sizeof(**nhops) * graph->vertices.len); if (*nhops == NULL) goto fail_pnhops; - *dist = malloc(sizeof(**dist) * graph->nr_vertices); + *dist = malloc(sizeof(**dist) * graph->vertices.len); if (*dist == NULL) goto fail_pdist; - used = malloc(sizeof(*used) * graph->nr_vertices); + used = malloc(sizeof(*used) * graph->vertices.len); if (used == NULL) goto fail_used; /* Init the data structures */ - memset(used, 0, sizeof(*used) * graph->nr_vertices); - memset(*nhops, 0, sizeof(**nhops) * graph->nr_vertices); - memset(*dist, 0, sizeof(**dist) * graph->nr_vertices); + memset(used, 0, sizeof(*used) * graph->vertices.len); + memset(*nhops, 0, sizeof(**nhops) * graph->vertices.len); + memset(*dist, 0, sizeof(**dist) * graph->vertices.len); - list_for_each(p, &graph->vertices) { + list_for_each(p, &graph->vertices.list) { v = list_entry(p, struct vertex, next); (*dist)[i++] = (v->addr == src) ? 0 : INT_MAX; } @@ -527,7 +517,7 @@ static int graph_routing_table_simple(struct graph * graph, assert(dist); /* We need at least 2 vertices for a table */ - if (graph->nr_vertices < 2) + if (graph->vertices.len < 2) goto fail_vertices; if (dijkstra(graph, s_addr, &nhops, dist)) @@ -536,7 +526,7 @@ static int graph_routing_table_simple(struct graph * graph, list_head_init(table); /* Now construct the routing table from the nhops. */ - list_for_each(p, &graph->vertices) { + list_for_each(p, &graph->vertices.list) { v = list_entry(p, struct vertex, next); /* This is the src */ @@ -634,7 +624,7 @@ static int graph_routing_table_lfa(struct graph * graph, addrs[j] = -1; } - list_for_each(p, &graph->vertices) { + list_for_each(p, &graph->vertices.list) { v = list_entry(p, struct vertex, next); if (v->addr != s_addr) @@ -660,7 +650,7 @@ static int graph_routing_table_lfa(struct graph * graph, } /* Loop though all nodes to see if we have a LFA for them. */ - list_for_each(p, &graph->vertices) { + list_for_each(p, &graph->vertices.list) { v = list_entry(p, struct vertex, next); if (v->addr == s_addr) @@ -717,14 +707,14 @@ static int graph_routing_table_ecmp(struct graph * graph, assert(graph); assert(dist); - if (graph-> nr_vertices < 2) + if (graph->vertices.len < 2) goto fail_vertices; - forwarding = malloc(sizeof(*forwarding) * graph->nr_vertices); + forwarding = malloc(sizeof(*forwarding) * graph->vertices.len); if (forwarding == NULL) goto fail_vertices; - for (i = 0; i < graph->nr_vertices; ++i) + for (i = 0; i < graph->vertices.len; ++i) list_head_init(&forwarding[i]); if (dijkstra(graph, s_addr, &nhops, dist)) @@ -745,7 +735,7 @@ static int graph_routing_table_ecmp(struct graph * graph, free(nhops); - list_for_each(h, &graph->vertices) { + list_for_each(h, &graph->vertices.list) { v = list_entry(h, struct vertex, next); if (tmp_dist[v->index] + 1 == (*dist)[v->index]) { n = malloc(sizeof(*n)); @@ -763,7 +753,7 @@ static int graph_routing_table_ecmp(struct graph * graph, list_head_init(table); i = 0; - list_for_each(p, &graph->vertices) { + list_for_each(p, &graph->vertices.list) { v = list_entry(p, struct vertex, next); if (v->addr == s_addr) { ++i; diff --git a/src/ipcpd/unicast/routing/graph.h b/src/ipcpd/unicast/routing/graph.h index 632cc5a0..8190cc6c 100644 --- a/src/ipcpd/unicast/routing/graph.h +++ b/src/ipcpd/unicast/routing/graph.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Undirected graph structure * diff --git a/src/ipcpd/unicast/routing/link-state.c b/src/ipcpd/unicast/routing/link-state.c index 7ceb86a1..95a104bb 100644 --- a/src/ipcpd/unicast/routing/link-state.c +++ b/src/ipcpd/unicast/routing/link-state.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Link state routing policy * @@ -42,6 +42,7 @@ #include <ouroboros/rib.h> #include <ouroboros/utils.h> +#include "addr-auth.h" #include "common/comp.h" #include "common/connmgr.h" #include "graph.h" @@ -54,16 +55,19 @@ #include <inttypes.h> #include <string.h> -#define RECALC_TIME 4 -#define LS_UPDATE_TIME 15 -#define LS_TIMEO 60 #define LS_ENTRY_SIZE 104 -#define LSDB "lsdb" +#define Lspb "lspb" #ifndef CLOCK_REALTIME_COARSE #define CLOCK_REALTIME_COARSE CLOCK_REALTIME #endif +#define LINK_FMT ADDR_FMT32 "--" ADDR_FMT32 +#define LINK_VAL(src, dst) ADDR_VAL32(&src), ADDR_VAL32(&dst) + +#define LSU_FMT "LSU ["ADDR_FMT32 " -- " ADDR_FMT32 " seq: %09" PRIu64 "]" +#define LSU_VAL(src, dst, seqno) ADDR_VAL32(&src), ADDR_VAL32(&dst), seqno + struct lsa { uint64_t d_addr; uint64_t s_addr; @@ -106,30 +110,45 @@ struct nb { }; struct { - struct list_head nbs; - size_t nbs_len; + uint64_t addr; + + enum routing_algo routing_algo; + + struct ls_config conf; + fset_t * mgmt_set; - struct list_head db; - size_t db_len; + struct graph * graph; + + struct { + struct { + struct list_head list; + size_t len; + } nbs; + + struct { + struct list_head list; + size_t len; + } db; - pthread_rwlock_t db_lock; + pthread_rwlock_t lock; + }; - struct graph * graph; + struct { + struct list_head list; + pthread_mutex_t mtx; + } instances; pthread_t lsupdate; pthread_t lsreader; pthread_t listener; - - struct list_head routing_instances; - pthread_mutex_t routing_i_lock; - - enum routing_algo routing_algo; } ls; struct routing_ops link_state_ops = { - .init = link_state_init, + .init = (int (*)(void *, enum pol_pff *)) link_state_init, .fini = link_state_fini, + .start = link_state_start, + .stop = link_state_stop, .routing_i_create = link_state_routing_i_create, .routing_i_destroy = link_state_routing_i_destroy }; @@ -138,7 +157,7 @@ static int str_adj(struct adjacency * adj, char * buf, size_t len) { - char tmbuf[64]; + char tmstr[RIB_TM_STRLEN]; char srcbuf[64]; char dstbuf[64]; char seqnobuf[64]; @@ -149,15 +168,16 @@ static int str_adj(struct adjacency * adj, if (len < LS_ENTRY_SIZE) return -1; - tm = localtime(&adj->stamp); - strftime(tmbuf, sizeof(tmbuf), "%F %T", tm); /* 19 chars */ + tm = gmtime(&adj->stamp); + strftime(tmstr, sizeof(tmstr), RIB_TM_FORMAT, tm); - sprintf(srcbuf, "%" PRIu64, adj->src); - sprintf(dstbuf, "%" PRIu64, adj->dst); + sprintf(srcbuf, ADDR_FMT32, ADDR_VAL32(&adj->src)); + sprintf(dstbuf, ADDR_FMT32, ADDR_VAL32(&adj->dst)); sprintf(seqnobuf, "%" PRIu64, adj->seqno); - sprintf(buf, "src: %20s\ndst: %20s\nseqno: %18s\nupd: %20s\n", - srcbuf, dstbuf, seqnobuf, tmbuf); + sprintf(buf, "src: %20s\ndst: %20s\nseqno: %18s\n" + "upd: %s\n", + srcbuf, dstbuf, seqnobuf, tmstr); return LS_ENTRY_SIZE; } @@ -169,9 +189,9 @@ static struct adjacency * get_adj(const char * path) assert(path); - list_for_each(p, &ls.db) { + list_for_each(p, &ls.db.list) { struct adjacency * a = list_entry(p, struct adjacency, next); - sprintf(entry, "%" PRIu64 ".%" PRIu64, a->src, a->dst); + sprintf(entry, LINK_FMT, LINK_VAL(a->src, a->dst)); if (strcmp(entry, path) == 0) return a; } @@ -179,7 +199,7 @@ static struct adjacency * get_adj(const char * path) return NULL; } -static int lsdb_rib_getattr(const char * path, +static int lspb_rib_getattr(const char * path, struct rib_attr * attr) { struct adjacency * adj; @@ -194,7 +214,7 @@ static int lsdb_rib_getattr(const char * path, clock_gettime(CLOCK_REALTIME_COARSE, &now); - pthread_rwlock_rdlock(&ls.db_lock); + pthread_rwlock_rdlock(&ls.lock); adj = get_adj(entry); if (adj != NULL) { @@ -205,12 +225,12 @@ static int lsdb_rib_getattr(const char * path, attr->size = 0; } - pthread_rwlock_unlock(&ls.db_lock); + pthread_rwlock_unlock(&ls.lock); return 0; } -static int lsdb_rib_read(const char * path, +static int lspb_rib_read(const char * path, char * buf, size_t len) { @@ -223,9 +243,9 @@ static int lsdb_rib_read(const char * path, entry = strstr(path, RIB_SEPARATOR) + 1; assert(entry); - pthread_rwlock_rdlock(&ls.db_lock); + pthread_rwlock_rdlock(&ls.lock); - if (ls.db_len + ls.nbs_len == 0) + if (ls.db.len + ls.nbs.len == 0) goto fail; a = get_adj(entry); @@ -236,111 +256,103 @@ static int lsdb_rib_read(const char * path, if (size < 0) goto fail; - pthread_rwlock_unlock(&ls.db_lock); + pthread_rwlock_unlock(&ls.lock); return size; fail: - pthread_rwlock_unlock(&ls.db_lock); + pthread_rwlock_unlock(&ls.lock); return -1; } -static int lsdb_rib_readdir(char *** buf) +static int lspb_rib_readdir(char *** buf) { struct list_head * p; char entry[RIB_PATH_LEN + 1]; ssize_t idx = 0; - assert(buf); + assert(buf != NULL); - pthread_rwlock_rdlock(&ls.db_lock); + pthread_rwlock_rdlock(&ls.lock); - if (ls.db_len + ls.nbs_len == 0) { - pthread_rwlock_unlock(&ls.db_lock); - return 0; + if (ls.db.len + ls.nbs.len == 0) { + *buf = NULL; + goto no_entries; } - *buf = malloc(sizeof(**buf) * (ls.db_len + ls.nbs_len)); - if (*buf == NULL) { - pthread_rwlock_unlock(&ls.db_lock); - return -ENOMEM; - } - list_for_each(p, &ls.nbs) { + *buf = malloc(sizeof(**buf) * (ls.db.len + ls.nbs.len)); + if (*buf == NULL) + goto fail_entries; + + list_for_each(p, &ls.nbs.list) { struct nb * nb = list_entry(p, struct nb, next); - char * str = (nb->type == NB_DT ? "dt." : "mgmt."); - sprintf(entry, "%s%" PRIu64, str, nb->addr); + char * str = (nb->type == NB_DT ? ".dt " : ".mgmt "); + sprintf(entry, "%s" ADDR_FMT32 , str, ADDR_VAL32(&nb->addr)); (*buf)[idx] = malloc(strlen(entry) + 1); - if ((*buf)[idx] == NULL) { - while (idx-- > 0) - free((*buf)[idx]); - free(buf); - pthread_rwlock_unlock(&ls.db_lock); - return -ENOMEM; - } - - strcpy((*buf)[idx], entry); + if ((*buf)[idx] == NULL) + goto fail_entry; - idx++; + strcpy((*buf)[idx++], entry); } - list_for_each(p, &ls.db) { + list_for_each(p, &ls.db.list) { struct adjacency * a = list_entry(p, struct adjacency, next); - sprintf(entry, "%" PRIu64 ".%" PRIu64, a->src, a->dst); + sprintf(entry, LINK_FMT, LINK_VAL(a->src, a->dst)); (*buf)[idx] = malloc(strlen(entry) + 1); - if ((*buf)[idx] == NULL) { - ssize_t j; - for (j = 0; j < idx; ++j) - free(*buf[j]); - free(buf); - pthread_rwlock_unlock(&ls.db_lock); - return -ENOMEM; - } - - strcpy((*buf)[idx], entry); + if ((*buf)[idx] == NULL) + goto fail_entry; - idx++; + strcpy((*buf)[idx++], entry); } - - pthread_rwlock_unlock(&ls.db_lock); + no_entries: + pthread_rwlock_unlock(&ls.lock); return idx; + + fail_entry: + while (idx-- > 0) + free((*buf)[idx]); + free(*buf); + fail_entries: + pthread_rwlock_unlock(&ls.lock); + return -ENOMEM; } static struct rib_ops r_ops = { - .read = lsdb_rib_read, - .readdir = lsdb_rib_readdir, - .getattr = lsdb_rib_getattr + .read = lspb_rib_read, + .readdir = lspb_rib_readdir, + .getattr = lspb_rib_getattr }; -static int lsdb_add_nb(uint64_t addr, +static int lspb_add_nb(uint64_t addr, int fd, enum nb_type type) { struct list_head * p; struct nb * nb; - pthread_rwlock_wrlock(&ls.db_lock); + pthread_rwlock_wrlock(&ls.lock); - list_for_each(p, &ls.nbs) { + list_for_each(p, &ls.nbs.list) { struct nb * el = list_entry(p, struct nb, next); - if (el->addr == addr && el->type == type) { - log_dbg("Already know %s neighbor %" PRIu64 ".", - type == NB_DT ? "dt" : "mgmt", addr); - if (el->fd != fd) { - log_warn("Existing neighbor assigned new fd."); - el->fd = fd; - } - pthread_rwlock_unlock(&ls.db_lock); - return -EPERM; - } - if (addr > el->addr) break; + if (el->addr != addr || el->type != type) + continue; + + log_dbg("Already know %s neighbor " ADDR_FMT32 ".", + type == NB_DT ? "dt" : "mgmt", ADDR_VAL32(&addr)); + if (el->fd != fd) { + log_warn("Existing neighbor assigned new fd."); + el->fd = fd; + } + pthread_rwlock_unlock(&ls.lock); + return -EPERM; } nb = malloc(sizeof(*nb)); if (nb == NULL) { - pthread_rwlock_unlock(&ls.db_lock); + pthread_rwlock_unlock(&ls.lock); return -ENOMEM; } @@ -350,38 +362,39 @@ static int lsdb_add_nb(uint64_t addr, list_add_tail(&nb->next, p); - ++ls.nbs_len; + ++ls.nbs.len; - log_dbg("Type %s neighbor %" PRIu64 " added.", - nb->type == NB_DT ? "dt" : "mgmt", addr); + log_dbg("Type %s neighbor " ADDR_FMT32 " added.", + nb->type == NB_DT ? "dt" : "mgmt", ADDR_VAL32(&addr)); - pthread_rwlock_unlock(&ls.db_lock); + pthread_rwlock_unlock(&ls.lock); return 0; } -static int lsdb_del_nb(uint64_t addr, +static int lspb_del_nb(uint64_t addr, int fd) { struct list_head * p; struct list_head * h; - pthread_rwlock_wrlock(&ls.db_lock); + pthread_rwlock_wrlock(&ls.lock); - list_for_each_safe(p, h, &ls.nbs) { + list_for_each_safe(p, h, &ls.nbs.list) { struct nb * nb = list_entry(p, struct nb, next); - if (nb->addr == addr && nb->fd == fd) { - list_del(&nb->next); - --ls.nbs_len; - pthread_rwlock_unlock(&ls.db_lock); - log_dbg("Type %s neighbor %" PRIu64 " deleted.", - nb->type == NB_DT ? "dt" : "mgmt", addr); - free(nb); - return 0; - } + if (nb->addr != addr || nb->fd != fd) + continue; + + list_del(&nb->next); + --ls.nbs.len; + pthread_rwlock_unlock(&ls.lock); + log_dbg("Type %s neighbor " ADDR_FMT32 " deleted.", + nb->type == NB_DT ? "dt" : "mgmt", ADDR_VAL32(&addr)); + free(nb); + return 0; } - pthread_rwlock_unlock(&ls.db_lock); + pthread_rwlock_unlock(&ls.lock); return -EPERM; } @@ -391,18 +404,18 @@ static int nbr_to_fd(uint64_t addr) struct list_head * p; int fd; - pthread_rwlock_rdlock(&ls.db_lock); + pthread_rwlock_rdlock(&ls.lock); - list_for_each(p, &ls.nbs) { + list_for_each(p, &ls.nbs.list) { struct nb * nb = list_entry(p, struct nb, next); if (nb->addr == addr && nb->type == NB_DT) { fd = nb->fd; - pthread_rwlock_unlock(&ls.db_lock); + pthread_rwlock_unlock(&ls.lock); return fd; } } - pthread_rwlock_unlock(&ls.db_lock); + pthread_rwlock_unlock(&ls.lock); return -1; } @@ -417,8 +430,7 @@ static void calculate_pff(struct routing_i * instance) assert(instance); - if (graph_routing_table(ls.graph, ls.routing_algo, - ipcpi.dt_addr, &table)) + if (graph_routing_table(ls.graph, ls.routing_algo, ls.addr, &table)) return; pff_lock(instance->pff); @@ -453,8 +465,8 @@ static void set_pff_modified(bool calc) { struct list_head * p; - pthread_mutex_lock(&ls.routing_i_lock); - list_for_each(p, &ls.routing_instances) { + pthread_mutex_lock(&ls.instances.mtx); + list_for_each(p, &ls.instances.list) { struct routing_i * inst = list_entry(p, struct routing_i, next); pthread_mutex_lock(&inst->lock); @@ -463,10 +475,10 @@ static void set_pff_modified(bool calc) if (calc) calculate_pff(inst); } - pthread_mutex_unlock(&ls.routing_i_lock); + pthread_mutex_unlock(&ls.instances.mtx); } -static int lsdb_add_link(uint64_t src, +static int lspb_add_link(uint64_t src, uint64_t dst, uint64_t seqno, qosspec_t * qs) @@ -480,9 +492,9 @@ static int lsdb_add_link(uint64_t src, clock_gettime(CLOCK_REALTIME_COARSE, &now); - pthread_rwlock_wrlock(&ls.db_lock); + pthread_rwlock_wrlock(&ls.lock); - list_for_each(p, &ls.db) { + list_for_each(p, &ls.db.list) { struct adjacency * a = list_entry(p, struct adjacency, next); if (a->dst == dst && a->src == src) { if (a->seqno < seqno) { @@ -490,7 +502,7 @@ static int lsdb_add_link(uint64_t src, a->seqno = seqno; ret = 0; } - pthread_rwlock_unlock(&ls.db_lock); + pthread_rwlock_unlock(&ls.lock); return ret; } @@ -500,7 +512,7 @@ static int lsdb_add_link(uint64_t src, adj = malloc(sizeof(*adj)); if (adj == NULL) { - pthread_rwlock_unlock(&ls.db_lock); + pthread_rwlock_unlock(&ls.lock); return -ENOMEM; } @@ -511,43 +523,43 @@ static int lsdb_add_link(uint64_t src, list_add_tail(&adj->next, p); - ls.db_len++; + ls.db.len++; if (graph_update_edge(ls.graph, src, dst, *qs)) log_warn("Failed to add edge to graph."); - pthread_rwlock_unlock(&ls.db_lock); + pthread_rwlock_unlock(&ls.lock); set_pff_modified(true); return 0; } -static int lsdb_del_link(uint64_t src, +static int lspb_del_link(uint64_t src, uint64_t dst) { struct list_head * p; struct list_head * h; - pthread_rwlock_wrlock(&ls.db_lock); + pthread_rwlock_wrlock(&ls.lock); - list_for_each_safe(p, h, &ls.db) { + list_for_each_safe(p, h, &ls.db.list) { struct adjacency * a = list_entry(p, struct adjacency, next); if (a->dst == dst && a->src == src) { list_del(&a->next); if (graph_del_edge(ls.graph, src, dst)) log_warn("Failed to delete edge from graph."); - ls.db_len--; + ls.db.len--; - pthread_rwlock_unlock(&ls.db_lock); + pthread_rwlock_unlock(&ls.lock); set_pff_modified(false); free(a); return 0; } } - pthread_rwlock_unlock(&ls.db_lock); + pthread_rwlock_unlock(&ls.lock); return -EPERM; } @@ -570,7 +582,7 @@ static void * periodic_recalc_pff(void * o) if (modified) calculate_pff(inst); - sleep(RECALC_TIME); + sleep(ls.conf.t_recalc); } return (void *) 0; @@ -587,15 +599,25 @@ static void send_lsm(uint64_t src, lsm.s_addr = hton64(src); lsm.seqno = hton64(seqno); - list_for_each(p, &ls.nbs) { + list_for_each(p, &ls.nbs.list) { struct nb * nb = list_entry(p, struct nb, next); - if (nb->type == NB_MGMT) - flow_write(nb->fd, &lsm, sizeof(lsm)); + if (nb->type != NB_MGMT) + continue; + + if (flow_write(nb->fd, &lsm, sizeof(lsm)) < 0) + log_err("Failed to send LSM to " ADDR_FMT32, + ADDR_VAL32(&nb->addr)); +#ifdef DEBUG_PROTO_LS + else + log_proto(LSU_FMT " --> " ADDR_FMT32, + LSU_VAL(src, dst, seqno), + ADDR_VAL32(&nb->addr)); +#endif } } -/* replicate the lsdb to a mgmt neighbor */ -static void lsdb_replicate(int fd) +/* replicate the lspb to a mgmt neighbor */ +static void lspb_replicate(int fd) { struct list_head * p; struct list_head * h; @@ -603,16 +625,16 @@ static void lsdb_replicate(int fd) list_head_init(©); - /* Lock the lsdb, copy the lsms and send outside of lock. */ - pthread_rwlock_rdlock(&ls.db_lock); + /* Lock the lspb, copy the lsms and send outside of lock. */ + pthread_rwlock_rdlock(&ls.lock); - list_for_each(p, &ls.db) { + list_for_each(p, &ls.db.list) { struct adjacency * adj; struct adjacency * cpy; adj = list_entry(p, struct adjacency, next); cpy = malloc(sizeof(*cpy)); if (cpy == NULL) { - log_warn("Failed to replicate full lsdb."); + log_warn("Failed to replicate full lspb."); break; } @@ -623,7 +645,7 @@ static void lsdb_replicate(int fd) list_add_tail(&cpy->next, ©); } - pthread_rwlock_unlock(&ls.db_lock); + pthread_rwlock_unlock(&ls.lock); list_for_each_safe(p, h, ©) { struct lsa lsm; @@ -649,17 +671,17 @@ static void * lsupdate(void * o) while (true) { clock_gettime(CLOCK_REALTIME_COARSE, &now); - pthread_rwlock_wrlock(&ls.db_lock); + pthread_rwlock_wrlock(&ls.lock); - pthread_cleanup_push(__cleanup_rwlock_unlock, &ls.db_lock); + pthread_cleanup_push(__cleanup_rwlock_unlock, &ls.lock); - list_for_each_safe(p, h, &ls.db) { + list_for_each_safe(p, h, &ls.db.list) { struct adjacency * adj; adj = list_entry(p, struct adjacency, next); - if (now.tv_sec - adj->stamp > LS_TIMEO) { + if (now.tv_sec > adj->stamp + ls.conf.t_timeo) { list_del(&adj->next); - log_dbg("%" PRIu64 " - %" PRIu64" timed out.", - adj->src, adj->dst); + log_dbg(LINK_FMT " timed out.", + LINK_VAL(adj->src, adj->dst)); if (graph_del_edge(ls.graph, adj->src, adj->dst)) log_err("Failed to del edge."); @@ -667,7 +689,7 @@ static void * lsupdate(void * o) continue; } - if (adj->src == ipcpi.dt_addr) { + if (adj->src == ls.addr) { adj->seqno++; send_lsm(adj->src, adj->dst, adj->seqno); adj->stamp = now.tv_sec; @@ -676,7 +698,7 @@ static void * lsupdate(void * o) pthread_cleanup_pop(true); - sleep(LS_UPDATE_TIME); + sleep(ls.conf.t_update); } return (void *) 0; @@ -708,15 +730,36 @@ static void forward_lsm(uint8_t * buf, int in_fd) { struct list_head * p; +#ifdef DEBUG_PROTO_LS + struct lsa lsm; - pthread_rwlock_rdlock(&ls.db_lock); + assert(buf); + assert(len >= sizeof(struct lsa)); + + memcpy(&lsm, buf, sizeof(lsm)); + + lsm.s_addr = ntoh64(lsm.s_addr); + lsm.d_addr = ntoh64(lsm.d_addr); + lsm.seqno = ntoh64(lsm.seqno); +#endif + pthread_rwlock_rdlock(&ls.lock); - pthread_cleanup_push(__cleanup_rwlock_unlock, &ls.db_lock); + pthread_cleanup_push(__cleanup_rwlock_unlock, &ls.lock); - list_for_each(p, &ls.nbs) { + list_for_each(p, &ls.nbs.list) { struct nb * nb = list_entry(p, struct nb, next); - if (nb->type == NB_MGMT && nb->fd != in_fd) - flow_write(nb->fd, buf, len); + if (nb->type != NB_MGMT || nb->fd == in_fd) + continue; + + if (flow_write(nb->fd, buf, len) < 0) + log_err("Failed to forward LSM to " ADDR_FMT32, + ADDR_VAL32(&nb->addr)); +#ifdef DEBUG_PROTO_LS + else + log_proto(LSU_FMT " --> " ADDR_FMT32 " [forwarded]", + LSU_VAL(lsm.s_addr, lsm.d_addr, lsm.seqno), + ADDR_VAL32(&nb->addr)); +#endif } pthread_cleanup_pop(true); @@ -729,13 +772,13 @@ static void cleanup_fqueue(void * fq) static void * lsreader(void * o) { - fqueue_t * fq; - int ret; - uint8_t buf[sizeof(struct lsa)]; - int fd; - qosspec_t qs; - struct lsa * msg; - size_t len; + fqueue_t * fq; + int ret; + uint8_t buf[sizeof(struct lsa)]; + int fd; + qosspec_t qs; + struct lsa msg; + size_t len; (void) o; @@ -758,15 +801,22 @@ static void * lsreader(void * o) if (fqueue_type(fq) != FLOW_PKT) continue; - len = flow_read(fd, buf, sizeof(*msg)); - if (len <= 0 || len != sizeof(*msg)) + len = flow_read(fd, buf, sizeof(msg)); + if (len <= 0 || len != sizeof(msg)) continue; - msg = (struct lsa *) buf; - - if (lsdb_add_link(ntoh64(msg->s_addr), - ntoh64(msg->d_addr), - ntoh64(msg->seqno), + memcpy(&msg, buf, sizeof(msg)); + msg.s_addr = ntoh64(msg.s_addr); + msg.d_addr = ntoh64(msg.d_addr); + msg.seqno = ntoh64(msg.seqno); +#ifdef DEBUG_PROTO_LS + log_proto(LSU_FMT " <-- " ADDR_FMT32, + LSU_VAL(msg.s_addr, msg.d_addr, msg.seqno), + ADDR_VAL32(&ls.addr)); +#endif + if (lspb_add_link(msg.s_addr, + msg.d_addr, + msg.seqno, &qs)) continue; @@ -787,14 +837,14 @@ static void flow_event(int fd, log_dbg("Notifying routing instances of flow event."); - pthread_mutex_lock(&ls.routing_i_lock); + pthread_mutex_lock(&ls.instances.mtx); - list_for_each(p, &ls.routing_instances) { + list_for_each(p, &ls.instances.list) { struct routing_i * ri = list_entry(p, struct routing_i, next); pff_flow_state_change(ri->pff, fd, up); } - pthread_mutex_unlock(&ls.routing_i_lock); + pthread_mutex_unlock(&ls.instances.mtx); } static void handle_event(void * self, @@ -816,27 +866,27 @@ static void handle_event(void * self, switch (event) { case NOTIFY_DT_CONN_ADD: - pthread_rwlock_rdlock(&ls.db_lock); + pthread_rwlock_rdlock(&ls.lock); - pthread_cleanup_push(__cleanup_rwlock_unlock, &ls.db_lock); + pthread_cleanup_push(__cleanup_rwlock_unlock, &ls.lock); - send_lsm(ipcpi.dt_addr, c->conn_info.addr, 0); + send_lsm(ls.addr, c->conn_info.addr, 0); pthread_cleanup_pop(true); - if (lsdb_add_nb(c->conn_info.addr, c->flow_info.fd, NB_DT)) - log_dbg("Failed to add neighbor to LSDB."); + if (lspb_add_nb(c->conn_info.addr, c->flow_info.fd, NB_DT)) + log_dbg("Failed to add neighbor to Lspb."); - if (lsdb_add_link(ipcpi.dt_addr, c->conn_info.addr, 0, &qs)) - log_dbg("Failed to add new adjacency to LSDB."); + if (lspb_add_link(ls.addr, c->conn_info.addr, 0, &qs)) + log_dbg("Failed to add new adjacency to Lspb."); break; case NOTIFY_DT_CONN_DEL: flow_event(c->flow_info.fd, false); - if (lsdb_del_nb(c->conn_info.addr, c->flow_info.fd)) - log_dbg("Failed to delete neighbor from LSDB."); + if (lspb_del_nb(c->conn_info.addr, c->flow_info.fd)) + log_dbg("Failed to delete neighbor from Lspb."); - if (lsdb_del_link(ipcpi.dt_addr, c->conn_info.addr)) - log_dbg("Local link was not in LSDB."); + if (lspb_del_link(ls.addr, c->conn_info.addr)) + log_dbg("Local link was not in Lspb."); break; case NOTIFY_DT_CONN_QOS: log_dbg("QoS changes currently unsupported."); @@ -851,15 +901,15 @@ static void handle_event(void * self, fccntl(c->flow_info.fd, FLOWGFLAGS, &flags); fccntl(c->flow_info.fd, FLOWSFLAGS, flags | FLOWFRNOPART); fset_add(ls.mgmt_set, c->flow_info.fd); - if (lsdb_add_nb(c->conn_info.addr, c->flow_info.fd, NB_MGMT)) - log_warn("Failed to add mgmt neighbor to LSDB."); - /* replicate the entire lsdb */ - lsdb_replicate(c->flow_info.fd); + if (lspb_add_nb(c->conn_info.addr, c->flow_info.fd, NB_MGMT)) + log_warn("Failed to add mgmt neighbor to Lspb."); + /* replicate the entire lspb */ + lspb_replicate(c->flow_info.fd); break; case NOTIFY_MGMT_CONN_DEL: fset_del(ls.mgmt_set, c->flow_info.fd); - if (lsdb_del_nb(c->conn_info.addr, c->flow_info.fd)) - log_warn("Failed to delete mgmt neighbor from LSDB."); + if (lspb_del_nb(c->conn_info.addr, c->flow_info.fd)) + log_warn("Failed to delete mgmt neighbor from Lspb."); break; default: break; @@ -886,11 +936,11 @@ struct routing_i * link_state_routing_i_create(struct pff * pff) periodic_recalc_pff, tmp)) goto fail_pthread_create_lsupdate; - pthread_mutex_lock(&ls.routing_i_lock); + pthread_mutex_lock(&ls.instances.mtx); - list_add(&tmp->next, &ls.routing_instances); + list_add(&tmp->next, &ls.instances.list); - pthread_mutex_unlock(&ls.routing_i_lock); + pthread_mutex_unlock(&ls.instances.mtx); return tmp; @@ -906,11 +956,11 @@ void link_state_routing_i_destroy(struct routing_i * instance) { assert(instance); - pthread_mutex_lock(&ls.routing_i_lock); + pthread_mutex_lock(&ls.instances.mtx); list_del(&instance->next); - pthread_mutex_unlock(&ls.routing_i_lock); + pthread_mutex_unlock(&ls.instances.mtx); pthread_cancel(instance->calculator); @@ -921,96 +971,146 @@ void link_state_routing_i_destroy(struct routing_i * instance) free(instance); } -int link_state_init(enum pol_routing pr) +int link_state_start(void) +{ + if (notifier_reg(handle_event, NULL)) { + log_err("Failed to register link-state with notifier."); + goto fail_notifier_reg; + } + + if (pthread_create(&ls.lsupdate, NULL, lsupdate, NULL)) { + log_err("Failed to create lsupdate thread."); + goto fail_pthread_create_lsupdate; + } + + if (pthread_create(&ls.lsreader, NULL, lsreader, NULL)) { + log_err("Failed to create lsreader thread."); + goto fail_pthread_create_lsreader; + } + + if (pthread_create(&ls.listener, NULL, ls_conn_handle, NULL)) { + log_err("Failed to create listener thread."); + goto fail_pthread_create_listener; + } + + return 0; + + fail_pthread_create_listener: + pthread_cancel(ls.lsreader); + pthread_join(ls.lsreader, NULL); + fail_pthread_create_lsreader: + pthread_cancel(ls.lsupdate); + pthread_join(ls.lsupdate, NULL); + fail_pthread_create_lsupdate: + notifier_unreg(handle_event); + fail_notifier_reg: + return -1; +} + +void link_state_stop(void) +{ + pthread_cancel(ls.listener); + pthread_cancel(ls.lsreader); + pthread_cancel(ls.lsupdate); + + pthread_join(ls.listener, NULL); + pthread_join(ls.lsreader, NULL); + pthread_join(ls.lsupdate, NULL); + + notifier_unreg(handle_event); +} + + +int link_state_init(struct ls_config * conf, + enum pol_pff * pff_type) { struct conn_info info; + assert(conf != NULL); + assert(pff_type != NULL); + memset(&info, 0, sizeof(info)); + ls.addr = addr_auth_address(); + strcpy(info.comp_name, LS_COMP); strcpy(info.protocol, LS_PROTO); info.pref_version = 1; info.pref_syntax = PROTO_GPB; - info.addr = ipcpi.dt_addr; + info.addr = ls.addr; - switch (pr) { - case ROUTING_LINK_STATE: - log_dbg("Using link state routing policy."); + ls.conf = *conf; + + switch (conf->pol) { + case LS_SIMPLE: + *pff_type = PFF_SIMPLE; ls.routing_algo = ROUTING_SIMPLE; + log_dbg("Using Link State Routing policy."); break; - case ROUTING_LINK_STATE_LFA: - log_dbg("Using Loop-Free Alternates policy."); + case LS_LFA: ls.routing_algo = ROUTING_LFA; + *pff_type = PFF_ALTERNATE; + log_dbg("Using Loop-Free Alternates policy."); break; - case ROUTING_LINK_STATE_ECMP: - log_dbg("Using Equal-Cost Multipath policy."); + case LS_ECMP: ls.routing_algo = ROUTING_ECMP; + *pff_type = PFF_MULTIPATH; + log_dbg("Using Equal-Cost Multipath policy."); break; default: goto fail_graph; } + log_dbg("LS update interval: %ld seconds.", ls.conf.t_update); + log_dbg("LS link timeout : %ld seconds.", ls.conf.t_timeo); + log_dbg("LS recalc interval: %ld seconds.", ls.conf.t_recalc); + ls.graph = graph_create(); if (ls.graph == NULL) goto fail_graph; - if (notifier_reg(handle_event, NULL)) - goto fail_notifier_reg; - - if (pthread_rwlock_init(&ls.db_lock, NULL)) - goto fail_db_lock_init; + if (pthread_rwlock_init(&ls.lock, NULL)) { + log_err("Failed to init lock."); + goto fail_lock_init; + } - if (pthread_mutex_init(&ls.routing_i_lock, NULL)) + if (pthread_mutex_init(&ls.instances.mtx, NULL)) { + log_err("Failed to init instances mutex."); goto fail_routing_i_lock_init; + } - if (connmgr_comp_init(COMPID_MGMT, &info)) + if (connmgr_comp_init(COMPID_MGMT, &info)) { + log_err("Failed to init connmgr."); goto fail_connmgr_comp_init; + } ls.mgmt_set = fset_create(); - if (ls.mgmt_set == NULL) + if (ls.mgmt_set == NULL) { + log_err("Failed to create fset."); goto fail_fset_create; + } - list_head_init(&ls.db); - list_head_init(&ls.nbs); - list_head_init(&ls.routing_instances); - - if (pthread_create(&ls.lsupdate, NULL, lsupdate, NULL)) - goto fail_pthread_create_lsupdate; - - if (pthread_create(&ls.lsreader, NULL, lsreader, NULL)) - goto fail_pthread_create_lsreader; - - if (pthread_create(&ls.listener, NULL, ls_conn_handle, NULL)) - goto fail_pthread_create_listener; + list_head_init(&ls.db.list); + list_head_init(&ls.nbs.list); + list_head_init(&ls.instances.list); - if (rib_reg(LSDB, &r_ops)) + if (rib_reg(Lspb, &r_ops)) goto fail_rib_reg; - ls.db_len = 0; - ls.nbs_len = 0; + ls.db.len = 0; + ls.nbs.len = 0; return 0; fail_rib_reg: - pthread_cancel(ls.listener); - pthread_join(ls.listener, NULL); - fail_pthread_create_listener: - pthread_cancel(ls.lsreader); - pthread_join(ls.lsreader, NULL); - fail_pthread_create_lsreader: - pthread_cancel(ls.lsupdate); - pthread_join(ls.lsupdate, NULL); - fail_pthread_create_lsupdate: fset_destroy(ls.mgmt_set); fail_fset_create: connmgr_comp_fini(COMPID_MGMT); fail_connmgr_comp_init: - pthread_mutex_destroy(&ls.routing_i_lock); + pthread_mutex_destroy(&ls.instances.mtx); fail_routing_i_lock_init: - pthread_rwlock_destroy(&ls.db_lock); - fail_db_lock_init: - notifier_unreg(handle_event); - fail_notifier_reg: + pthread_rwlock_destroy(&ls.lock); + fail_lock_init: graph_destroy(ls.graph); fail_graph: return -1; @@ -1021,17 +1121,7 @@ void link_state_fini(void) struct list_head * p; struct list_head * h; - rib_unreg(LSDB); - - notifier_unreg(handle_event); - - pthread_cancel(ls.listener); - pthread_cancel(ls.lsreader); - pthread_cancel(ls.lsupdate); - - pthread_join(ls.listener, NULL); - pthread_join(ls.lsreader, NULL); - pthread_join(ls.lsupdate, NULL); + rib_unreg(Lspb); fset_destroy(ls.mgmt_set); @@ -1039,17 +1129,17 @@ void link_state_fini(void) graph_destroy(ls.graph); - pthread_rwlock_wrlock(&ls.db_lock); + pthread_rwlock_wrlock(&ls.lock); - list_for_each_safe(p, h, &ls.db) { + list_for_each_safe(p, h, &ls.db.list) { struct adjacency * a = list_entry(p, struct adjacency, next); list_del(&a->next); free(a); } - pthread_rwlock_unlock(&ls.db_lock); + pthread_rwlock_unlock(&ls.lock); - pthread_rwlock_destroy(&ls.db_lock); + pthread_rwlock_destroy(&ls.lock); - pthread_mutex_destroy(&ls.routing_i_lock); + pthread_mutex_destroy(&ls.instances.mtx); } diff --git a/src/ipcpd/unicast/routing/link-state.h b/src/ipcpd/unicast/routing/link-state.h index c6e573ff..69eb6781 100644 --- a/src/ipcpd/unicast/routing/link-state.h +++ b/src/ipcpd/unicast/routing/link-state.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Link state routing policy * @@ -28,10 +28,15 @@ #include "ops.h" -int link_state_init(enum pol_routing pr); +int link_state_init(struct ls_config * ls, + enum pol_pff * pff_type); void link_state_fini(void); +int link_state_start(void); + +void link_state_stop(void); + struct routing_i * link_state_routing_i_create(struct pff * pff); void link_state_routing_i_destroy(struct routing_i * instance); diff --git a/src/ipcpd/unicast/routing/ops.h b/src/ipcpd/unicast/routing/ops.h index 1522ccd9..4bf75c80 100644 --- a/src/ipcpd/unicast/routing/ops.h +++ b/src/ipcpd/unicast/routing/ops.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Routing policy ops * @@ -26,10 +26,15 @@ #include "pff.h" struct routing_ops { - int (* init)(enum pol_routing pr); + int (* init)(void * conf, + enum pol_pff * pff_type); void (* fini)(void); + int (* start)(void); + + void (* stop)(void); + struct routing_i * (* routing_i_create)(struct pff * pff); void (* routing_i_destroy)(struct routing_i * instance); diff --git a/src/ipcpd/unicast/routing/pol.h b/src/ipcpd/unicast/routing/pol.h index f6e5274e..b6a6f150 100644 --- a/src/ipcpd/unicast/routing/pol.h +++ b/src/ipcpd/unicast/routing/pol.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Routing policies * diff --git a/src/ipcpd/unicast/routing/tests/CMakeLists.txt b/src/ipcpd/unicast/routing/tests/CMakeLists.txt index d0652533..be2de72c 100644 --- a/src/ipcpd/unicast/routing/tests/CMakeLists.txt +++ b/src/ipcpd/unicast/routing/tests/CMakeLists.txt @@ -3,32 +3,32 @@ get_filename_component(CURRENT_SOURCE_PARENT_DIR get_filename_component(CURRENT_BINARY_PARENT_DIR ${CMAKE_CURRENT_BINARY_DIR} DIRECTORY) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) - -include_directories(${CURRENT_SOURCE_PARENT_DIR}) -include_directories(${CURRENT_BINARY_PARENT_DIR}) - -include_directories(${CMAKE_SOURCE_DIR}/include) -include_directories(${CMAKE_BINARY_DIR}/include) - get_filename_component(PARENT_PATH ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) get_filename_component(PARENT_DIR ${PARENT_PATH} NAME) +compute_test_prefix() + create_test_sourcelist(${PARENT_DIR}_tests test_suite.c # Add new tests here graph_test.c ) -add_executable(${PARENT_DIR}_test EXCLUDE_FROM_ALL ${${PARENT_DIR}_tests}) -target_link_libraries(${PARENT_DIR}_test ouroboros-common) +add_executable(${PARENT_DIR}_test ${${PARENT_DIR}_tests}) + +target_include_directories(${PARENT_DIR}_test PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CURRENT_SOURCE_PARENT_DIR} + ${CURRENT_BINARY_PARENT_DIR} + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_BINARY_DIR}/include + ${CMAKE_SOURCE_DIR}/src/ipcpd + ${CMAKE_BINARY_DIR}/src/ipcpd +) -add_dependencies(check ${PARENT_DIR}_test) +disable_test_logging_for_target(${PARENT_DIR}_test) +target_link_libraries(${PARENT_DIR}_test PRIVATE ouroboros-common) -set(tests_to_run ${${PARENT_DIR}_tests}) -remove(tests_to_run test_suite.c) +add_dependencies(build_tests ${PARENT_DIR}_test) -foreach (test ${tests_to_run}) - get_filename_component(test_name ${test} NAME_WE) - add_test(${test_name} ${C_TEST_PATH}/${PARENT_DIR}_test ${test_name}) -endforeach (test) +ouroboros_register_tests(TARGET ${PARENT_DIR}_test TESTS ${${PARENT_DIR}_tests}) diff --git a/src/ipcpd/unicast/routing/tests/graph_test.c b/src/ipcpd/unicast/routing/tests/graph_test.c index 217c7eab..d805640c 100644 --- a/src/ipcpd/unicast/routing/tests/graph_test.c +++ b/src/ipcpd/unicast/routing/tests/graph_test.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Test of the graph structure * diff --git a/src/irmd/CMakeLists.txt b/src/irmd/CMakeLists.txt index 59d0d103..d65635af 100644 --- a/src/irmd/CMakeLists.txt +++ b/src/irmd/CMakeLists.txt @@ -1,52 +1,65 @@ -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) - -include_directories(${CMAKE_SOURCE_DIR}/include) -include_directories(${CMAKE_BINARY_DIR}/include) - -set(IRMD_REQ_ARR_TIMEOUT 500 CACHE STRING - "Timeout for an application to respond to a new flow (ms)") -set(IRMD_FLOW_TIMEOUT 5000 CACHE STRING - "Timeout for a flow allocation response (ms)") -set(BOOTSTRAP_TIMEOUT 5000 CACHE STRING - "Timeout for an IPCP to bootstrap (ms)") -set(ENROLL_TIMEOUT 60000 CACHE STRING - "Timeout for an IPCP to enroll (ms)") -set(REG_TIMEOUT 10000 CACHE STRING - "Timeout for registering a name (ms)") -set(QUERY_TIMEOUT 3000 CACHE STRING - "Timeout to query a name with an IPCP (ms)") -set(CONNECT_TIMEOUT 60000 CACHE STRING - "Timeout to connect an IPCP to another IPCP (ms)") -set(IRMD_MIN_THREADS 8 CACHE STRING - "Minimum number of worker threads in the IRMd.") -set(IRMD_ADD_THREADS 8 CACHE STRING - "Number of extra threads to start when the IRMD faces thread starvation") +# IRMd (IPC Resource Manager daemon) build configuration +# Configuration options are in cmake/config/global.cmake and cmake/config/irmd.cmake + +# Generate and install configuration files if TOML support available +# HAVE_TOML is set in cmake/dependencies/irmd/libtoml.cmake +if(HAVE_TOML) + set(INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}") + configure_file("${CMAKE_SOURCE_DIR}/irmd.conf.in" + "${CMAKE_BINARY_DIR}/${OUROBOROS_CONFIG_FILE}.example" @ONLY) + configure_file("${CMAKE_SOURCE_DIR}/enc.conf.in" + "${CMAKE_BINARY_DIR}/enc.conf.example" @ONLY) + install(FILES "${CMAKE_BINARY_DIR}/${OUROBOROS_CONFIG_FILE}.example" + DESTINATION "${OUROBOROS_CONFIG_DIR}") + install(FILES "${CMAKE_BINARY_DIR}/enc.conf.example" + DESTINATION "${OUROBOROS_CONFIG_DIR}") + install(CODE " + if(NOT EXISTS \"${OUROBOROS_CONFIG_DIR}/${OUROBOROS_CONFIG_FILE}\") + file(WRITE \"${OUROBOROS_CONFIG_DIR}/${OUROBOROS_CONFIG_FILE}\" \"\") + endif() + ") + unset(INSTALL_DIR) +endif() configure_file("${CMAKE_CURRENT_SOURCE_DIR}/config.h.in" "${CMAKE_CURRENT_BINARY_DIR}/config.h" @ONLY) -set(SOURCE_FILES - # Add source files here - proc_table.c - prog_table.c +set(IRMD_SOURCES ipcp.c - irm_flow.c + configfile.c main.c - registry.c - utils.c - ) + oap/io.c + oap/hdr.c + oap/auth.c + oap/srv.c + oap/cli.c + reg/flow.c + reg/ipcp.c + reg/pool.c + reg/proc.c + reg/prog.c + reg/name.c + reg/reg.c +) + +add_executable(irmd ${IRMD_SOURCES}) -add_executable (irmd ${SOURCE_FILES}) +target_include_directories(irmd PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_BINARY_DIR}/include) -target_link_libraries (irmd LINK_PUBLIC ouroboros-common) +target_link_libraries(irmd PRIVATE ouroboros-common) +if(HAVE_TOML) + target_link_libraries(irmd PRIVATE toml::toml) +endif() -include(AddCompileFlags) -if (CMAKE_BUILD_TYPE MATCHES "Debug*") - add_compile_flags(irmd -DCONFIG_OUROBOROS_DEBUG) -endif () +ouroboros_target_debug_definitions(irmd) install(TARGETS irmd RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}) -# Enable once irmd has tests -# add_subdirectory(tests) +if(BUILD_TESTS) + add_subdirectory(oap/tests) + add_subdirectory(reg/tests) +endif() diff --git a/src/irmd/config.h.in b/src/irmd/config.h.in index a7bd9066..e1072193 100644 --- a/src/irmd/config.h.in +++ b/src/irmd/config.h.in @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Configuration for the IPC Resource Manager * @@ -20,36 +20,83 @@ * Foundation, Inc., http://www.fsf.org/about/contact/. */ -#define IPCP_UDP_EXEC "@IPCP_UDP_TARGET@" -#define IPCP_ETH_LLC_EXEC "@IPCP_ETH_LLC_TARGET@" -#define IPCP_ETH_DIX_EXEC "@IPCP_ETH_DIX_TARGET@" -#define IPCP_UNICAST_EXEC "@IPCP_UNICAST_TARGET@" -#define IPCP_BROADCAST_EXEC "@IPCP_BROADCAST_TARGET@" -#define IPCP_LOCAL_EXEC "@IPCP_LOCAL_TARGET@" -#define INSTALL_PREFIX "@CMAKE_INSTALL_PREFIX@" +#define IPCP_UDP4_EXEC "@IPCP_UDP4_TARGET@" +#define IPCP_UDP6_EXEC "@IPCP_UDP6_TARGET@" +#define IPCP_ETH_LLC_EXEC "@IPCP_ETH_LLC_TARGET@" +#define IPCP_ETH_DIX_EXEC "@IPCP_ETH_DIX_TARGET@" +#define IPCP_UNICAST_EXEC "@IPCP_UNICAST_TARGET@" +#define IPCP_BROADCAST_EXEC "@IPCP_BROADCAST_TARGET@" +#define IPCP_LOCAL_EXEC "@IPCP_LOCAL_TARGET@" -#define PTHREAD_COND_CLOCK @PTHREAD_COND_CLOCK@ +#define INSTALL_PREFIX "@CMAKE_INSTALL_PREFIX@" +#define INSTALL_SBINDIR "@CMAKE_INSTALL_SBINDIR@" -#define SOCKET_TIMEOUT @SOCKET_TIMEOUT@ +#define PTHREAD_COND_CLOCK @PTHREAD_COND_CLOCK@ -#define IRMD_REQ_ARR_TIMEOUT @IRMD_REQ_ARR_TIMEOUT@ -#define IRMD_FLOW_TIMEOUT @IRMD_FLOW_TIMEOUT@ +#define SOCKET_TIMEOUT @SOCKET_TIMEOUT@ -#define BOOTSTRAP_TIMEOUT @BOOTSTRAP_TIMEOUT@ -#define ENROLL_TIMEOUT @ENROLL_TIMEOUT@ -#define REG_TIMEOUT @REG_TIMEOUT@ -#define QUERY_TIMEOUT @QUERY_TIMEOUT@ -#define CONNECT_TIMEOUT @CONNECT_TIMEOUT@ +#define IRMD_REQ_ARR_TIMEOUT @IRMD_REQ_ARR_TIMEOUT@ -#define SYS_MAX_FLOWS @SYS_MAX_FLOWS@ +#define FLOW_ALLOC_TIMEOUT @FLOW_ALLOC_TIMEOUT@ +#define FLOW_DEALLOC_TIMEOUT @FLOW_DEALLOC_TIMEOUT@ -#define IRMD_MIN_THREADS @IRMD_MIN_THREADS@ -#define IRMD_ADD_THREADS @IRMD_ADD_THREADS@ +#define OAP_REPLAY_TIMER @OAP_REPLAY_TIMER@ + +#define BOOTSTRAP_TIMEOUT @BOOTSTRAP_TIMEOUT@ +#define ENROLL_TIMEOUT @ENROLL_TIMEOUT@ +#define REG_TIMEOUT @REG_TIMEOUT@ +#define QUERY_TIMEOUT @QUERY_TIMEOUT@ +#define CONNECT_TIMEOUT @CONNECT_TIMEOUT@ + +#define SYS_MAX_FLOWS @SYS_MAX_FLOWS@ +#define IRMD_MIN_THREADS @IRMD_MIN_THREADS@ +#define IRMD_ADD_THREADS @IRMD_ADD_THREADS@ + +#define SSM_PID_GSPP 0 #cmakedefine HAVE_FUSE #ifdef HAVE_FUSE -#define FUSE_PREFIX "@FUSE_PREFIX@" +#define FUSE_PREFIX "@FUSE_PREFIX@" #endif +#cmakedefine HAVE_TOML +#ifdef HAVE_TOML +#define OUROBOROS_CONFIG_DIR "@OUROBOROS_CONFIG_DIR@" +#define OUROBOROS_CONFIG_FILE "@OUROBOROS_CONFIG_FILE@" +#endif + +#define OUROBOROS_SECURITY_DIR "@OUROBOROS_SECURITY_DIR@" +#define OUROBOROS_CA_CRT_DIR "@OUROBOROS_CA_CRT_DIR@" +#define OUROBOROS_SRV_CRT_DIR "@OUROBOROS_SRV_CRT_DIR@" +#define OUROBOROS_CLI_CRT_DIR "@OUROBOROS_CLI_CRT_DIR@" +#define OUROBOROS_CHAIN_DIR "@OUROBOROS_UNTRUSTED_DIR@" + +#define IRMD_PKILL_TIMEOUT @IRMD_PKILL_TIMEOUT@ + +#cmakedefine IRMD_KILL_ALL_PROCESSES #cmakedefine HAVE_LIBGCRYPT +#cmakedefine HAVE_OPENSSL +#ifdef HAVE_OPENSSL +#cmakedefine HAVE_OPENSSL_PQC +#endif +#define IRMD_SECMEM_MAX @IRMD_SECMEM_MAX@ +#ifdef CONFIG_OUROBOROS_DEBUG +#cmakedefine DEBUG_PROTO_OAP +#endif + +#define _B "[38;5;4m" +#define _G "[38;5;8m" +#define RST "[0m" + +#define O7S_ASCII_ART \ +RST "\n" \ +_B " ▄▄█████▄▄▄ \n" \ +_B " ▄█▀▀ ▀▀███▄ " _G " â–ˆ \n" \ +_B " ██ â–„â–„â–„ ▄███▄ " _G "â–„ â–„ â–„ â–„ â–„â–„ █▄▄ â–„â–„ â–„ â–„ â–„â–„ â–„â–„ \n" \ +_B " ██ â–ˆ â–ˆ █████ " _G "â–ˆ â–ˆ █▀ â–€ â–ˆ â–ˆ â–ˆ â–ˆ â–ˆ â–ˆ █▀ â–€ â–ˆ â–ˆ ▀▄ â–€\n" \ +_B " ██ ▀▄▄▄▀ ▀█▀ " _G "â–ˆ â–ˆ â–ˆ â–ˆ â–ˆ â–ˆ â–ˆ â–ˆ â–ˆ â–ˆ â–ˆ â–ˆ â–„ ▀▄\n" \ +_B " █▄ â–ˆ " _G " ▀▀ â–€ â–€ ▀▀ ▀▀▀ ▀▀ â–€ ▀▀ ▀▀ \n" \ +_B " ▀█▄▄▄▄▄▄▄▄▀ \n" \ +_B " ▀▀▀▀▀▀ \n" \ +RST "\n" diff --git a/src/irmd/configfile.c b/src/irmd/configfile.c new file mode 100644 index 00000000..ce9fc8fc --- /dev/null +++ b/src/irmd/configfile.c @@ -0,0 +1,1133 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * The IPC Resource Manager / Configuration from file + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + + +#include "config.h" + +#if defined (HAVE_TOML) + +#define _POSIX_C_SOURCE 200809L +#define _XOPEN_SOURCE 500 + +#define OUROBOROS_PREFIX "irmd/configuration" + +#include <ouroboros/errno.h> +#include <ouroboros/ipcp.h> +#include <ouroboros/logs.h> +#include <ouroboros/utils.h> + +#include "irmd.h" +#include "configfile.h" + +#include "reg/reg.h" + +#include <assert.h> +#include <errno.h> +#include <inttypes.h> +#include <stdlib.h> +#include <string.h> +#include <toml.h> +#include <arpa/inet.h> +#ifdef __FreeBSD__ +#include <sys/socket.h> +#endif + +#define ERRBUFSZ 200 +#define DATUMSZ 256 + +static int toml_hash(toml_table_t * table, + struct layer_info * info) +{ + toml_datum_t hash; + + hash = toml_string_in(table, "hash"); + if (!hash.ok) { + log_dbg("No hash specified, using default."); + return 0; + } + + if (strcmp(hash.u.s, "SHA3_224") == 0) { + info->dir_hash_algo = DIR_HASH_SHA3_224; + } else if (strcmp(hash.u.s, "SHA3_256") == 0) { + info->dir_hash_algo = DIR_HASH_SHA3_256; + } else if (strcmp(hash.u.s, "SHA3_384") == 0) { + info->dir_hash_algo = DIR_HASH_SHA3_384; + } else if (strcmp(hash.u.s, "SHA3_512") == 0) { + info->dir_hash_algo = DIR_HASH_SHA3_512; + } else { + log_err("Unknown hash algorithm: %s.", hash.u.s); + free(hash.u.s); + return -1; + } + + free(hash.u.s); + + return 0; +} + +static int toml_local(toml_table_t * table, + struct ipcp_config * conf) +{ + *conf = local_default_conf; + + return toml_hash(table, &conf->layer_info); +} + +static int toml_eth_dev(toml_table_t * table, + struct eth_config * conf) +{ + toml_datum_t dev; + + dev = toml_string_in(table, "dev"); + if (!dev.ok) { + log_err("Missing device."); + return -1; + } + + if (strlen(dev.u.s) > DEV_NAME_SIZE) { + log_err("Device name too long: %s", dev.u.s); + free(dev.u.s); + return -1; + } + + strcpy(conf->dev, dev.u.s); + free(dev.u.s); + + return 0; +} + +static int toml_eth_llc(toml_table_t * table, + struct ipcp_config * conf) +{ + *conf = eth_llc_default_conf; + + if (toml_hash(table, &conf->layer_info) < 0) + return -1; + + return toml_eth_dev(table, &conf->eth); +} + + +static int toml_ethertype(toml_table_t * table, + struct eth_config * conf) +{ + toml_datum_t ethertype; + + ethertype = toml_int_in(table, "ethertype"); + if (ethertype.ok) + conf->ethertype = ethertype.u.i; + + if (conf->ethertype < 0x0600 || conf->ethertype == 0xFFFF) + return -1; + + return 0; +} + +static int toml_eth_dix(toml_table_t * table, + struct ipcp_config * conf) +{ + *conf = eth_dix_default_conf; + + if (toml_hash(table, &conf->layer_info) < 0) + return -1; + + if (toml_eth_dev(table, &conf->eth) < 0) + return -1; + + if (toml_ethertype(table, &conf->eth) < 0) { + log_err("Ethertype not in valid range."); + return -1; + } + + return 0; +} + +static int toml_udp4(toml_table_t * table, + struct ipcp_config * conf) +{ + struct udp4_config * udp4; + toml_datum_t ip; + toml_datum_t port; + toml_datum_t dns; + + *conf = udp4_default_conf; + udp4 = &conf->udp4; + + ip = toml_string_in(table, "ip"); + if (!ip.ok) { + log_err("No IP address specified!"); + goto fail_ip; + } + + if (inet_pton (AF_INET, ip.u.s, &udp4->ip_addr.s_addr) != 1) { + log_err("Failed to parse IPv4 address %s.", ip.u.s); + goto fail_addr; + } + + port = toml_int_in(table, "port"); + if (port.ok) + udp4->port = port.u.i; + + dns = toml_string_in(table, "dns"); + if (dns.ok) { + if (inet_pton(AF_INET, dns.u.s, &udp4->dns_addr.s_addr) < 0) { + log_err("Failed to parse DNS address %s.", ip.u.s); + goto fail_dns; + } + + free(dns.u.s); + } + + free(ip.u.s); + + return 0; + + fail_dns: + free(dns.u.s); + fail_addr: + free(ip.u.s); + fail_ip: + return -1; +} + +static int toml_udp6(toml_table_t * table, + struct ipcp_config * conf) +{ + struct in6_addr ip6; + struct in6_addr dns6; + toml_datum_t ip; + toml_datum_t port; + toml_datum_t dns; + + *conf = udp6_default_conf; + ip6 = conf->udp6.ip_addr; + dns6 = conf->udp6.dns_addr; + + ip = toml_string_in(table, "ip"); + if (!ip.ok) { + log_err("No IP address specified!"); + goto fail_ip; + } + + if (inet_pton (AF_INET6, ip.u.s, &ip6.s6_addr) != 1) { + log_err("Failed to parse IPv4 address %s.", ip.u.s); + goto fail_addr; + } + + port = toml_int_in(table, "port"); + if (port.ok) + conf->udp6.port = port.u.i; + + dns = toml_string_in(table, "dns"); + if (dns.ok) { + if (inet_pton(AF_INET6, dns.u.s, &dns6.s6_addr) < 0) { + log_err("Failed to parse DNS address %s.", ip.u.s); + goto fail_dns; + } + + free(dns.u.s); + } + + free(ip.u.s); + + return 0; + + fail_dns: + free(dns.u.s); + fail_addr: + free(ip.u.s); + fail_ip: + return -1; +} + +static int toml_broadcast(toml_table_t * table, + struct ipcp_config * conf) +{ + (void) table; + (void) conf; + + /* Nothing to do here. */ + + return 0; +} + +#define BETWEEN(a, b, c) ((a) >= (b) && (a) <= (c)) +#define DHT(conf, x) (conf)->dht.params.x +static int toml_dir(toml_table_t * table, + struct dir_config * conf) +{ + toml_datum_t dir; + toml_datum_t alpha; + toml_datum_t t_expire; + toml_datum_t t_refresh; + toml_datum_t t_replicate; + toml_datum_t k; + + dir = toml_string_in(table, "directory"); + if (dir.ok) { + log_dbg("Found directory type: %s", dir.u.s); + if (strlen(dir.u.s) > DATUMSZ) { + log_err("Directory name too long: %s", dir.u.s); + free(dir.u.s); + return -1; + } + if (strcmp(dir.u.s, "DHT") == 0) + conf->pol = DIR_DHT; + else if (strcmp(dir.u.s, "dht") == 0) + conf->pol = DIR_DHT; + else { + log_err("Unknown directory type: %s", dir.u.s); + free(dir.u.s); + return -EINVAL; + } + free(dir.u.s); + } + + switch(conf->pol) { + case DIR_DHT: + log_info("Using DHT directory policy."); + alpha = toml_int_in(table, "dht_alpha"); + if (alpha.ok) { + if (!BETWEEN(alpha.u.i, + DHT_ALPHA_MIN, DHT_ALPHA_MAX)) { + log_err("Invalid alpha value: %ld", + (long) alpha.u.i); + return -EINVAL; + } + DHT(conf, alpha) = alpha.u.i; + } + t_expire = toml_int_in(table, "dht_t_expire"); + if (t_expire.ok) { + if (!BETWEEN(t_expire.u.i, + DHT_T_EXPIRE_MIN, DHT_T_EXPIRE_MAX)) { + log_err("Invalid expire time: %ld", + (long) t_expire.u.i); + return -EINVAL; + } + DHT(conf, t_expire) = t_expire.u.i; + } + t_refresh = toml_int_in(table, "dht_t_refresh"); + if (t_refresh.ok) { + if (!BETWEEN(t_refresh.u.i, + DHT_T_REFRESH_MIN, DHT_T_REFRESH_MAX)) { + log_err("Invalid refresh time: %ld", + (long) t_refresh.u.i); + return -EINVAL; + } + DHT(conf, t_refresh) = t_refresh.u.i; + } + t_replicate = toml_int_in(table, "dht_t_replicate"); + if (t_replicate.ok) { + if (!BETWEEN(t_replicate.u.i, + DHT_T_REPLICATE_MIN, DHT_T_REPLICATE_MAX)) { + log_err("Invalid replication time: %ld", + (long) t_replicate.u.i); + return -EINVAL; + } + DHT(conf, t_replicate) = t_replicate.u.i; + } + k = toml_int_in(table, "dht_k"); + if (k.ok) { + if (!BETWEEN(k.u.i, DHT_K_MIN, DHT_K_MAX)) { + log_err("Invalid replication factor: %ld", + (long) k.u.i); + return -EINVAL; + } + DHT(conf, k) = k.u.i; + } + break; + default: + assert(false); + break; + } + + return 0; +} + +static int toml_routing(toml_table_t * table, + struct dt_config * conf) +{ + toml_datum_t routing; + toml_datum_t t_recalc; + toml_datum_t t_update; + toml_datum_t t_timeo; + + routing = toml_string_in(table, "routing"); + if (routing.ok) { + if (strcmp(routing.u.s, "link-state") == 0) { + conf->routing.pol = ROUTING_LINK_STATE; + conf->routing.ls.pol = LS_SIMPLE; + } else if (strcmp(routing.u.s, "lfa") == 0) { + conf->routing.pol = ROUTING_LINK_STATE; + conf->routing.ls.pol = LS_LFA; + } else if (strcmp(routing.u.s, "ecmp") == 0) { + conf->routing.pol = ROUTING_LINK_STATE; + conf->routing.ls.pol = LS_ECMP; + } else { + conf->routing.pol = ROUTING_INVALID; + return -EINVAL; + } + free(routing.u.s); + } + + switch (conf->routing.pol) { + case ROUTING_LINK_STATE: + log_info("Using Link State routing policy."); + t_recalc = toml_int_in(table, "ls_t_recalc"); + if (t_recalc.ok) { + if (t_recalc.u.i < 1) { + log_err("Invalid ls_t_recalc value: %ld", + (long) t_recalc.u.i); + return -EINVAL; + } + conf->routing.ls.t_recalc = t_recalc.u.i; + } + t_update = toml_int_in(table, "ls_t_update"); + if (t_update.ok) { + if (t_update.u.i < 1) { + log_err("Invalid ls_t_update value: %ld", + (long) t_update.u.i); + return -EINVAL; + } + conf->routing.ls.t_update = t_update.u.i; + } + t_timeo = toml_int_in(table, "ls_t_timeo"); + if (t_timeo.ok) { + if (t_timeo.u.i < 1) { + log_err("Invalid ls_t_timeo value: %ld", + (long) t_timeo.u.i); + return -EINVAL; + } + conf->routing.ls.t_timeo = t_timeo.u.i; + } + break; + default: + log_err("Invalid routing policy: %d", conf->routing.pol); + return -EINVAL; + } + + return 0; +} + +static int toml_addr_auth(toml_table_t * table, + struct uni_config * conf) +{ + toml_datum_t addr_auth; + + addr_auth = toml_string_in(table, "addr-auth"); + if (addr_auth.ok) { + if (strcmp(addr_auth.u.s, "flat") == 0) + conf->addr_auth_type = ADDR_AUTH_FLAT_RANDOM; + else + conf->addr_auth_type = ADDR_AUTH_INVALID; + free(addr_auth.u.s); + } + + if (conf->addr_auth_type == ADDR_AUTH_INVALID) + return -1; + + return 0; +} + +static int toml_congestion(toml_table_t * table, + struct uni_config * conf) +{ + toml_datum_t congestion; + + congestion = toml_string_in(table, "congestion"); + if (congestion.ok) { + if (strcmp(congestion.u.s, "none") == 0) + conf->cong_avoid = CA_NONE; + else if (strcmp(congestion.u.s, "lfa") == 0) + conf->cong_avoid = CA_MB_ECN; + else + conf->cong_avoid = CA_INVALID; + free(congestion.u.s); + + } + + if (conf->cong_avoid == CA_INVALID) + return -1; + + return 0; +} + +static int toml_dt(toml_table_t * table, + struct dt_config * conf) +{ + toml_datum_t addr; + toml_datum_t eid; + toml_datum_t ttl; + + addr = toml_int_in(table, "addr_size"); + if (addr.ok) + conf->addr_size = addr.u.i; + + eid = toml_int_in(table, "eid_size"); + if (eid.ok) + conf->eid_size = eid.u.i; + + ttl = toml_int_in(table, "max_ttl"); + if (ttl.ok) + conf->max_ttl = ttl.u.i; + + if (toml_routing(table, conf) < 0) { + log_err("Invalid routing option."); + return -1; + } + + return 0; +} + +static int toml_unicast(toml_table_t * table, + struct ipcp_config * conf) +{ + *conf = uni_default_conf; + + if (toml_dir(table, &conf->unicast.dir) < 0) { + log_err("Invalid directory configuration."); + return -1; + } + + if (toml_dt(table, &conf->unicast.dt) < 0) { + log_err("Invalid DT configuration."); + return -1; + } + + if (toml_addr_auth(table, &conf->unicast) < 0) { + log_err("Invalid address authority"); + return -1; + } + + if (toml_congestion(table, &conf->unicast) < 0) { + log_err("Invalid congestion avoidance algorithm."); + return -1; + } + + + return 0; +} + +static int toml_autobind(toml_table_t * table, + pid_t pid, + const char * name, + const char * layer) +{ + toml_datum_t autobind; + + autobind = toml_bool_in(table, "autobind"); + if (!autobind.ok) + return 0; + + if (bind_process(pid, name) < 0) { + log_err("Failed to bind IPCP process %d to %s.", pid, name); + return -1; + } + + if (layer != NULL && bind_process(pid, layer) < 0) { + log_err("Failed to bind IPCP process %d to %s.", pid, layer); + return -1; + } + + return 0; +} + +static int toml_register(toml_table_t * table, + pid_t pid) +{ + toml_array_t * reg; + int i; + int ret = 0; + struct name_info info = { + .pol_lb = LB_SPILL + }; + + reg = toml_array_in(table, "reg"); + if (reg == NULL) + return 0; + + for (i = 0; ret == 0; i++) { + toml_datum_t name; + + name = toml_string_at(reg, i); + if (!name.ok) + break; + + log_dbg("Registering %s in %d", name.u.s, pid); + + strcpy(info.name, name.u.s); + + ret = name_create(&info); + if (ret < 0 && ret != -ENAME) { + free(name.u.s); + break; + } + + ret = name_reg(name.u.s, pid); + free(name.u.s); + } + + return ret; +} + +static int toml_connect(toml_table_t * table, + pid_t pid) +{ + toml_array_t * conn; + int i; + int ret = 0; + + conn = toml_array_in(table, "conn"); + if (conn == NULL) + return 0; + + for (i=0; ret == 0; i++) { + toml_datum_t dst; + qosspec_t qs = qos_raw; + + dst = toml_string_at(conn, i); + if (!dst.ok) + break; + + log_dbg("Connecting %d to %s", pid, dst.u.s); + + ret = connect_ipcp(pid, dst.u.s, MGMT_COMP, qs); + if (ret == 0) + ret = connect_ipcp(pid, dst.u.s, DT_COMP, qs); + + free(dst.u.s); + } + + return ret; +} + +static int toml_ipcp(toml_table_t * table, + struct ipcp_info * info, + struct ipcp_config * conf) +{ + toml_datum_t bootstrap; + toml_datum_t enrol; + int ret; + + log_dbg("Found IPCP %s in configuration file.", info->name); + + if (create_ipcp(info) < 0) { + log_err("Failed to create IPCP %s.", info->name); + return -1; + } + + bootstrap = toml_string_in(table, "bootstrap"); + enrol = toml_string_in(table, "enrol"); + + if (bootstrap.ok && enrol.ok) { + log_err("Ignoring bootstrap for IPCP %s.", info->name); + free(bootstrap.u.s); + bootstrap.ok = false; + } + + if (!bootstrap.ok && !enrol.ok) { + log_dbg("Nothing more to do for %s.", info->name); + return 0; + } + + if (enrol.ok) { + struct layer_info layer; + ret = enroll_ipcp(info->pid, enrol.u.s); + free(enrol.u.s); + if (ret < 0) { + log_err("Failed to enrol %s.", info->name); + return -1; + } + + if (reg_get_ipcp(info, &layer) < 0) + return -1; + + if (toml_autobind(table, info->pid, info->name, layer.name)) + return -1; + + if (toml_register(table, info->pid) < 0) { + log_err("Failed to register names."); + return -1; + } + + if (toml_connect(table, info->pid) < 0) { + log_err("Failed to register names."); + return -1; + } + + return 0; + } + + assert(bootstrap.ok); + + if (strlen(bootstrap.u.s) > LAYER_NAME_SIZE) { + log_err("Layer name too long: %s", bootstrap.u.s); + free(bootstrap.u.s); + return -1; + } + + switch (conf->type) { + case IPCP_LOCAL: + ret = toml_local(table, conf); + break; + case IPCP_ETH_DIX: + ret = toml_eth_dix(table, conf); + break; + case IPCP_ETH_LLC: + ret = toml_eth_llc(table, conf); + break; + case IPCP_UDP4: + ret = toml_udp4(table, conf); + break; + case IPCP_UDP6: + ret = toml_udp6(table, conf); + break; + case IPCP_BROADCAST: + ret = toml_broadcast(table, conf); + break; + case IPCP_UNICAST: + ret = toml_unicast(table, conf); + break; + default: + log_err("Invalid IPCP type"); + ret = -1; + } + + if (ret < 0) + return -1; + + strcpy(conf->layer_info.name, bootstrap.u.s); + free(bootstrap.u.s); + + if (bootstrap_ipcp(info->pid, conf) < 0) + return -1; + + if (toml_autobind(table, info->pid, info->name, + conf->layer_info.name) < 0) + return -1; + + if (toml_register(table, info->pid) < 0) { + log_err("Failed to register names."); + return -1; + } + + return 0; +} + +static int toml_ipcp_list(toml_table_t * table, + enum ipcp_type type) +{ + int i = 0; + int ret = 0; + + for (i = 0; ret == 0; i++) { + const char * key; + struct ipcp_info info; + struct ipcp_config conf; + + memset(&conf, 0, sizeof(conf)); + memset(&info, 0, sizeof(info)); + + key = toml_key_in(table, i); + if (key == NULL) + break; + + if (strlen(key) > IPCP_NAME_SIZE) { + log_err("IPCP name too long: %s,", key); + return -1; + } + + info.type = type; + strcpy(info.name, key); + conf.type = type; + + ret = toml_ipcp(toml_table_in(table, key), &info, &conf); + } + + return ret; +} + +static int args_to_argv(const char * prog, + const char * args, + char *** argv) +{ + char * tok; + char * str; + int argc = 0; + + str = (char *) args; + + if (str != NULL) { + tok = str; + while (*(tok += strspn(tok, " ")) != '\0') { + tok += strcspn(tok, " "); + argc++; + } + } + + *argv = malloc((argc + 2) * sizeof(**argv)); + if (*argv == NULL) + goto fail_malloc; + + (*argv)[0] = strdup(prog); + if ((*argv)[0] == NULL) + goto fail_malloc2; + + argc = 1; + + if (str == NULL) + goto finish; + + tok = str; + while (*(tok += strspn(tok, " ")) != '\0') { + size_t toklen = strcspn(tok, " "); + (*argv)[argc] = malloc((toklen + 1) * sizeof(***argv)); + if ((*argv)[argc] == NULL) + goto fail_malloc2; + + strncpy((*argv)[argc], tok, toklen); + (*argv)[argc++][toklen] = '\0'; + tok += toklen; + } + + finish: + (*argv)[argc] = NULL; + + return argc; + + fail_malloc2: + argvfree(*argv); + fail_malloc: + return -1; + +} + +static int toml_prog(const char * prog, + const char * args, + const char * name) +{ + uint16_t flags = 0; + int argc; + char ** exec; + int ret; + + if (args != NULL) + flags |= BIND_AUTO; + + argc = args_to_argv(prog, args, &exec); + if (argc < 0) { + log_err("Failed to parse arguments: %s", args); + return -1; + } + + ret = bind_program(exec, name, flags); + if (ret < 0) + log_err("Failed to bind program %s %s for name %s.", + prog, args, name); + + argvfree(exec); + + return ret; +} + +static int toml_prog_list(toml_array_t * progs, + toml_array_t * args, + const char * name) +{ + int ret = 0; + int i; + + for (i = 0; ret == 0; i++) { + toml_datum_t prog; + toml_datum_t arg; + + prog = toml_string_at(progs, i); + if (!prog.ok) + break; + + if (args == NULL) { + ret = toml_prog(prog.u.s, NULL, name); + } else { + arg = toml_string_at(args, i); + if (!arg.ok) { + args = NULL; /* no more arguments in list. */ + assert(arg.u.s == NULL); + } + + ret = toml_prog(prog.u.s, arg.u.s, name); + + if (arg.ok) + free(arg.u.s); + } + + free(prog.u.s); + } + + return ret; +} + +static int cp_chk_path(char * buf, + char * path) +{ + char * rp; + + assert(path != NULL); + + rp = realpath(path, NULL); + if (rp == NULL) { + log_err("Failed to check path %s: %s.", path, strerror(errno)); + goto fail_rp; + } + + if (strlen(rp) > NAME_PATH_SIZE) { + log_err("File path too long: %s.", rp); + goto fail_len; + } + + strcpy(buf, rp); + free(rp); + free(path); + + return 0; + + fail_len: + free(rp); + fail_rp: + free(path); + return -1; +} + +static int toml_name(toml_table_t * table, + const char * name) +{ + toml_array_t * progs; + toml_array_t * args; + toml_datum_t lb; + toml_datum_t senc; + toml_datum_t scrt; + toml_datum_t skey; + toml_datum_t cenc; + toml_datum_t ccrt; + toml_datum_t ckey; + + struct name_info info = { + .pol_lb = LB_SPILL + }; + + log_dbg("Found service name %s in configuration file.", name); + + if (strlen(name) > NAME_SIZE) { + log_err("Name too long: %s", name); + return -1; + } + + strcpy(info.name, name); + + lb = toml_string_in(table, "lb"); + if (lb.ok) { + if (strcmp(lb.u.s, "spill") == 0) + info.pol_lb = LB_SPILL; + else if (strcmp(lb.u.s, "round-robin") == 0) + info.pol_lb = LB_RR; + else + info.pol_lb = LB_INVALID; + free(lb.u.s); + } + + if (info.pol_lb == LB_INVALID) { + log_err("Invalid load-balancing policy for %s.", name); + return -1; + } + senc = toml_string_in(table, "server_enc_file"); + if (senc.ok && cp_chk_path(info.s.enc, senc.u.s) < 0) + return -1; + + scrt = toml_string_in(table, "server_crt_file"); + if (scrt.ok && cp_chk_path(info.s.crt, scrt.u.s) < 0) + return -1; + + skey = toml_string_in(table, "server_key_file"); + if (skey.ok && cp_chk_path(info.s.key, skey.u.s) < 0) + return -1; + + cenc = toml_string_in(table, "client_enc_file"); + if (cenc.ok && cp_chk_path(info.c.enc, cenc.u.s) < 0) + return -1; + + ccrt = toml_string_in(table, "client_crt_file"); + if (ccrt.ok && cp_chk_path(info.c.crt, ccrt.u.s) < 0) + return -1; + + ckey = toml_string_in(table, "client_key_file"); + if (ckey.ok && cp_chk_path(info.c.key, ckey.u.s) < 0) + return -1; + + if (name_create(&info) < 0) { + log_err("Failed to create name %s.", name); + return -1; + } + + progs = toml_array_in(table, "prog"); + if (progs == NULL) + return 0; + + args = toml_array_in(table, "args"); + if (toml_prog_list(progs, args, name) < 0) + return -1; + + return 0; +} + +static int toml_name_list(toml_table_t * table) +{ + int i = 0; + int ret = 0; + + for (i = 0; ret == 0; i++) { + const char * key; + + key = toml_key_in(table, i); + if (key == NULL) + break; + + ret = toml_name(toml_table_in(table, key), key); + } + + return ret; + return 0; +} + +static int toml_toplevel(toml_table_t * table, + const char * key) +{ + toml_table_t * subtable; + + subtable = toml_table_in(table, key); + if (strcmp(key, "name") == 0) + return toml_name_list(subtable); + else if (strcmp(key, "local") == 0) + return toml_ipcp_list(subtable, IPCP_LOCAL); + else if (strcmp(key, "eth-dix") == 0) + return toml_ipcp_list(subtable, IPCP_ETH_DIX); + else if (strcmp(key, "eth-llc") == 0) + return toml_ipcp_list(subtable, IPCP_ETH_LLC); + else if (strcmp(key, "udp4") == 0) + return toml_ipcp_list(subtable, IPCP_UDP4); + else if (strcmp(key, "udp6") == 0) + return toml_ipcp_list(subtable, IPCP_UDP6); + else if (strcmp(key, "broadcast") == 0) + return toml_ipcp_list(subtable, IPCP_BROADCAST); + else if (strcmp(key, "unicast") == 0) + return toml_ipcp_list(subtable, IPCP_UNICAST); + else + log_err("Unkown toplevel key: %s.", key); + return -1; +} + +static int toml_load(toml_table_t * table) +{ + int i = 0; + int ret = 0; + + for (i = 0; ret == 0; i++) { + const char * key; + + key = toml_key_in(table, i); + if (key == NULL) + break; + + ret = toml_toplevel(table, key); + } + + return ret; +} + +static int toml_cfg(FILE * fp) +{ + toml_table_t * table; + char errbuf[ERRBUFSZ + 1]; + + assert(fp != NULL); + + table = toml_parse_file(fp, errbuf, sizeof(errbuf)); + if (table == NULL) { + log_err("Failed to parse config file: %s.", errbuf); + goto fail_parse; + } + + if (toml_load(table) < 0) { + log_err("Failed to load configuration."); + goto fail_load; + } + + toml_free(table); + + return 0; + + fail_load: + toml_free(table); + fail_parse: + return -1; +} + +int irm_configure(const char * path) +{ + FILE * fp; + char * rp; + + if (path == NULL) + return 0; + + rp = realpath(path, NULL); + if (rp == NULL) { + log_err("Failed to check path for %s: %s.", + path, strerror(errno)); + goto fail_resolve; + } + + log_info("Reading configuration from file %s", rp); + + fp = fopen(rp, "r"); + if (fp == NULL) { + log_err("Failed to open config file: %s\n", strerror(errno)); + goto fail_fopen; + } + + if (toml_cfg(fp) < 0) { + log_err("Failed to load config file."); + goto fail_cfg; + } + + fclose(fp); + free(rp); + + return 0; + + fail_cfg: + fclose(fp); + fail_fopen: + free(rp); + fail_resolve: + return -1; +} + +#endif /* HAVE_TOML */ diff --git a/src/ipcpd/broadcast/enroll.c b/src/irmd/configfile.h index 143f16d5..3ccf53fd 100644 --- a/src/ipcpd/broadcast/enroll.c +++ b/src/irmd/configfile.h @@ -1,7 +1,7 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * - * Enrollment Task + * The IPC Resource Manager / Configuration from file * * Dimitri Staessens <dimitri@ouroboros.rocks> * Sander Vrijders <sander@ouroboros.rocks> @@ -19,6 +19,11 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., http://www.fsf.org/about/contact/. */ -#define BUILD_IPCP_BROADCAST -#include "common/enroll.c" + +#ifndef OUROBOROS_IRMD_CONFIGURATION_H +#define OUROBOROS_IRMD_CONFIGURATION_H + +int irm_configure(const char * path); + +#endif /* OUROBOROS_IRMD_CONFIGURATION_H */ diff --git a/src/irmd/ipcp.c b/src/irmd/ipcp.c index ae5325c5..d261fc57 100644 --- a/src/irmd/ipcp.c +++ b/src/irmd/ipcp.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * The API to instruct IPCPs * @@ -20,195 +20,183 @@ * Foundation, Inc., http://www.fsf.org/about/contact/. */ -#define _POSIX_C_SOURCE 199309L +#define _POSIX_C_SOURCE 200112L #include "config.h" #define OUROBOROS_PREFIX "irmd/ipcp" -#include <ouroboros/logs.h> #include <ouroboros/errno.h> -#include <ouroboros/utils.h> +#include <ouroboros/flow.h> +#include <ouroboros/logs.h> #include <ouroboros/sockets.h> +#include <ouroboros/time.h> +#include <ouroboros/utils.h> #include "ipcp.h" -#include <stdlib.h> -#include <string.h> +#include <fcntl.h> +#include <pthread.h> #include <signal.h> +#include <spawn.h> #include <stdbool.h> -#include <pthread.h> +#include <stdlib.h> +#include <string.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/socket.h> #include <sys/time.h> -#include <spawn.h> -ipcp_msg_t * send_recv_ipcp_msg(pid_t pid, - ipcp_msg_t * msg) +static char * str_ipcp_cmd(int code) { - int sockfd = 0; - uint8_t buf[SOCK_BUF_SIZE]; - char * sock_path = NULL; - ssize_t len; - ipcp_msg_t * recv_msg = NULL; - struct timeval tv; - - if (kill(pid, 0) < 0) - return NULL; - - sock_path = ipcp_sock_path(pid); - if (sock_path == NULL) - return NULL; - - sockfd = client_socket_open(sock_path); - if (sockfd < 0) { - free(sock_path); - return NULL; - } - - free(sock_path); - - len = ipcp_msg__get_packed_size(msg); - if (len == 0) { - close(sockfd); - return NULL; - } - - switch (msg->code) { - case IPCP_MSG_CODE__IPCP_BOOTSTRAP: - tv.tv_sec = BOOTSTRAP_TIMEOUT / 1000; - tv.tv_usec = (BOOTSTRAP_TIMEOUT % 1000) * 1000; - break; - case IPCP_MSG_CODE__IPCP_ENROLL: - tv.tv_sec = ENROLL_TIMEOUT / 1000; - tv.tv_usec = (ENROLL_TIMEOUT % 1000) * 1000; - break; - case IPCP_MSG_CODE__IPCP_REG: - tv.tv_sec = REG_TIMEOUT / 1000; - tv.tv_usec = (REG_TIMEOUT % 1000) * 1000; - break; - case IPCP_MSG_CODE__IPCP_QUERY: - tv.tv_sec = QUERY_TIMEOUT / 1000; - tv.tv_usec = (QUERY_TIMEOUT % 1000) * 1000; - break; - case IPCP_MSG_CODE__IPCP_CONNECT: - tv.tv_sec = CONNECT_TIMEOUT / 1000; - tv.tv_usec = (CONNECT_TIMEOUT % 1000) * 1000; - break; - default: - tv.tv_sec = SOCKET_TIMEOUT / 1000; - tv.tv_usec = (SOCKET_TIMEOUT % 1000) * 1000; - break; - } - - if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, - (void *) &tv, sizeof(tv))) - log_warn("Failed to set timeout on socket."); - - pthread_cleanup_push(__cleanup_close_ptr, (void *) &sockfd); - - ipcp_msg__pack(msg, buf); - - if (write(sockfd, buf, len) != -1) - len = read(sockfd, buf, SOCK_BUF_SIZE); - - if (len > 0) - recv_msg = ipcp_msg__unpack(NULL, len, buf); - - pthread_cleanup_pop(true); - - return recv_msg; + switch (code) { + case IPCP_MSG_CODE__IPCP_BOOTSTRAP: + return "bootstrap"; + case IPCP_MSG_CODE__IPCP_ENROLL: + return "enroll"; + case IPCP_MSG_CODE__IPCP_CONNECT: + return "connect"; + case IPCP_MSG_CODE__IPCP_DISCONNECT: + return "disconnect"; + case IPCP_MSG_CODE__IPCP_REG: + return "reg"; + case IPCP_MSG_CODE__IPCP_UNREG: + return "unreg"; + case IPCP_MSG_CODE__IPCP_QUERY: + return "query"; + case IPCP_MSG_CODE__IPCP_FLOW_JOIN: + return "join"; + case IPCP_MSG_CODE__IPCP_FLOW_ALLOC: + return "alloc"; + case IPCP_MSG_CODE__IPCP_FLOW_ALLOC_RESP: + return "alloc_resp"; + case IPCP_MSG_CODE__IPCP_FLOW_DEALLOC: + return "dealloc"; + default: + assert(false); + return "unknown"; + } } -pid_t ipcp_create(const char * name, - enum ipcp_type ipcp_type) +ipcp_msg_t * send_recv_ipcp_msg(pid_t pid, + ipcp_msg_t * msg) { - pid_t pid = -1; - char * ipcp_dir = "/sbin/"; - char * exec_name = NULL; - char irmd_pid[10]; - char full_name[256]; - char * argv[5]; - - switch(ipcp_type) { - case IPCP_UNICAST: - exec_name = IPCP_UNICAST_EXEC; + int sockfd; + uint8_t buf[SOCK_BUF_SIZE]; + char * spath; + ssize_t len; + struct timeval tv; + struct timespec tic; + struct timespec toc; + bool may_fail = false; + + if (kill(pid, 0) < 0) + return NULL; + + spath = sock_path(pid, IPCP_SOCK_PATH_PREFIX); + if (spath == NULL) { + log_err("Failed to get IPCP socket path for pid %d.", pid); + return NULL; + } + + sockfd = client_socket_open(spath); + if (sockfd < 0) { + log_err("Failed to open client socket at %s.", spath); + free(spath); + return NULL; + } + + free(spath); + + len = ipcp_msg__get_packed_size(msg); + if (len == 0 || len >= SOCK_BUF_SIZE) { + log_warn("IPCP message has invalid size: %zd.", len); + close(sockfd); + return NULL; + } + + switch (msg->code) { + case IPCP_MSG_CODE__IPCP_BOOTSTRAP: + tv.tv_sec = BOOTSTRAP_TIMEOUT / 1000; + tv.tv_usec = (BOOTSTRAP_TIMEOUT % 1000) * 1000; + break; + case IPCP_MSG_CODE__IPCP_ENROLL: + tv.tv_sec = ENROLL_TIMEOUT / 1000; + tv.tv_usec = (ENROLL_TIMEOUT % 1000) * 1000; break; - case IPCP_BROADCAST: - exec_name = IPCP_BROADCAST_EXEC; + case IPCP_MSG_CODE__IPCP_REG: + tv.tv_sec = REG_TIMEOUT / 1000; + tv.tv_usec = (REG_TIMEOUT % 1000) * 1000; break; - case IPCP_UDP: - exec_name = IPCP_UDP_EXEC; + case IPCP_MSG_CODE__IPCP_QUERY: + may_fail = true; /* name not always in Layer */ + tv.tv_sec = QUERY_TIMEOUT / 1000; + tv.tv_usec = (QUERY_TIMEOUT % 1000) * 1000; break; - case IPCP_ETH_LLC: - exec_name = IPCP_ETH_LLC_EXEC; + case IPCP_MSG_CODE__IPCP_CONNECT: + tv.tv_sec = CONNECT_TIMEOUT / 1000; + tv.tv_usec = (CONNECT_TIMEOUT % 1000) * 1000; break; - case IPCP_ETH_DIX: - exec_name = IPCP_ETH_DIX_EXEC; + case IPCP_MSG_CODE__IPCP_FLOW_ALLOC: + tv.tv_sec = FLOW_ALLOC_TIMEOUT / 1000; + tv.tv_usec = (FLOW_ALLOC_TIMEOUT % 1000) * 1000; break; - case IPCP_LOCAL: - exec_name = IPCP_LOCAL_EXEC; + case IPCP_MSG_CODE__IPCP_FLOW_DEALLOC: + may_fail = true; + tv.tv_sec = 0; /* FIX DEALLOC: don't wait for dealloc */ + tv.tv_usec = 500; break; default: - return -1; + tv.tv_sec = SOCKET_TIMEOUT / 1000; + tv.tv_usec = (SOCKET_TIMEOUT % 1000) * 1000; + break; } - if (strlen(exec_name) == 0) { - log_err("IPCP type not installed."); - return -1; - } + if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, + (void *) &tv, sizeof(tv))) + log_warn("Failed to set timeout on socket."); - sprintf(irmd_pid, "%u", getpid()); + pthread_cleanup_push(__cleanup_close_ptr, (void *) &sockfd); - strcpy(full_name, INSTALL_PREFIX); - strcat(full_name, ipcp_dir); - strcat(full_name, exec_name); + ipcp_msg__pack(msg, buf); - /* log_file to be placed at the end */ - argv[0] = full_name; - argv[1] = irmd_pid; - argv[2] = (char *) name; - if (log_syslog) - argv[3] = "1"; - else - argv[3] = NULL; + clock_gettime(CLOCK_REALTIME, &tic); - argv[4] = NULL; + if (write(sockfd, buf, len) != -1) + len = read(sockfd, buf, SOCK_BUF_SIZE); - if (posix_spawn(&pid, argv[0], NULL, NULL, argv, NULL)) { - log_err("Failed to spawn new process"); - return -1; - } + clock_gettime(CLOCK_REALTIME, &toc); - return pid; -} + pthread_cleanup_pop(true); /* close socket */ -int ipcp_destroy(pid_t pid) -{ - if (kill(pid, SIGTERM)) { - log_err("Failed to destroy IPCP"); - return -1; + if (len > 0) + return ipcp_msg__unpack(NULL, len, buf); + + if (errno == EAGAIN && !may_fail) { + int diff = ts_diff_ms(&toc, &tic); + log_warn("IPCP %s timed out after %d ms.", + str_ipcp_cmd(msg->code), diff); } - return 0; + return NULL; } -int ipcp_bootstrap(pid_t pid, - ipcp_config_msg_t * conf, - struct layer_info * info) +int ipcp_bootstrap(pid_t pid, + struct ipcp_config * conf, + struct layer_info * info) { - ipcp_msg_t msg = IPCP_MSG__INIT; - ipcp_msg_t * recv_msg = NULL; - int ret = -1; + ipcp_msg_t msg = IPCP_MSG__INIT; + ipcp_msg_t * recv_msg; + int ret; if (conf == NULL) return -EINVAL; msg.code = IPCP_MSG_CODE__IPCP_BOOTSTRAP; - msg.conf = conf; + msg.conf = ipcp_config_s_to_msg(conf); recv_msg = send_recv_ipcp_msg(pid, &msg); + ipcp_config_msg__free_unpacked(msg.conf, NULL); if (recv_msg == NULL) return -EIPCP; @@ -229,7 +217,7 @@ int ipcp_bootstrap(pid_t pid, } info->dir_hash_algo = recv_msg->layer_info->dir_hash_algo; - strcpy(info->layer_name, recv_msg->layer_info->layer_name); + strcpy(info->name, recv_msg->layer_info->name); ret = recv_msg->result; ipcp_msg__free_unpacked(recv_msg, NULL); @@ -241,9 +229,9 @@ int ipcp_enroll(pid_t pid, const char * dst, struct layer_info * info) { - ipcp_msg_t msg = IPCP_MSG__INIT; - ipcp_msg_t * recv_msg = NULL; - int ret = -1; + ipcp_msg_t msg = IPCP_MSG__INIT; + ipcp_msg_t * recv_msg; + int ret; if (dst == NULL) return -EINVAL; @@ -272,7 +260,7 @@ int ipcp_enroll(pid_t pid, } info->dir_hash_algo = recv_msg->layer_info->dir_hash_algo; - strcpy(info->layer_name, recv_msg->layer_info->layer_name); + strcpy(info->name, recv_msg->layer_info->name); ipcp_msg__free_unpacked(recv_msg, NULL); @@ -284,20 +272,19 @@ int ipcp_connect(pid_t pid, const char * component, qosspec_t qs) { - ipcp_msg_t msg = IPCP_MSG__INIT; - qosspec_msg_t qs_msg = QOSSPEC_MSG__INIT; - int ret = -1; + ipcp_msg_t msg = IPCP_MSG__INIT; ipcp_msg_t * recv_msg; + int ret; msg.code = IPCP_MSG_CODE__IPCP_CONNECT; msg.dst = (char *) dst; msg.comp = (char *) component; msg.has_pid = true; msg.pid = pid; - qs_msg = spec_to_msg(&qs); - msg.qosspec = &qs_msg; + msg.qosspec = qos_spec_s_to_msg(&qs); recv_msg = send_recv_ipcp_msg(pid, &msg); + free(msg.qosspec); if (recv_msg == NULL) return -EIPCP; @@ -316,9 +303,9 @@ int ipcp_disconnect(pid_t pid, const char * dst, const char * component) { - ipcp_msg_t msg = IPCP_MSG__INIT; - ipcp_msg_t * recv_msg = NULL; - int ret = -1; + ipcp_msg_t msg = IPCP_MSG__INIT; + ipcp_msg_t * recv_msg; + int ret; msg.code = IPCP_MSG_CODE__IPCP_DISCONNECT; msg.dst = (char *) dst; @@ -341,20 +328,17 @@ int ipcp_disconnect(pid_t pid, return ret; } -int ipcp_reg(pid_t pid, - const uint8_t * hash, - size_t len) +int ipcp_reg(pid_t pid, + const buffer_t hash) { - ipcp_msg_t msg = IPCP_MSG__INIT; - ipcp_msg_t * recv_msg = NULL; - int ret = -1; - - assert(hash); + ipcp_msg_t msg = IPCP_MSG__INIT; + ipcp_msg_t * recv_msg; + int ret; msg.code = IPCP_MSG_CODE__IPCP_REG; msg.has_hash = true; - msg.hash.len = len; - msg.hash.data = (uint8_t *)hash; + msg.hash.data = (uint8_t *) hash.data; + msg.hash.len = hash.len; recv_msg = send_recv_ipcp_msg(pid, &msg); if (recv_msg == NULL) @@ -371,18 +355,17 @@ int ipcp_reg(pid_t pid, return ret; } -int ipcp_unreg(pid_t pid, - const uint8_t * hash, - size_t len) +int ipcp_unreg(pid_t pid, + const buffer_t hash) { - ipcp_msg_t msg = IPCP_MSG__INIT; - ipcp_msg_t * recv_msg = NULL; - int ret = -1; + ipcp_msg_t msg = IPCP_MSG__INIT; + ipcp_msg_t * recv_msg; + int ret; msg.code = IPCP_MSG_CODE__IPCP_UNREG; msg.has_hash = true; - msg.hash.len = len; - msg.hash.data = (uint8_t *) hash; + msg.hash.data = (uint8_t *) hash.data; + msg.hash.len = hash.len; recv_msg = send_recv_ipcp_msg(pid, &msg); if (recv_msg == NULL) @@ -399,18 +382,17 @@ int ipcp_unreg(pid_t pid, return ret; } -int ipcp_query(pid_t pid, - const uint8_t * hash, - size_t len) +int ipcp_query(pid_t pid, + const buffer_t dst) { - ipcp_msg_t msg = IPCP_MSG__INIT; - ipcp_msg_t * recv_msg = NULL; - int ret = -1; + ipcp_msg_t msg = IPCP_MSG__INIT; + ipcp_msg_t * recv_msg; + int ret; msg.code = IPCP_MSG_CODE__IPCP_QUERY; msg.has_hash = true; - msg.hash.len = len; - msg.hash.data = (uint8_t *) hash; + msg.hash.data = (uint8_t *) dst.data; + msg.hash.len = dst.len; recv_msg = send_recv_ipcp_msg(pid, &msg); if (recv_msg == NULL) @@ -427,39 +409,27 @@ int ipcp_query(pid_t pid, return ret; } -static int __ipcp_flow_alloc(pid_t pid, - int flow_id, - pid_t n_pid, - const uint8_t * dst, - size_t len, - qosspec_t qs, - bool join, - const void * data, - size_t dlen) +int ipcp_flow_join(const struct flow_info * flow, + const buffer_t dst) { - ipcp_msg_t msg = IPCP_MSG__INIT; - qosspec_msg_t qs_msg; - ipcp_msg_t * recv_msg = NULL; - int ret = -1; - - assert(dst); + ipcp_msg_t msg = IPCP_MSG__INIT; + ipcp_msg_t * recv_msg; + int ret; - msg.code = join ? IPCP_MSG_CODE__IPCP_FLOW_JOIN - : IPCP_MSG_CODE__IPCP_FLOW_ALLOC; + msg.code = IPCP_MSG_CODE__IPCP_FLOW_JOIN; msg.has_flow_id = true; - msg.flow_id = flow_id; + msg.flow_id = flow->id; msg.has_pid = true; - msg.pid = n_pid; + msg.pid = flow->n_pid; + msg.has_uid = true; + msg.uid = flow->uid; msg.has_hash = true; - msg.hash.len = len; - msg.hash.data = (uint8_t *) dst; - qs_msg = spec_to_msg(&qs); - msg.qosspec = &qs_msg; - msg.has_pk = true; - msg.pk.data = (uint8_t *) data; - msg.pk.len = (uint32_t) dlen; + msg.hash.data = (uint8_t *) dst.data; + msg.hash.len = dst.len; + msg.has_pk = false; - recv_msg = send_recv_ipcp_msg(pid, &msg); + recv_msg = send_recv_ipcp_msg(flow->n_1_pid, &msg); + free(msg.qosspec); if (recv_msg == NULL) return -EIPCP; @@ -474,53 +444,70 @@ static int __ipcp_flow_alloc(pid_t pid, return ret; } -int ipcp_flow_alloc(pid_t pid, - int flow_id, - pid_t n_pid, - const uint8_t * dst, - size_t len, - qosspec_t qs, - const void * data, - size_t dlen) +int ipcp_flow_alloc(const struct flow_info * flow, + const buffer_t dst, + const buffer_t data) { - return __ipcp_flow_alloc(pid, flow_id, n_pid, dst, len, qs, false, - data, dlen); -} + ipcp_msg_t msg = IPCP_MSG__INIT; + ipcp_msg_t * recv_msg; + int ret; -int ipcp_flow_join(pid_t pid, - int flow_id, - pid_t n_pid, - const uint8_t * dst, - size_t len, - qosspec_t qs) -{ - return __ipcp_flow_alloc(pid, flow_id, n_pid, dst, len, qs, true, - NULL, 0); + msg.code = IPCP_MSG_CODE__IPCP_FLOW_ALLOC; + msg.has_flow_id = true; + msg.flow_id = flow->id; + msg.has_pid = true; + msg.pid = flow->n_pid; + msg.has_uid = true; + msg.uid = flow->uid; + msg.qosspec = qos_spec_s_to_msg(&flow->qs); + msg.has_hash = true; + msg.hash.data = (uint8_t *) dst.data; + msg.hash.len = dst.len; + msg.has_pk = true; + msg.pk.data = data.data; + msg.pk.len = data.len; + + recv_msg = send_recv_ipcp_msg(flow->n_1_pid, &msg); + free(msg.qosspec); + if (recv_msg == NULL) { + log_err("Did not receive message."); + return -EIPCP; + } + + if (!recv_msg->has_result) { + log_err("Message has no result"); + ipcp_msg__free_unpacked(recv_msg, NULL); + return -EIPCP; + } + + ret = recv_msg->result; + ipcp_msg__free_unpacked(recv_msg, NULL); + + return ret; } -int ipcp_flow_alloc_resp(pid_t pid, - int flow_id, - pid_t n_pid, - int response, - const void * data, - size_t len) +int ipcp_flow_alloc_resp(const struct flow_info * flow, + int response, + const buffer_t data) { - ipcp_msg_t msg = IPCP_MSG__INIT; - ipcp_msg_t * recv_msg = NULL; - int ret = -1; + ipcp_msg_t msg = IPCP_MSG__INIT; + ipcp_msg_t * recv_msg; + int ret; msg.code = IPCP_MSG_CODE__IPCP_FLOW_ALLOC_RESP; msg.has_flow_id = true; - msg.flow_id = flow_id; + msg.flow_id = flow->id; msg.has_pid = true; - msg.pid = n_pid; + msg.pid = flow->n_pid; + msg.has_uid = true; + msg.uid = flow->uid; msg.has_response = true; msg.response = response; - msg.has_pk = true; - msg.pk.data = (uint8_t *) data; - msg.pk.len = (uint32_t) len; + msg.has_pk = response == 0; + msg.pk.data = data.data; + msg.pk.len = data.len; - recv_msg = send_recv_ipcp_msg(pid, &msg); + recv_msg = send_recv_ipcp_msg(flow->n_1_pid, &msg); if (recv_msg == NULL) return -EIPCP; @@ -539,9 +526,9 @@ int ipcp_flow_dealloc(pid_t pid, int flow_id, time_t timeo) { - ipcp_msg_t msg = IPCP_MSG__INIT; - ipcp_msg_t * recv_msg = NULL; - int ret = -1; + ipcp_msg_t msg = IPCP_MSG__INIT; + ipcp_msg_t * recv_msg; + int ret; msg.code = IPCP_MSG_CODE__IPCP_FLOW_DEALLOC; msg.has_flow_id = true; diff --git a/src/irmd/ipcp.h b/src/irmd/ipcp.h index eb2361c7..b7413cd2 100644 --- a/src/irmd/ipcp.h +++ b/src/irmd/ipcp.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * The API for the IRM to instruct IPCPs * @@ -21,25 +21,19 @@ */ #include <ouroboros/ipcp.h> +#include <ouroboros/protobuf.h> #include <ouroboros/sockets.h> -#include <sys/types.h> - #ifndef OUROBOROS_IRMD_IPCP_H #define OUROBOROS_IRMD_IPCP_H -pid_t ipcp_create(const char * name, - enum ipcp_type ipcp_type); - -int ipcp_destroy(pid_t pid); - int ipcp_enroll(pid_t pid, const char * dst, struct layer_info * info); -int ipcp_bootstrap(pid_t pid, - ipcp_config_msg_t * conf, - struct layer_info * info); +int ipcp_bootstrap(pid_t pid, + struct ipcp_config * conf, + struct layer_info * info); int ipcp_connect(pid_t pid, const char * dst, @@ -50,40 +44,25 @@ int ipcp_disconnect(pid_t pid, const char * dst, const char * component); -int ipcp_reg(pid_t pid, - const uint8_t * hash, - size_t len); +int ipcp_reg(pid_t pid, + const buffer_t hash); -int ipcp_unreg(pid_t pid, - const uint8_t * hash, - size_t len); +int ipcp_unreg(pid_t pid, + const buffer_t hash); -int ipcp_query(pid_t pid, - const uint8_t * hash, - size_t len); +int ipcp_query(pid_t pid, + const buffer_t dst); -int ipcp_flow_alloc(pid_t pid, - int flow_id, - pid_t n_pid, - const uint8_t * dst, - size_t len, - qosspec_t qs, - const void * data, - size_t dlen); +int ipcp_flow_alloc(const struct flow_info * flow, + const buffer_t hash, + const buffer_t data); -int ipcp_flow_join(pid_t pid, - int flow_id, - pid_t n_pid, - const uint8_t * dst, - size_t len, - qosspec_t qs); +int ipcp_flow_join(const struct flow_info * flow, + const buffer_t dst); -int ipcp_flow_alloc_resp(pid_t pid, - int flow_id, - pid_t n_pid, - int response, - const void * data, - size_t len); +int ipcp_flow_alloc_resp(const struct flow_info * flow, + int response, + const buffer_t data); int ipcp_flow_dealloc(pid_t pid, int flow_id, diff --git a/src/irmd/irm_flow.c b/src/irmd/irm_flow.c deleted file mode 100644 index 75df7a80..00000000 --- a/src/irmd/irm_flow.c +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2021 - * - * The IPC Resource Manager - Flows - * - * Dimitri Staessens <dimitri@ouroboros.rocks> - * Sander Vrijders <sander@ouroboros.rocks> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., http://www.fsf.org/about/contact/. - */ - -#define _POSIX_C_SOURCE 200112L - -#include "config.h" - -#define OUROBOROS_PREFIX "irm_flow" - -#include <ouroboros/errno.h> -#include <ouroboros/logs.h> -#include <ouroboros/time_utils.h> -#include <ouroboros/pthread.h> - -#include "irm_flow.h" - -#include <stdlib.h> -#include <stdbool.h> -#include <assert.h> - -struct irm_flow * irm_flow_create(pid_t n_pid, - pid_t n_1_pid, - int flow_id, - qosspec_t qs) -{ - pthread_condattr_t cattr; - struct irm_flow * f = malloc(sizeof(*f)); - if (f == NULL) - goto fail_malloc; - - if (pthread_condattr_init(&cattr)) - goto fail_cattr; - -#ifndef __APPLE__ - pthread_condattr_setclock(&cattr, PTHREAD_COND_CLOCK); -#endif - if (pthread_cond_init(&f->state_cond, &cattr)) - goto fail_state_cond; - - if (pthread_mutex_init(&f->state_lock, NULL)) - goto fail_mutex; - - f->n_pid = n_pid; - f->n_1_pid = n_1_pid; - f->flow_id = flow_id; - f->qs = qs; - f->data = NULL; - f->len = 0; - - f->n_rb = shm_rbuff_create(n_pid, flow_id); - if (f->n_rb == NULL) { - log_err("Could not create ringbuffer for process %d.", n_pid); - goto fail_n_rbuff; - } - - f->n_1_rb = shm_rbuff_create(n_1_pid, flow_id); - if (f->n_1_rb == NULL) { - log_err("Could not create ringbuffer for process %d.", n_1_pid); - goto fail_n_1_rbuff; - } - - f->state = FLOW_ALLOC_PENDING; - - if (clock_gettime(CLOCK_MONOTONIC, &f->t0) < 0) - log_warn("Failed to set timestamp."); - - pthread_condattr_destroy(&cattr); - - return f; - - fail_n_1_rbuff: - shm_rbuff_destroy(f->n_rb); - fail_n_rbuff: - pthread_mutex_destroy(&f->state_lock); - fail_mutex: - pthread_cond_destroy(&f->state_cond); - fail_state_cond: - pthread_condattr_destroy(&cattr); - fail_cattr: - free(f); - fail_malloc: - return NULL; -} - -static void cancel_irm_destroy(void * o) -{ - struct irm_flow * f = (struct irm_flow *) o; - - pthread_mutex_unlock(&f->state_lock); - - pthread_cond_destroy(&f->state_cond); - pthread_mutex_destroy(&f->state_lock); - - shm_rbuff_destroy(f->n_rb); - shm_rbuff_destroy(f->n_1_rb); - - free(f); -} - -void irm_flow_destroy(struct irm_flow * f) -{ - assert(f); - - pthread_mutex_lock(&f->state_lock); - - assert(f->len == 0); - - if (f->state == FLOW_DESTROY) { - pthread_mutex_unlock(&f->state_lock); - return; - } - - if (f->state == FLOW_ALLOC_PENDING) - f->state = FLOW_DESTROY; - else - f->state = FLOW_NULL; - - pthread_cond_signal(&f->state_cond); - - pthread_cleanup_push(cancel_irm_destroy, f); - - while (f->state != FLOW_NULL) - pthread_cond_wait(&f->state_cond, &f->state_lock); - - pthread_cleanup_pop(true); -} - -enum flow_state irm_flow_get_state(struct irm_flow * f) -{ - enum flow_state state; - - assert(f); - - pthread_mutex_lock(&f->state_lock); - - state = f->state; - - pthread_mutex_unlock(&f->state_lock); - - return state; -} - -void irm_flow_set_state(struct irm_flow * f, - enum flow_state state) -{ - assert(f); - assert(state != FLOW_DESTROY); - - pthread_mutex_lock(&f->state_lock); - - f->state = state; - pthread_cond_broadcast(&f->state_cond); - - pthread_mutex_unlock(&f->state_lock); -} - -int irm_flow_wait_state(struct irm_flow * f, - enum flow_state state, - struct timespec * timeo) -{ - int ret = 0; - int s; - - struct timespec dl; - - assert(f); - assert(state != FLOW_NULL); - assert(state != FLOW_DESTROY); - assert(state != FLOW_DEALLOC_PENDING); - - if (timeo != NULL) { - clock_gettime(PTHREAD_COND_CLOCK, &dl); - ts_add(&dl, timeo, &dl); - } - - pthread_mutex_lock(&f->state_lock); - - assert(f->state != FLOW_NULL); - - pthread_cleanup_push(__cleanup_mutex_unlock, &f->state_lock); - - while (!(f->state == state || - f->state == FLOW_DESTROY || - f->state == FLOW_DEALLOC_PENDING) && - ret != -ETIMEDOUT) { - if (timeo == NULL) - ret = -pthread_cond_wait(&f->state_cond, - &f->state_lock); - else - ret = -pthread_cond_timedwait(&f->state_cond, - &f->state_lock, - &dl); - } - - if (f->state == FLOW_DESTROY || - f->state == FLOW_DEALLOC_PENDING || - ret == -ETIMEDOUT) { - f->state = FLOW_NULL; - pthread_cond_broadcast(&f->state_cond); - } - - s = f->state; - - pthread_cleanup_pop(true); - - return ret ? ret : s; -} diff --git a/src/irmd/irm_flow.h b/src/irmd/irm_flow.h deleted file mode 100644 index 35e7dc2c..00000000 --- a/src/irmd/irm_flow.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2021 - * - * The IPC Resource Manager - Flows - * - * Dimitri Staessens <dimitri@ouroboros.rocks> - * Sander Vrijders <sander@ouroboros.rocks> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., http://www.fsf.org/about/contact/. - */ - -#ifndef OUROBOROS_IRMD_IRM_FLOW_H -#define OUROBOROS_IRMD_IRM_FLOW_H - -#include <ouroboros/list.h> -#include <ouroboros/qos.h> -#include <ouroboros/shm_rbuff.h> - -#include <sys/types.h> -#include <pthread.h> -#include <time.h> - -enum flow_state { - FLOW_NULL = 0, - FLOW_ALLOC_PENDING, - FLOW_ALLOCATED, - FLOW_DEALLOC_PENDING, - FLOW_DESTROY -}; - -struct irm_flow { - struct list_head next; - - int flow_id; - - pid_t n_pid; - pid_t n_1_pid; - - qosspec_t qs; - void * data; - size_t len; - - struct shm_rbuff * n_rb; - struct shm_rbuff * n_1_rb; - - struct timespec t0; - - enum flow_state state; - pthread_cond_t state_cond; - pthread_mutex_t state_lock; -}; - -struct irm_flow * irm_flow_create(pid_t n_pid, - pid_t n_1_pid, - int flow_id, - qosspec_t qs); - -void irm_flow_destroy(struct irm_flow * f); - -enum flow_state irm_flow_get_state(struct irm_flow * f); - - -void irm_flow_set_state(struct irm_flow * f, - enum flow_state state); - -int irm_flow_wait_state(struct irm_flow * f, - enum flow_state state, - struct timespec * timeo); - -#endif /* OUROBOROS_IRMD_IRM_FLOW_H */ diff --git a/src/irmd/irmd.h b/src/irmd/irmd.h new file mode 100644 index 00000000..3e54904a --- /dev/null +++ b/src/irmd/irmd.h @@ -0,0 +1,54 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * The IPC Resource Manager + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#ifndef OUROBOROS_IRMD_H +#define OUROBOROS_IRMD_H + +#include <ouroboros/ipcp.h> +#include <ouroboros/irm.h> + +int create_ipcp(struct ipcp_info * info); + +int bootstrap_ipcp(pid_t pid, + struct ipcp_config * conf); + +int enroll_ipcp(pid_t pid, + const char * dst); + +int connect_ipcp(pid_t pid, + const char * dst, + const char * component, + qosspec_t qs); + +int name_create(struct name_info * info); + +int name_reg(const char * name, + pid_t pid); + +int bind_process(pid_t pid, + const char * name); + +int bind_program(char ** exec, + const char * name, + uint8_t flags); + +#endif /* OUROBOROS_IRMD_H*/ diff --git a/src/irmd/main.c b/src/irmd/main.c index 22d94136..ccb16017 100644 --- a/src/irmd/main.c +++ b/src/irmd/main.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * The IPC Resource Manager * @@ -22,6 +22,7 @@ #if defined(__linux__) || defined(__CYGWIN__) #define _DEFAULT_SOURCE +#define _GNU_SOURCE #else #define _POSIX_C_SOURCE 200809L #endif @@ -30,29 +31,35 @@ #define OUROBOROS_PREFIX "irmd" -#include <ouroboros/hash.h> +#include <ouroboros/bitmap.h> +#include <ouroboros/crypt.h> #include <ouroboros/errno.h> -#include <ouroboros/sockets.h> -#include <ouroboros/list.h> -#include <ouroboros/utils.h> +#include <ouroboros/flow.h> +#include <ouroboros/hash.h> #include <ouroboros/irm.h> +#include <ouroboros/list.h> #include <ouroboros/lockfile.h> -#include <ouroboros/shm_rbuff.h> -#include <ouroboros/shm_rdrbuff.h> -#include <ouroboros/bitmap.h> -#include <ouroboros/qos.h> -#include <ouroboros/time_utils.h> -#include <ouroboros/tpm.h> #include <ouroboros/logs.h> -#include <ouroboros/version.h> +#include <ouroboros/protobuf.h> #include <ouroboros/pthread.h> +#include <ouroboros/random.h> +#include <ouroboros/rib.h> +#include <ouroboros/ssm_pool.h> +#include <ouroboros/sockets.h> +#include <ouroboros/time.h> +#include <ouroboros/tpm.h> +#include <ouroboros/utils.h> +#include <ouroboros/version.h> -#include "utils.h" -#include "registry.h" -#include "irm_flow.h" -#include "proc_table.h" +#include "irmd.h" #include "ipcp.h" +#include "oap.h" +#include "reg/reg.h" +#include "configfile.h" +#include <dirent.h> +#include <grp.h> +#include <pwd.h> #include <sys/socket.h> #include <sys/un.h> #include <signal.h> @@ -69,62 +76,34 @@ #define IRMD_CLEANUP_TIMER ((IRMD_FLOW_TIMEOUT / 20) * MILLION) /* ns */ #define SHM_SAN_HOLDOFF 1000 /* ms */ -#define IPCP_HASH_LEN(e) hash_len(e->dir_hash_algo) -#define IB_LEN SOCK_BUF_SIZE +#define IPCP_HASH_LEN(p) hash_len((p)->dir_hash_algo) #define BIND_TIMEOUT 10 /* ms */ +#define TIMESYNC_SLACK 100 /* ms */ +#define OAP_SEEN_TIMER 20 /* s */ #define DEALLOC_TIME 300 /* s */ -enum init_state { - IPCP_NULL = 0, - IPCP_BOOT, - IPCP_LIVE -}; - -struct ipcp_entry { - struct list_head next; - - char * name; - pid_t pid; - enum ipcp_type type; - enum hash_algo dir_hash_algo; - char * layer; - - enum init_state state; - pthread_cond_t cond; - pthread_mutex_t lock; -}; - enum irm_state { IRMD_NULL = 0, - IRMD_RUNNING + IRMD_INIT, + IRMD_RUNNING, + IRMD_SHUTDOWN }; struct cmd { struct list_head next; - uint8_t cbuf[IB_LEN]; + uint8_t cbuf[SOCK_BUF_SIZE]; size_t len; int fd; }; struct { - struct list_head registry; /* registered names known */ - size_t n_names; /* number of names */ - - struct list_head ipcps; /* list of ipcps in system */ - size_t n_ipcps; /* number of ipcps */ - - struct list_head proc_table; /* processes */ - struct list_head prog_table; /* programs known */ - struct list_head spawned_pids; /* child processes */ - pthread_rwlock_t reg_lock; /* lock for registration info */ - - struct bmp * flow_ids; /* flow_ids for flows */ - struct list_head irm_flows; /* flow information */ - pthread_rwlock_t flows_lock; /* lock for flows */ - + bool log_stdout; /* log to stdout */ +#ifdef HAVE_TOML + char * cfg_file; /* configuration file path */ +#endif struct lockfile * lf; /* single irmd per system */ - struct shm_rdrbuff * rdrb; /* rdrbuff for packets */ + struct ssm_pool * gspp; /* pool for packets */ int sockfd; /* UNIX socket */ @@ -163,492 +142,265 @@ static void irmd_set_state(enum irm_state state) pthread_rwlock_unlock(&irmd.state_lock); } -static void clear_irm_flow(struct irm_flow * f) { - ssize_t idx; - - assert(f); - - if (f->len != 0) { - free(f->data); - f->len = 0; - } - - while ((idx = shm_rbuff_read(f->n_rb)) >= 0) - shm_rdrbuff_remove(irmd.rdrb, idx); - - while ((idx = shm_rbuff_read(f->n_1_rb)) >= 0) - shm_rdrbuff_remove(irmd.rdrb, idx); -} - -static struct irm_flow * get_irm_flow(int flow_id) +static pid_t spawn_program(char ** argv) { - struct list_head * pos = NULL; + pid_t pid; + struct stat s; - list_for_each(pos, &irmd.irm_flows) { - struct irm_flow * e = list_entry(pos, struct irm_flow, next); - if (e->flow_id == flow_id) - return e; + if (stat(argv[0], &s) != 0) { + log_warn("Program %s does not exist.", argv[0]); + return -1; } - return NULL; -} - -static struct irm_flow * get_irm_flow_n(pid_t n_pid) -{ - struct list_head * pos = NULL; - - list_for_each(pos, &irmd.irm_flows) { - struct irm_flow * e = list_entry(pos, struct irm_flow, next); - if (e->n_pid == n_pid && - irm_flow_get_state(e) == FLOW_ALLOC_PENDING) - return e; + if (!(s.st_mode & S_IXUSR)) { + log_warn("Program %s is not executable.", argv[0]); + return -1; } - return NULL; -} - -static struct ipcp_entry * ipcp_entry_create(const char * name, - enum ipcp_type type) -{ - struct ipcp_entry * e; - pthread_condattr_t cattr; - - e = malloc(sizeof(*e)); - if (e == NULL) - goto fail_malloc; - - e->layer = NULL; - e->type = type; - e->state = IPCP_BOOT; - e->name = strdup(name); - if (e->name == NULL) - goto fail_name; - - if (pthread_condattr_init(&cattr)) - goto fail_cattr; -#ifndef __APPLE__ - pthread_condattr_setclock(&cattr, PTHREAD_COND_CLOCK); -#endif - if (pthread_cond_init(&e->cond, &cattr)) - goto fail_cond; - - if (pthread_mutex_init(&e->lock, NULL)) - goto fail_mutex; - - - list_head_init(&e->next); - - pthread_condattr_destroy(&cattr); - - return e; - - fail_mutex: - pthread_cond_destroy(&e->cond); - fail_cond: - pthread_condattr_destroy(&cattr); - fail_cattr: - free(e->name); - fail_name: - free(e); - fail_malloc: - return NULL; -} - -static void ipcp_entry_destroy(struct ipcp_entry * e) -{ - assert(e); - - pthread_mutex_lock(&e->lock); - - while (e->state == IPCP_BOOT) - pthread_cond_wait(&e->cond, &e->lock); - - pthread_mutex_unlock(&e->lock); + if (posix_spawn(&pid, argv[0], NULL, NULL, argv, NULL)) { + log_err("Failed to spawn new process for %s.", argv[0]); + return -1; + } - free(e->name); - free(e->layer); - free(e); -} + log_info("Instantiated %s as process %d.", argv[0], pid); -static void ipcp_entry_set_state(struct ipcp_entry * e, - enum init_state state) -{ - pthread_mutex_lock(&e->lock); - e->state = state; - pthread_cond_broadcast(&e->cond); - pthread_mutex_unlock(&e->lock); + return pid; } -static int ipcp_entry_wait_boot(struct ipcp_entry * e) +static pid_t spawn_ipcp(struct ipcp_info * info) { - int ret = 0; - struct timespec dl; - struct timespec to = {SOCKET_TIMEOUT / 1000, - (SOCKET_TIMEOUT % 1000) * MILLION}; - - clock_gettime(PTHREAD_COND_CLOCK, &dl); - ts_add(&dl, &to, &dl); - - pthread_mutex_lock(&e->lock); - - while (e->state == IPCP_BOOT && ret != ETIMEDOUT) - ret = pthread_cond_timedwait(&e->cond, &e->lock, &dl); - - if (ret == ETIMEDOUT) { - kill(e->pid, SIGTERM); - e->state = IPCP_NULL; - pthread_cond_signal(&e->cond); + char * exec_name = NULL; + char irmd_pid[10]; + char full_name[256]; + char * argv[5]; + pid_t pid; + + switch(info->type) { + case IPCP_UNICAST: + exec_name = IPCP_UNICAST_EXEC; + break; + case IPCP_BROADCAST: + exec_name = IPCP_BROADCAST_EXEC; + break; + case IPCP_UDP4: + exec_name = IPCP_UDP4_EXEC; + break; + case IPCP_UDP6: + exec_name = IPCP_UDP6_EXEC; + break; + case IPCP_ETH_LLC: + exec_name = IPCP_ETH_LLC_EXEC; + break; + case IPCP_ETH_DIX: + exec_name = IPCP_ETH_DIX_EXEC; + break; + case IPCP_LOCAL: + exec_name = IPCP_LOCAL_EXEC; + break; + default: + assert(false); } - if (e->state != IPCP_LIVE) { - pthread_mutex_unlock(&e->lock); + if (exec_name == NULL) { + log_err("IPCP type not installed."); return -1; } - pthread_mutex_unlock(&e->lock); - - return 0; -} - -static struct ipcp_entry * get_ipcp_entry_by_pid(pid_t pid) -{ - struct list_head * p; + sprintf(irmd_pid, "%u", getpid()); - list_for_each(p, &irmd.ipcps) { - struct ipcp_entry * e = list_entry(p, struct ipcp_entry, next); - if (e->pid == pid) - return e; - } + strcpy(full_name, INSTALL_PREFIX"/"INSTALL_SBINDIR"/"); + strcat(full_name, exec_name); - return NULL; -} + /* log_file to be placed at the end */ + argv[0] = full_name; + argv[1] = irmd_pid; + argv[2] = (char *) info->name; + if (log_syslog) + argv[3] = "1"; + else + argv[3] = NULL; -static struct ipcp_entry * get_ipcp_entry_by_name(const char * name) -{ - struct list_head * p; + argv[4] = NULL; - list_for_each(p, &irmd.ipcps) { - struct ipcp_entry * e = list_entry(p, struct ipcp_entry, next); - if (strcmp(name, e->name) == 0) - return e; + pid = spawn_program(argv); + if (pid < 0) { + log_err("Failed to spawn IPCP %s.", info->name); + return -1; } - return NULL; -} - -static struct ipcp_entry * get_ipcp_entry_by_layer(const char * layer) -{ - struct list_head * p; - - list_for_each(p, &irmd.ipcps) { - struct ipcp_entry * e = list_entry(p, struct ipcp_entry, next); - if (strcmp(layer, e->layer) == 0) - return e; - } + info->pid = pid; + info->state = IPCP_INIT; - return NULL; + return 0; } -static struct ipcp_entry * get_ipcp_by_dst_name(const char * name, - pid_t src) +static int kill_ipcp(pid_t pid) { - struct list_head * p; - struct list_head * h; - uint8_t * hash; - pid_t pid; - size_t len; - - pthread_rwlock_rdlock(&irmd.reg_lock); - - list_for_each_safe(p, h, &irmd.ipcps) { - struct ipcp_entry * e = list_entry(p, struct ipcp_entry, next); - if (e->layer == NULL || e->pid == src) - continue; - - len = IPCP_HASH_LEN(e); - - hash = malloc(len); - if (hash == NULL) { - pthread_rwlock_unlock(&irmd.reg_lock); - return NULL; - } + int status; - str_hash(e->dir_hash_algo, hash, name); - - pid = e->pid; - - pthread_rwlock_unlock(&irmd.reg_lock); - - if (ipcp_query(pid, hash, len) == 0) { - free(hash); - return e; - } - - free(hash); - - pthread_rwlock_rdlock(&irmd.reg_lock); + if (kill(pid, SIGTERM) < 0) { + log_err("Failed to destroy IPCP: %s.", strerror(errno)); + return -1; } - pthread_rwlock_unlock(&irmd.reg_lock); + waitpid(pid, &status, 0); - return NULL; + return 0; } -static pid_t create_ipcp(const char * name, - enum ipcp_type type) +int create_ipcp(struct ipcp_info * info) { - struct pid_el * ppid; - struct ipcp_entry * entry; - struct list_head * p; - pid_t pid; + struct timespec abstime; + struct timespec timeo = TIMESPEC_INIT_MS(SOCKET_TIMEOUT); + int status; - pthread_rwlock_rdlock(&irmd.reg_lock); + assert(info->pid == 0); - entry = get_ipcp_entry_by_name(name); - if (entry != NULL) { - pthread_rwlock_unlock(&irmd.reg_lock); - log_err("IPCP by that name already exists."); - return -EPERM; - } - - pthread_rwlock_unlock(&irmd.reg_lock); - - ppid = malloc(sizeof(*ppid)); - if (ppid == NULL) - goto fail_ppid; - - entry = ipcp_entry_create(name, type); - if (entry == NULL) { - log_err("Failed to create IPCP entry."); - goto fail_ipcp_entry; - } + clock_gettime(PTHREAD_COND_CLOCK, &abstime); + ts_add(&abstime, &timeo, &abstime); - pid = ipcp_create(name, type); - if (pid == -1) { + if (spawn_ipcp(info) < 0) { log_err("Failed to create IPCP."); goto fail_ipcp; } - entry->pid = pid; - - pthread_rwlock_wrlock(&irmd.reg_lock); - - list_for_each(p, &irmd.ipcps) { - if (list_entry(p, struct ipcp_entry, next)->type > type) - break; + if (reg_create_ipcp(info) < 0) { + log_err("Failed to create IPCP entry."); + goto fail_reg_ipcp; } - list_add_tail(&entry->next, p); - ++irmd.n_ipcps; - - ppid->pid = entry->pid; - list_add(&ppid->next, &irmd.spawned_pids); - - pthread_rwlock_unlock(&irmd.reg_lock); - - /* IRMd maintenance will clean up if booting fails. */ - if (ipcp_entry_wait_boot(entry)) { - log_err("IPCP %d failed to boot.", pid); - return -1; + if (reg_wait_ipcp_boot(info, &abstime)) { + log_err("IPCP %d failed to boot.", info->pid); + goto fail_boot; } - log_info("Created IPCP %d.", pid); + log_info("Created IPCP %d.", info->pid); - return pid; + return 0; - fail_ipcp: - ipcp_entry_destroy(entry); - fail_ipcp_entry: - free(ppid); - fail_ppid: + fail_boot: + waitpid(info->pid, &status, 0); + reg_destroy_proc(info->pid); return -1; -} - -static int create_ipcp_r(pid_t pid, - int result) -{ - struct list_head * p; - - pthread_rwlock_rdlock(&irmd.reg_lock); - list_for_each(p, &irmd.ipcps) { - struct ipcp_entry * e = list_entry(p, struct ipcp_entry, next); - if (e->pid == pid) { - ipcp_entry_set_state(e, result ? IPCP_NULL : IPCP_LIVE); - break; - } - } - - pthread_rwlock_unlock(&irmd.reg_lock); - - return 0; + fail_reg_ipcp: + kill_ipcp(info->pid); + fail_ipcp: + return -1; } -static void clear_spawned_process(pid_t pid) +static int create_ipcp_r(struct ipcp_info * info) { - struct list_head * p; - struct list_head * h; - - list_for_each_safe(p, h, &(irmd.spawned_pids)) { - struct pid_el * a = list_entry(p, struct pid_el, next); - if (a->pid == pid) { - list_del(&a->next); - free(a); - } - } + return reg_respond_ipcp(info); } static int destroy_ipcp(pid_t pid) { - struct list_head * p; - struct list_head * h; - - pthread_rwlock_wrlock(&irmd.reg_lock); - - list_for_each_safe(p, h, &irmd.ipcps) { - struct ipcp_entry * e = list_entry(p, struct ipcp_entry, next); - if (e->pid == pid) { - clear_spawned_process(pid); - if (ipcp_destroy(pid)) - log_err("Could not destroy IPCP."); - list_del(&e->next); - ipcp_entry_destroy(e); - --irmd.n_ipcps; - log_info("Destroyed IPCP %d.", pid); - } + if (kill_ipcp(pid)) { + log_err("Could not destroy IPCP."); + goto fail; } - pthread_rwlock_unlock(&irmd.reg_lock); + if (reg_destroy_proc(pid)) { + log_err("Failed to remove IPCP from registry."); + goto fail; + } return 0; + fail: + return -1; } -static int bootstrap_ipcp(pid_t pid, - ipcp_config_msg_t * conf) +int bootstrap_ipcp(pid_t pid, + struct ipcp_config * conf) { - struct ipcp_entry * entry; - struct layer_info info; + struct ipcp_info info; + struct layer_info layer; - pthread_rwlock_wrlock(&irmd.reg_lock); + info.pid = pid; - entry = get_ipcp_entry_by_pid(pid); - if (entry == NULL) { - pthread_rwlock_unlock(&irmd.reg_lock); - log_err("No such IPCP."); - return -1; + if (reg_get_ipcp(&info, NULL) < 0) { + log_err("Could not find IPCP %d.", pid); + goto fail; } - if (entry->type != (enum ipcp_type) conf->ipcp_type) { - pthread_rwlock_unlock(&irmd.reg_lock); - log_err("Configuration does not match IPCP type."); - return -1; - } + if (conf->type == IPCP_UDP4 || conf->type == IPCP_UDP6) + conf->layer_info.dir_hash_algo = (enum pol_dir_hash) HASH_MD5; - if (ipcp_bootstrap(entry->pid, conf, &info)) { - pthread_rwlock_unlock(&irmd.reg_lock); + if (ipcp_bootstrap(pid, conf, &layer)) { log_err("Could not bootstrap IPCP."); - return -1; - } - - entry->layer = strdup(info.layer_name); - if (entry->layer == NULL) { - pthread_rwlock_unlock(&irmd.reg_lock); - log_warn("Failed to set name of layer."); - return -ENOMEM; + goto fail; } - entry->dir_hash_algo = info.dir_hash_algo; + info.state = IPCP_BOOT; - pthread_rwlock_unlock(&irmd.reg_lock); + if (reg_set_layer_for_ipcp(&info, &layer) < 0) { + log_err("Failed to set layer info for IPCP."); + goto fail; + } - log_info("Bootstrapped IPCP %d in layer %s.", - pid, conf->layer_info->layer_name); + log_info("Bootstrapped IPCP %d.", pid); return 0; + fail: + return -1; } -static int enroll_ipcp(pid_t pid, - char * dst) +int enroll_ipcp(pid_t pid, + const char * dst) { - struct ipcp_entry * entry = NULL; - struct layer_info info; + struct layer_info layer; + struct ipcp_info info; - pthread_rwlock_wrlock(&irmd.reg_lock); - - entry = get_ipcp_entry_by_pid(pid); - if (entry == NULL) { - pthread_rwlock_unlock(&irmd.reg_lock); - log_err("No such IPCP."); - return -1; - } + info.pid = pid; - if (entry->layer != NULL) { - pthread_rwlock_unlock(&irmd.reg_lock); - log_err("IPCP in wrong state"); - return -1; + if (reg_get_ipcp(&info, NULL) < 0) { + log_err("Could not find IPCP."); + goto fail; } - pthread_rwlock_unlock(&irmd.reg_lock); - - if (ipcp_enroll(pid, dst, &info) < 0) { + if (ipcp_enroll(pid, dst, &layer) < 0) { log_err("Could not enroll IPCP %d.", pid); - return -1; + goto fail; } - pthread_rwlock_wrlock(&irmd.reg_lock); + info.state = IPCP_BOOT; - entry = get_ipcp_entry_by_pid(pid); - if (entry == NULL) { - pthread_rwlock_unlock(&irmd.reg_lock); - log_err("No such IPCP."); - return -1; - } - - entry->layer = strdup(info.layer_name); - if (entry->layer == NULL) { - pthread_rwlock_unlock(&irmd.reg_lock); - log_err("Failed to strdup layer_name."); - return -ENOMEM; + if (reg_set_layer_for_ipcp(&info, &layer) < 0) { + log_err("Failed to set layer info for IPCP."); + goto fail; } - entry->dir_hash_algo = info.dir_hash_algo; - - pthread_rwlock_unlock(&irmd.reg_lock); - - log_info("Enrolled IPCP %d in layer %s.", - pid, info.layer_name); + log_info("Enrolled IPCP %d in layer %s.", pid, layer.name); return 0; + fail: + return -1; } -static int connect_ipcp(pid_t pid, - const char * dst, - const char * component, - qosspec_t qs) +int connect_ipcp(pid_t pid, + const char * dst, + const char * component, + qosspec_t qs) { - struct ipcp_entry * entry = NULL; + struct ipcp_info info; - pthread_rwlock_rdlock(&irmd.reg_lock); + info.pid = pid; - entry = get_ipcp_entry_by_pid(pid); - if (entry == NULL) { - pthread_rwlock_unlock(&irmd.reg_lock); + if (reg_get_ipcp(&info, NULL) < 0) { log_err("No such IPCP."); return -EIPCP; } - if (entry->type != IPCP_UNICAST && entry->type != IPCP_BROADCAST) { - pthread_rwlock_unlock(&irmd.reg_lock); + if (info.type != IPCP_UNICAST && info.type != IPCP_BROADCAST) { log_err("Cannot establish connections for this IPCP type."); return -EIPCP; } - pthread_rwlock_unlock(&irmd.reg_lock); - log_dbg("Connecting %s to %s.", component, dst); if (ipcp_connect(pid, dst, component, qs)) { - log_err("Could not connect IPCP."); + log_err("Could not connect IPCP %d to %s.", pid, dst); return -EPERM; } @@ -662,25 +414,20 @@ static int disconnect_ipcp(pid_t pid, const char * dst, const char * component) { - struct ipcp_entry * entry = NULL; + struct ipcp_info info; - pthread_rwlock_rdlock(&irmd.reg_lock); + info.pid = pid; - entry = get_ipcp_entry_by_pid(pid); - if (entry == NULL) { - pthread_rwlock_unlock(&irmd.reg_lock); + if (reg_get_ipcp(&info, NULL) < 0) { log_err("No such IPCP."); return -EIPCP; } - if (entry->type != IPCP_UNICAST) { - pthread_rwlock_unlock(&irmd.reg_lock); + if (info.type != IPCP_UNICAST && info.type != IPCP_BROADCAST) { log_err("Cannot tear down connections for this IPCP type."); return -EIPCP; } - pthread_rwlock_unlock(&irmd.reg_lock); - if (ipcp_disconnect(pid, dst, component)) { log_err("Could not disconnect IPCP."); return -EPERM; @@ -692,1302 +439,900 @@ static int disconnect_ipcp(pid_t pid, return 0; } -static int bind_program(char * prog, - char * name, - uint16_t flags, - int argc, - char ** argv) +static void name_update_sec_paths(struct name_info * info) { - char * progs; - char ** argv_dup = NULL; - int i; - char * name_dup = NULL; - struct prog_entry * e = NULL; - struct reg_entry * re = NULL; - - if (prog == NULL || name == NULL) - return -EINVAL; - - pthread_rwlock_wrlock(&irmd.reg_lock); - - e = prog_table_get(&irmd.prog_table, path_strip(prog)); - if (e == NULL) { - progs = strdup(path_strip(prog)); - if (progs == NULL) { - pthread_rwlock_unlock(&irmd.reg_lock); - return -ENOMEM; - } + char * srv_dir = OUROBOROS_SRV_CRT_DIR; + char * cli_dir = OUROBOROS_CLI_CRT_DIR; - if ((flags & BIND_AUTO) && argc) { - /* We need to duplicate argv and set argv[0] to prog. */ - argv_dup = malloc((argc + 2) * sizeof(*argv_dup)); - argv_dup[0] = strdup(prog); - for (i = 1; i <= argc; ++i) { - argv_dup[i] = strdup(argv[i - 1]); - if (argv_dup[i] == NULL) { - pthread_rwlock_unlock(&irmd.reg_lock); - argvfree(argv_dup); - log_err("Failed to bind program " - "%s to %s.", - prog, name); - free(progs); - return -ENOMEM; - } - } - argv_dup[argc + 1] = NULL; - } - e = prog_entry_create(progs, flags, argv_dup); - if (e == NULL) { - pthread_rwlock_unlock(&irmd.reg_lock); - free(progs); - argvfree(argv_dup); - return -ENOMEM; - } - prog_table_add(&irmd.prog_table, e); - } + assert(info != NULL); - name_dup = strdup(name); - if (name_dup == NULL) { - pthread_rwlock_unlock(&irmd.reg_lock); - return -ENOMEM; - } + if (strlen(info->s.enc) == 0) + sprintf(info->s.enc, "%s/%s/enc.conf", srv_dir, info->name); - if (prog_entry_add_name(e, name_dup)) { - log_err("Failed adding name."); - pthread_rwlock_unlock(&irmd.reg_lock); - free(name_dup); - return -ENOMEM; - } + if (strlen(info->s.crt) == 0) + sprintf(info->s.crt, "%s/%s/crt.pem", srv_dir, info->name); - re = registry_get_entry(&irmd.registry, name); - if (re != NULL && reg_entry_add_prog(re, e) < 0) - log_err("Failed adding program %s for name %s.", prog, name); + if (strlen(info->s.key) == 0) + sprintf(info->s.key, "%s/%s/key.pem", srv_dir, info->name); - pthread_rwlock_unlock(&irmd.reg_lock); + if (strlen(info->c.enc) == 0) + sprintf(info->c.enc, "%s/%s/enc.conf", cli_dir, info->name); - log_info("Bound program %s to name %s.", prog, name); + if (strlen(info->c.crt) == 0) + sprintf(info->c.crt, "%s/%s/crt.pem", cli_dir, info->name); - return 0; + if (strlen(info->c.key) == 0) + sprintf(info->c.key, "%s/%s/key.pem", cli_dir, info->name); } -static int bind_process(pid_t pid, - char * name) +int name_create(struct name_info * info) { - char * name_dup = NULL; - struct proc_entry * e = NULL; - struct reg_entry * re = NULL; - struct timespec now; - struct timespec dl = {0, 10 * MILLION}; + int ret; - if (name == NULL) - return -EINVAL; - - clock_gettime(PTHREAD_COND_CLOCK, &now); + assert(info != NULL); - ts_add(&dl, &now, &dl); - - pthread_rwlock_wrlock(&irmd.reg_lock); - - while (!kill(pid, 0)) { - e = proc_table_get(&irmd.proc_table, pid); - if (e != NULL || ts_diff_ms(&now, &dl) > 0) - break; - clock_gettime(PTHREAD_COND_CLOCK, &now); - sched_yield(); - } + name_update_sec_paths(info); - if (e == NULL) { - log_err("Process %d does not %s.", pid, - kill(pid, 0) ? "exist" : "respond"); - pthread_rwlock_unlock(&irmd.reg_lock); - return -1; - } - - name_dup = strdup(name); - if (name_dup == NULL) { - pthread_rwlock_unlock(&irmd.reg_lock); - return -ENOMEM; + ret = reg_create_name(info); + if (ret == -EEXIST) { + log_info("Name %s already exists.", info->name); + return 0; } - if (proc_entry_add_name(e, name_dup)) { - pthread_rwlock_unlock(&irmd.reg_lock); - log_err("Failed to add name %s to process %d.", name, pid); - free(name_dup); + if (ret < 0) { + log_err("Failed to create name %s.", info->name); return -1; } - re = registry_get_entry(&irmd.registry, name); - if (re != NULL && reg_entry_add_pid(re, pid) < 0) - log_err("Failed adding process %d for name %s.", pid, name); - - pthread_rwlock_unlock(&irmd.reg_lock); - - log_info("Bound process %d to name %s.", pid, name); + log_info("Created new name: %s.", info->name); return 0; } -static int unbind_program(char * prog, - char * name) +static int name_destroy(const char * name) { - struct reg_entry * e; - if (prog == NULL) - return -EINVAL; + assert(name != NULL); - pthread_rwlock_wrlock(&irmd.reg_lock); - - if (name == NULL) - prog_table_del(&irmd.prog_table, prog); - else { - struct prog_entry * en = prog_table_get(&irmd.prog_table, prog); - if (en == NULL) { - pthread_rwlock_unlock(&irmd.reg_lock); - return -EINVAL; - } - - prog_entry_del_name(en, name); - - e = registry_get_entry(&irmd.registry, name); - if (e != NULL) - reg_entry_del_prog(e, prog); + if (reg_destroy_name(name) < 0) { + log_err("Failed to destroy name %s.", name); + return -1; } - pthread_rwlock_unlock(&irmd.reg_lock); - - if (name == NULL) - log_info("Program %s unbound.", prog); - else - log_info("All names matching %s unbound for %s.", name, prog); + log_info("Destroyed name: %s.", name); return 0; } -static int unbind_process(pid_t pid, - const char * name) +int bind_program(char ** exec, + const char * name, + uint8_t flags) { - struct reg_entry * e; + struct prog_info prog; + struct name_info ni; - pthread_rwlock_wrlock(&irmd.reg_lock); + if (name == NULL || exec == NULL || exec[0] == NULL) + return -EINVAL; - if (name == NULL) - proc_table_del(&irmd.proc_table, pid); - else { - struct proc_entry * en = proc_table_get(&irmd.proc_table, pid); - if (en != NULL) - proc_entry_del_name(en, name); + memset(&prog, 0, sizeof(prog)); + memset(&ni, 0, sizeof(ni)); - e = registry_get_entry(&irmd.registry, name); - if (e != NULL) - reg_entry_del_pid(e, pid); + if (!reg_has_prog(exec[0])) { + strcpy(prog.name, path_strip(exec[0])); + strcpy(prog.path, exec[0]); + if (reg_create_prog(&prog) < 0) + goto fail_prog; } - pthread_rwlock_unlock(&irmd.reg_lock); + if (!reg_has_name(name)) { + ni.pol_lb = LB_SPILL; + strcpy(ni.name, name); + if (name_create(&ni) < 0) + goto fail_name; + } - if (name == NULL) - log_info("Process %d unbound.", pid); - else - log_info("All names matching %s unbound for %d.", name, pid); + if (reg_bind_prog(name, exec, flags) < 0) { + log_err("Failed to bind program %s to name %s", exec[0], name); + goto fail_bind; + } + + log_info("Bound program %s to name %s.", exec[0], name); return 0; + + fail_bind: + if (strlen(ni.name) > 0) + reg_destroy_name(name); + fail_name: + if (strlen(prog.name) > 0) + reg_destroy_prog(exec[0]); + fail_prog: + return -1; } -static ssize_t list_ipcps(ipcp_info_msg_t *** ipcps, - size_t * n_ipcps) +int bind_process(pid_t pid, + const char * name) { - struct list_head * p; - int i = 0; + struct timespec abstime; + struct timespec timeo = TIMESPEC_INIT_MS(10); + struct name_info ni; - pthread_rwlock_rdlock(&irmd.reg_lock); + if (name == NULL) + return -EINVAL; - *n_ipcps = irmd.n_ipcps; - if (*n_ipcps == 0) { - pthread_rwlock_unlock(&irmd.reg_lock); - return 0; - } + clock_gettime(PTHREAD_COND_CLOCK, &abstime); + ts_add(&abstime, &timeo, &abstime); - *ipcps = malloc(irmd.n_ipcps * sizeof(**ipcps)); - if (*ipcps == NULL) { - pthread_rwlock_unlock(&irmd.reg_lock); - *n_ipcps = 0; - return -ENOMEM; + if (reg_wait_proc(pid, &abstime) < 0) { + log_err("Process %d does not %s.", pid, + kill(pid, 0) ? "exist" : "respond"); + goto fail; } - list_for_each(p, &irmd.ipcps) { - struct ipcp_entry * e = list_entry(p, struct ipcp_entry, next); - (*ipcps)[i] = malloc(sizeof(***ipcps)); - if ((*ipcps)[i] == NULL) { - --i; - goto fail; - } - - ipcp_info_msg__init((*ipcps)[i]); - (*ipcps)[i]->name = strdup(e->name); - if ((*ipcps)[i]->name == NULL) - goto fail; + memset(&ni, 0, sizeof(ni)); - (*ipcps)[i]->layer = strdup( - e->layer != NULL ? e->layer : "Not enrolled"); - if ((*ipcps)[i]->layer == NULL) + if (!reg_has_name(name)) { + ni.pol_lb = LB_SPILL; + strcpy(ni.name, name); + if (name_create(&ni) < 0) goto fail; + } - (*ipcps)[i]->pid = e->pid; - (*ipcps)[i++]->type = e->type; - } + if (reg_bind_proc(name, pid) < 0) { + log_err("Failed to add name %s to process %d.", name, pid); + goto fail_bind; + } - pthread_rwlock_unlock(&irmd.reg_lock); + log_info("Bound process %d to name %s.", pid, name); return 0; + fail_bind: + if (strlen(ni.name) > 0) + reg_destroy_name(name); fail: - pthread_rwlock_unlock(&irmd.reg_lock); - while (i >= 0) { - free((*ipcps)[i]->layer); - free((*ipcps)[i]->name); - free(*ipcps[i--]); - } - free(*ipcps); - *n_ipcps = 0; - return -ENOMEM; + return -1; + } -static int name_create(const char * name, - enum pol_balance pol) +static int unbind_program(const char * prog, + const char * name) { - struct reg_entry * re; - struct list_head * p; - - assert(name); - - pthread_rwlock_wrlock(&irmd.reg_lock); - - if (registry_has_name(&irmd.registry, name)) { - pthread_rwlock_unlock(&irmd.reg_lock); - log_err("Registry entry for %s already exists.", name); - return -ENAME; - } + if (prog == NULL) + return -EINVAL; - re = registry_add_name(&irmd.registry, name); - if (re == NULL) { - pthread_rwlock_unlock(&irmd.reg_lock); - log_err("Failed creating registry entry for %s.", name); - return -ENOMEM; - } - ++irmd.n_names; - reg_entry_set_policy(re, pol); - - /* check the tables for existing bindings */ - list_for_each(p, &irmd.proc_table) { - struct list_head * q; - struct proc_entry * e; - e = list_entry(p, struct proc_entry, next); - list_for_each(q, &e->names) { - struct str_el * s; - s = list_entry(q, struct str_el, next); - if (!strcmp(s->str, name)) - reg_entry_add_pid(re, e->pid); + if (name == NULL) { + if (reg_destroy_prog(prog) < 0) { + log_err("Failed to unbind %s.", prog); + goto fail; } - } - - list_for_each(p, &irmd.prog_table) { - struct list_head * q; - struct prog_entry * e; - e = list_entry(p, struct prog_entry, next); - list_for_each(q, &e->names) { - struct str_el * s; - s = list_entry(q, struct str_el, next); - if (!strcmp(s->str, name)) - reg_entry_add_prog(re, e); + log_info("Program %s unbound.", prog); + } else { + if (reg_unbind_prog(name, prog) < 0) { + log_err("Failed to unbind %s from %s", prog, name); + goto fail; } + log_info("Name %s unbound for %s.", name, prog); } - pthread_rwlock_unlock(&irmd.reg_lock); - - log_info("Created new name: %s.", name); - return 0; + + fail: + return -1; } -static int name_destroy(const char * name) +static int unbind_process(pid_t pid, + const char * name) { - assert(name); - - pthread_rwlock_wrlock(&irmd.reg_lock); - - if (!registry_has_name(&irmd.registry, name)) { - pthread_rwlock_unlock(&irmd.reg_lock); - log_warn("Registry entry for %s does not exist.", name); - return -ENAME; + if (name == NULL) { + if (reg_destroy_proc(pid) < 0) { + log_err("Failed to unbind %d.", pid); + goto fail; + } + log_info("Process %d unbound.", pid); + } else { + if (reg_unbind_proc(name, pid) < 0) { + log_err("Failed to unbind %d from %s", pid, name); + goto fail; + } + log_info("Name %s unbound for process %d.", name, pid); } - registry_del_name(&irmd.registry, name); - --irmd.n_names; - - pthread_rwlock_unlock(&irmd.reg_lock); - - log_info("Destroyed name: %s.", name); - return 0; + + fail: + return -1; } -static ssize_t list_names(name_info_msg_t *** names, - size_t * n_names) +static int list_ipcps(ipcp_list_msg_t *** ipcps, + size_t * n_ipcps) { - struct list_head * p; - int i = 0; + int n; - pthread_rwlock_rdlock(&irmd.reg_lock); - - *n_names = irmd.n_names; - if (*n_names == 0) { - pthread_rwlock_unlock(&irmd.reg_lock); - return 0; - } - - *names = malloc(irmd.n_names * sizeof(**names)); - if (*names == NULL) { - *n_names = 0; - pthread_rwlock_unlock(&irmd.reg_lock); - return -ENOMEM; - } + n = reg_list_ipcps(ipcps); + if (n < 0) + goto fail; - list_for_each(p, &irmd.registry) { - struct reg_entry * e = list_entry(p, struct reg_entry, next); + *n_ipcps = (size_t) n; - (*names)[i] = malloc(sizeof(***names)); - if ((*names)[i] == NULL) { - --i; - goto fail; - } + return 0; + fail: + *ipcps = NULL; + *n_ipcps = 0; + return -1; +} - name_info_msg__init((*names)[i]); - (*names)[i]->name = strdup(e->name); - if ((*names)[i]->name == NULL) - goto fail; +static int list_names(name_info_msg_t *** names, + size_t * n_names) +{ + int n; - (*names)[i++]->pol_lb = e->pol_lb; - } + n = reg_list_names(names); + if (n < 0) + goto fail; - pthread_rwlock_unlock(&irmd.reg_lock); + *n_names = (size_t) n; return 0; - fail: - pthread_rwlock_unlock(&irmd.reg_lock); - while (i >= 0) { - free((*names)[i]->name); - free(*names[i--]); - } - free(*names); + *names = NULL; *n_names = 0; - return -ENOMEM; + return -1; } -static int name_reg(const char * name, - pid_t pid) +int name_reg(const char * name, + pid_t pid) { - size_t len; - struct ipcp_entry * ipcp; - uint8_t * hash; - int err; + struct ipcp_info info; + struct layer_info layer; + buffer_t hash; assert(name); - pthread_rwlock_wrlock(&irmd.reg_lock); - - if (!registry_has_name(&irmd.registry, name)) { - err = -ENAME; - goto fail; - } + info.pid = pid; - ipcp = get_ipcp_entry_by_pid(pid); - if (ipcp == NULL) { - err = -EIPCP; - goto fail; + if (!reg_has_name(name)) { + log_err("Failed to get name %s.", name); + return -ENAME; } - if (ipcp->layer == NULL) { - err = -EPERM; - goto fail; + if (reg_get_ipcp(&info, &layer) < 0) { + log_err("Failed to get IPCP %d.", pid); + return -EIPCP; } - len = IPCP_HASH_LEN(ipcp); - - hash = malloc(len); - if (hash == NULL) { - err = -ENOMEM; - goto fail; + hash.len = hash_len((enum hash_algo) layer.dir_hash_algo); + hash.data = malloc(hash.len); + if (hash.data == NULL) { + log_err("Failed to malloc hash."); + return -ENOMEM; } - str_hash(ipcp->dir_hash_algo, hash, name); - pthread_rwlock_unlock(&irmd.reg_lock); + str_hash((enum hash_algo) layer.dir_hash_algo, hash.data, name); - if (ipcp_reg(pid, hash, len)) { - log_err("Could not register " HASH_FMT " with IPCP %d.", - HASH_VAL(hash), pid); - free(hash); - return -1; + if (ipcp_reg(pid, hash)) { + log_err("Could not register " HASH_FMT32 " with IPCP %d.", + HASH_VAL32(hash.data), pid); + goto fail_hash; } - log_info("Registered %s with IPCP %d as " HASH_FMT ".", - name, pid, HASH_VAL(hash)); + log_info("Registered %s with IPCP %d as " HASH_FMT32 ".", + name, pid, HASH_VAL32(hash.data)); - free(hash); + freebuf(hash); return 0; -fail: - pthread_rwlock_unlock(&irmd.reg_lock); - return err; + fail_hash: + freebuf(hash); + return -1; } static int name_unreg(const char * name, pid_t pid) { - struct ipcp_entry * ipcp; - int err; - uint8_t * hash; - size_t len; + struct ipcp_info info; + struct layer_info layer; + buffer_t hash; assert(name); - pthread_rwlock_wrlock(&irmd.reg_lock); + info.pid = pid; - ipcp = get_ipcp_entry_by_pid(pid); - if (ipcp == NULL) { - err = -EIPCP; - goto fail; + if (!reg_has_name(name)) { + log_err("Failed to get name %s.", name); + return -ENAME; } - if (ipcp->layer == NULL) { - err = -EPERM; - goto fail; + if (reg_get_ipcp(&info, &layer) < 0) { + log_err("Failed to get IPCP %d.", pid); + return -EIPCP; } - len = IPCP_HASH_LEN(ipcp); - - hash = malloc(len); - if (hash == NULL) { - err = -ENOMEM; - goto fail; + hash.len = hash_len((enum hash_algo) layer.dir_hash_algo); + hash.data = malloc(hash.len); + if (hash.data == NULL) { + log_err("Failed to malloc hash."); + return -ENOMEM; } - str_hash(ipcp->dir_hash_algo, hash, name); - - pthread_rwlock_unlock(&irmd.reg_lock); + str_hash((enum hash_algo) layer.dir_hash_algo, hash.data, name); - if (ipcp_unreg(pid, hash, len)) { + if (ipcp_unreg(pid, hash)) { log_err("Could not unregister %s with IPCP %d.", name, pid); - free(hash); - return -1; + goto fail_hash; } log_info("Unregistered %s from %d.", name, pid); - free(hash); + freebuf(hash); return 0; - fail: - pthread_rwlock_unlock(&irmd.reg_lock); - return err; + fail_hash: + freebuf(hash); + return -1; } -static int proc_announce(pid_t pid, - char * prog) +static int get_peer_ids(int fd, + uid_t * uid, + gid_t * gid) { - struct proc_entry * e; - struct prog_entry * a; - char * prog_dup; - - assert(prog); - - prog_dup = strdup(prog); - if (prog_dup == NULL) - return -ENOMEM; - - e = proc_entry_create(pid, prog_dup); - if (e == NULL) { - free(prog_dup); - return -ENOMEM; - } +#if defined(__linux__) + struct ucred ucred; + socklen_t len; - pthread_rwlock_wrlock(&irmd.reg_lock); + len = sizeof(ucred); - proc_table_add(&irmd.proc_table, e); + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) < 0) + goto fail; - /* Copy listen names from program if it exists. */ - a = prog_table_get(&irmd.prog_table, e->prog); - if (a != NULL) { - struct list_head * p; - list_for_each(p, &a->names) { - struct str_el * s = list_entry(p, struct str_el, next); - struct str_el * n = malloc(sizeof(*n)); - if (n == NULL) { - pthread_rwlock_unlock(&irmd.reg_lock); - return -ENOMEM; - } + *uid = ucred.uid; + *gid = ucred.gid; +#else + if (getpeereid(fd, uid, gid) < 0) + goto fail; +#endif + return 0; + fail: + return -1; +} - n->str = strdup(s->str); - if (n->str == NULL) { - pthread_rwlock_unlock(&irmd.reg_lock); - free(n); - return -ENOMEM; - } +static int proc_announce(const struct proc_info * info) +{ + if (reg_prepare_pool(info->uid, info->gid) < 0) { + log_err("Failed to prepare pool for uid %d.", info->uid); + goto fail; + } - list_add(&n->next, &e->names); - log_dbg("Process %d inherits name %s from program %s.", - pid, n->str, e->prog); - } + if (reg_create_proc(info) < 0) { + log_err("Failed to add process %d.", info->pid); + goto fail; } - pthread_rwlock_unlock(&irmd.reg_lock); + log_info("Process added: %d (%s).", info->pid, info->prog); return 0; + + fail: + return -1; } -static int flow_accept(pid_t pid, - struct timespec * timeo, - struct irm_flow * f_out, - const void * data, - size_t len) +static int proc_exit(pid_t pid) { - struct irm_flow * f = NULL; - struct proc_entry * pe = NULL; - struct reg_entry * re = NULL; - struct list_head * p = NULL; - - pid_t pid_n; - pid_t pid_n_1; - int flow_id; - int ret; - - pthread_rwlock_wrlock(&irmd.reg_lock); - - pe = proc_table_get(&irmd.proc_table, pid); - if (pe == NULL) { - pthread_rwlock_unlock(&irmd.reg_lock); - log_err("Unknown process %d calling accept.", pid); - return -EINVAL; - } + if (reg_destroy_proc(pid) < 0) + log_err("Failed to remove process %d.", pid); - log_dbg("New instance (%d) of %s added.", pid, pe->prog); - log_dbg("This process accepts flows for:"); + log_info("Process removed: %d.", pid); - list_for_each(p, &pe->names) { - struct str_el * s = list_entry(p, struct str_el, next); - log_dbg(" %s", s->str); - re = registry_get_entry(&irmd.registry, s->str); - if (re != NULL) - reg_entry_add_pid(re, pid); - } + return 0; +} - pthread_rwlock_unlock(&irmd.reg_lock); +static void __cleanup_flow(void * flow) +{ + reg_destroy_flow(((struct flow_info *) flow)->id); +} - ret = proc_entry_sleep(pe, timeo); - if (ret == -ETIMEDOUT) - return -ETIMEDOUT; +static int flow_accept(struct flow_info * flow, + buffer_t * data, + struct timespec * abstime, + struct crypt_sk * sk) +{ + buffer_t req_hdr; + buffer_t resp_hdr; + char name[NAME_SIZE + 1]; + struct name_info info; + int err; - if (ret == -1) - return -EPIPE; + assert(data != NULL && BUF_IS_EMPTY(data)); - if (irmd_get_state() != IRMD_RUNNING) - return -EIRMD; + clrbuf(req_hdr); + clrbuf(resp_hdr); - pthread_rwlock_rdlock(&irmd.flows_lock); + if (!reg_has_proc(flow->n_pid)) { + log_err("Unknown process %d calling accept.", flow->n_pid); + err = -EINVAL; + goto fail_flow; + } - f = get_irm_flow_n(pid); - if (f == NULL) { - pthread_rwlock_unlock(&irmd.flows_lock); - log_warn("Port_id was not created yet."); - return -EPERM; + if (reg_create_flow(flow) < 0) { + log_err("Failed to create flow."); + err = -EBADF; + goto fail_flow; } - pid_n = f->n_pid; - pid_n_1 = f->n_1_pid; - flow_id = f->flow_id; - - pthread_rwlock_unlock(&irmd.flows_lock); - pthread_rwlock_rdlock(&irmd.reg_lock); - - pe = proc_table_get(&irmd.proc_table, pid); - if (pe == NULL) { - pthread_rwlock_unlock(&irmd.reg_lock); - pthread_rwlock_wrlock(&irmd.flows_lock); - list_del(&f->next); - bmp_release(irmd.flow_ids, f->flow_id); - pthread_rwlock_unlock(&irmd.flows_lock); - ipcp_flow_alloc_resp(pid_n_1, flow_id, pid_n, -1, NULL, 0); - clear_irm_flow(f); - irm_flow_set_state(f, FLOW_NULL); - irm_flow_destroy(f); - log_dbg("Process gone while accepting flow."); - return -EPERM; + if (reg_prepare_flow_accept(flow) < 0) { + log_err("Failed to prepare accept."); + err = -EBADF; + goto fail_wait; } - pthread_mutex_lock(&pe->lock); + pthread_cleanup_push(__cleanup_flow, flow); - re = pe->re; + err = reg_wait_flow_accepted(flow, &req_hdr, abstime); - pthread_mutex_unlock(&pe->lock); + pthread_cleanup_pop(false); - if (reg_entry_get_state(re) != REG_NAME_FLOW_ARRIVED) { - pthread_rwlock_unlock(&irmd.reg_lock); - pthread_rwlock_wrlock(&irmd.flows_lock); - list_del(&f->next); - bmp_release(irmd.flow_ids, f->flow_id); - pthread_rwlock_unlock(&irmd.flows_lock); - ipcp_flow_alloc_resp(pid_n_1, flow_id, pid_n, -1, NULL, 0); - clear_irm_flow(f); - irm_flow_set_state(f, FLOW_NULL); - irm_flow_destroy(f); - log_err("Entry in wrong state."); - return -EPERM; + if (err == -ETIMEDOUT) { + log_err("Flow accept timed out."); + goto fail_wait; } - registry_del_process(&irmd.registry, pid); - - pthread_rwlock_unlock(&irmd.reg_lock); - - pthread_rwlock_wrlock(&irmd.flows_lock); - - f_out->flow_id = f->flow_id; - f_out->n_pid = f->n_pid; - f_out->n_1_pid = f->n_1_pid; - f_out->data = f->data; /* pass owner */ - f_out->len = f->len; - f_out->qs = f->qs; - f->data = NULL; - f->len = 0; + if (err == -1) { + log_dbg("Flow accept terminated."); + err = -EPIPE; + goto fail_wait; + } - pthread_rwlock_unlock(&irmd.flows_lock); + assert(err == 0); - if (f->qs.cypher_s == 0) { /* no crypto requested, don't send pubkey */ - data = NULL; - len = 0; + if (reg_get_name_for_flow_id(name, flow->id) < 0) { + log_err("Failed to get name for flow %d.", flow->id); + err = -EIPCP; + goto fail_oap; } - if (ipcp_flow_alloc_resp(pid_n_1, flow_id, pid_n, 0, data, len)) { - pthread_rwlock_wrlock(&irmd.flows_lock); - list_del(&f->next); - pthread_rwlock_unlock(&irmd.flows_lock); - log_dbg("Failed to respond to alloc. Port_id invalidated."); - clear_irm_flow(f); - irm_flow_set_state(f, FLOW_NULL); - irm_flow_destroy(f); - return -EPERM; + if (reg_get_name_info(name, &info) < 0) { + log_err("Failed to get name info for %s.", name); + err = -ENAME; + goto fail_oap; } - irm_flow_set_state(f, FLOW_ALLOCATED); + log_dbg("IPCP %d accepting flow %d for %s.", + flow->n_pid, flow->id, name); - log_info("Flow on flow_id %d allocated.", f->flow_id); - - return 0; -} + flow->uid = reg_get_proc_uid(flow->n_pid); -static int flow_alloc(pid_t pid, - const char * dst, - qosspec_t qs, - struct timespec * timeo, - struct irm_flow * f_out, - bool join, - const void * data, - size_t len) -{ - struct irm_flow * f; - struct ipcp_entry * ipcp; - int flow_id; - int state; - uint8_t * hash; - - ipcp = join ? get_ipcp_entry_by_layer(dst) - : get_ipcp_by_dst_name(dst, pid); - if (ipcp == NULL) { - log_info("Destination %s unreachable.", dst); - return -1; + err = oap_srv_process(&info, req_hdr, &resp_hdr, data, sk); + if (err < 0) { + log_err("OAP processing failed for %s.", name); + goto fail_oap; } - pthread_rwlock_wrlock(&irmd.flows_lock); - - flow_id = bmp_allocate(irmd.flow_ids); - if (!bmp_is_id_valid(irmd.flow_ids, flow_id)) { - pthread_rwlock_unlock(&irmd.flows_lock); - log_err("Could not allocate flow_id."); - return -EBADF; + if (ipcp_flow_alloc_resp(flow, 0, resp_hdr) < 0) { + log_err("Failed to respond to flow allocation."); + goto fail_resp; } - f = irm_flow_create(pid, ipcp->pid, flow_id, qs); - if (f == NULL) { - bmp_release(irmd.flow_ids, flow_id); - pthread_rwlock_unlock(&irmd.flows_lock); - log_err("Could not allocate flow_id."); - return -ENOMEM; - } + log_info("Flow %d accepted by %d for %s (uid %d).", + flow->id, flow->n_pid, name, flow->uid); - list_add(&f->next, &irmd.irm_flows); + freebuf(req_hdr); + freebuf(resp_hdr); - pthread_rwlock_unlock(&irmd.flows_lock); + return 0; - assert(irm_flow_get_state(f) == FLOW_ALLOC_PENDING); + fail_oap: + ipcp_flow_alloc_resp(flow, err, resp_hdr); + fail_wait: + reg_destroy_flow(flow->id); + fail_flow: + return err; - hash = malloc(IPCP_HASH_LEN(ipcp)); - if (hash == NULL) - /* sanitizer cleans this */ - return -ENOMEM; + fail_resp: + flow->state = FLOW_NULL; + freebuf(req_hdr); + freebuf(resp_hdr); + reg_destroy_flow(flow->id); + return -EIPCP; +} - str_hash(ipcp->dir_hash_algo, hash, dst); +static int flow_join(struct flow_info * flow, + const char * dst, + struct timespec * abstime) +{ + struct ipcp_info ipcp; + struct layer_info layer; + buffer_t hash; + buffer_t pbuf = BUF_INIT; /* nothing to piggyback */ + int err; - if (join) { - if (ipcp_flow_join(ipcp->pid, flow_id, pid, hash, - IPCP_HASH_LEN(ipcp), qs)) { - /* sanitizer cleans this */ - log_info("Flow_join failed."); - free(hash); - return -EAGAIN; - } - } else { - if (ipcp_flow_alloc(ipcp->pid, flow_id, pid, hash, - IPCP_HASH_LEN(ipcp), qs, data, len)) { - /* sanitizer cleans this */ - log_info("Flow_allocation failed."); - free(hash); - return -EAGAIN; - } + if (reg_create_flow(flow) < 0) { + log_err("Failed to create flow."); + err = -EBADF; + goto fail_flow; } - free(hash); + flow->uid = reg_get_proc_uid(flow->n_pid); - state = irm_flow_wait_state(f, FLOW_ALLOCATED, timeo); - if (state != FLOW_ALLOCATED) { - if (state == -ETIMEDOUT) { - log_dbg("Flow allocation timed out"); - return -ETIMEDOUT; - } + log_info("Allocating flow for %d to %s (uid %d).", + flow->n_pid, dst, flow->uid); - log_info("Pending flow to %s torn down.", dst); - return -EPIPE; + strcpy(layer.name, dst); + if (reg_get_ipcp_by_layer(&ipcp, &layer) < 0) { + log_err("Failed to get IPCP for layer %s.", dst); + err = -EIPCP; + goto fail_ipcp; } - pthread_rwlock_wrlock(&irmd.flows_lock); + flow->n_1_pid = ipcp.pid; - assert(irm_flow_get_state(f) == FLOW_ALLOCATED); - - f_out->flow_id = f->flow_id; - f_out->n_pid = f->n_pid; - f_out->n_1_pid = f->n_1_pid; - f_out->data = f->data; /* pass owner */ - f_out->len = f->len; - f->data = NULL; - f->len = 0; + hash.len = hash_len((enum hash_algo) layer.dir_hash_algo); + hash.data = malloc(hash.len); + if (hash.data == NULL) { + log_err("Failed to malloc hash buffer."); + err = -ENOMEM; + goto fail_ipcp; + } - pthread_rwlock_unlock(&irmd.flows_lock); + str_hash((enum hash_algo) layer.dir_hash_algo, hash.data, dst); - log_info("Flow on flow_id %d allocated.", flow_id); + reg_prepare_flow_alloc(flow); - return 0; -} + if (ipcp_flow_join(flow, hash)) { + log_err("Flow join with layer %s failed.", dst); + err = -ENOTALLOC; + goto fail_alloc; + } -static int flow_dealloc(pid_t pid, - int flow_id, - time_t timeo) -{ - pid_t n_1_pid = -1; - int ret = 0; + pthread_cleanup_push(__cleanup_flow, flow); + pthread_cleanup_push(free, hash.data); - struct irm_flow * f = NULL; + err = reg_wait_flow_allocated(flow, &pbuf, abstime); - pthread_rwlock_wrlock(&irmd.flows_lock); + pthread_cleanup_pop(false); + pthread_cleanup_pop(false); - f = get_irm_flow(flow_id); - if (f == NULL) { - pthread_rwlock_unlock(&irmd.flows_lock); - log_dbg("Deallocate unknown port %d by %d.", flow_id, pid); - return 0; + if (err == -ETIMEDOUT) { + log_err("Flow join timed out."); + goto fail_alloc; } - if (pid == f->n_pid) { - f->n_pid = -1; - n_1_pid = f->n_1_pid; - } else if (pid == f->n_1_pid) { - f->n_1_pid = -1; - } else { - pthread_rwlock_unlock(&irmd.flows_lock); - log_dbg("Dealloc called by wrong process."); - return -EPERM; + if (err == -1) { + log_dbg("Flow join terminated."); + err = -EPIPE; + goto fail_alloc; } - if (irm_flow_get_state(f) == FLOW_DEALLOC_PENDING) { - list_del(&f->next); - if ((kill(f->n_pid, 0) < 0 && f->n_1_pid == -1) || - (kill(f->n_1_pid, 0) < 0 && f->n_pid == -1)) - irm_flow_set_state(f, FLOW_NULL); - clear_irm_flow(f); - irm_flow_destroy(f); - bmp_release(irmd.flow_ids, flow_id); - log_info("Completed deallocation of flow_id %d by process %d.", - flow_id, pid); - } else { - irm_flow_set_state(f, FLOW_DEALLOC_PENDING); - log_dbg("Partial deallocation of flow_id %d by process %d.", - flow_id, pid); - } + assert(pbuf.data == NULL && pbuf.len == 0); + assert(err == 0); - pthread_rwlock_unlock(&irmd.flows_lock); + freebuf(hash); - if (n_1_pid != -1) - ret = ipcp_flow_dealloc(n_1_pid, flow_id, timeo); + return 0; - return ret; + fail_alloc: + freebuf(hash); + fail_ipcp: + reg_destroy_flow(flow->id); + fail_flow: + return err; } -static pid_t auto_execute(char ** argv) +static int get_ipcp_by_dst(const char * dst, + pid_t * pid, + buffer_t * hash) { - pid_t pid; - struct stat s; + ipcp_list_msg_t ** ipcps; + int n; + int i; + int err = -EIPCP; - if (stat(argv[0], &s) != 0) { - log_warn("Program %s does not exist.", argv[0]); - return -1; - } + n = reg_list_ipcps(&ipcps); - if (!(s.st_mode & S_IXUSR)) { - log_warn("Program %s is not executable.", argv[0]); - return -1; - } + /* Clean up the ipcp_msgs in this loop */ + for (i = 0; i < n; ++i) { + enum hash_algo algo; + enum ipcp_type type; + pid_t tmp; + bool enrolled; - if (posix_spawn(&pid, argv[0], NULL, NULL, argv, NULL)) { - log_err("Failed to spawn new process"); - return -1; - } + type = ipcps[i]->type; + algo = ipcps[i]->hash_algo; + tmp = ipcps[i]->pid; - log_info("Instantiated %s as process %d.", argv[0], pid); + enrolled = strcmp(ipcps[i]->layer, "Not enrolled.") != 0; - return pid; -} + ipcp_list_msg__free_unpacked(ipcps[i], NULL); -static int flow_req_arr(pid_t pid, - struct irm_flow * f_out, - const uint8_t * hash, - qosspec_t qs, - const void * data, - size_t len) -{ - struct reg_entry * re = NULL; - struct prog_entry * a = NULL; - struct proc_entry * pe = NULL; - struct irm_flow * f = NULL; + if (type == IPCP_BROADCAST) + continue; - struct pid_el * c_pid; - struct ipcp_entry * ipcp; - pid_t h_pid; - int flow_id; + if (err == 0 /* solution found */ || !enrolled) + continue; - struct timespec wt = {IRMD_REQ_ARR_TIMEOUT / 1000, - (IRMD_REQ_ARR_TIMEOUT % 1000) * MILLION}; + hash->len = hash_len(algo); + hash->data = malloc(hash->len); + if (hash->data == NULL) { + log_warn("Failed to malloc hash for query."); + err = -ENOMEM; + continue; + } - log_dbg("Flow req arrived from IPCP %d for " HASH_FMT ".", - pid, HASH_VAL(hash)); + str_hash(algo, hash->data, dst); - pthread_rwlock_rdlock(&irmd.reg_lock); + if (ipcp_query(tmp, *hash) < 0) { + freebuf(*hash); + continue; + } - ipcp = get_ipcp_entry_by_pid(pid); - if (ipcp == NULL) { - log_err("IPCP died."); - return -EIPCP; - } + *pid = tmp; - re = registry_get_entry_by_hash(&irmd.registry, ipcp->dir_hash_algo, - hash, IPCP_HASH_LEN(ipcp)); - if (re == NULL) { - pthread_rwlock_unlock(&irmd.reg_lock); - log_err("Unknown hash: " HASH_FMT ".", HASH_VAL(hash)); - return -1; + err = 0; } - log_info("Flow request arrived for %s.", re->name); + free(ipcps); - pthread_rwlock_unlock(&irmd.reg_lock); + return err; +} - /* Give the process a bit of slop time to call accept */ - if (reg_entry_leave_state(re, REG_NAME_IDLE, &wt) == -1) { - log_err("No processes for " HASH_FMT ".", HASH_VAL(hash)); - return -1; +static int flow_alloc(const char * dst, + struct flow_info * flow, + buffer_t * data, + struct timespec * abstime, + struct crypt_sk * sk) +{ + buffer_t req_hdr = BUF_INIT; + buffer_t resp_hdr = BUF_INIT; + buffer_t hash = BUF_INIT; + struct name_info info; + void * ctx; + int err; + + /* piggyback of user data not yet implemented */ + assert(data != NULL && BUF_IS_EMPTY(data)); + + /* Look up name_info for dst */ + if (reg_get_name_info(dst, &info) < 0) { + log_err("Failed to get name info for %s.", dst); + err = -ENAME; + goto fail_flow; } - pthread_rwlock_wrlock(&irmd.reg_lock); - - switch (reg_entry_get_state(re)) { - case REG_NAME_IDLE: - pthread_rwlock_unlock(&irmd.reg_lock); - log_err("No processes for " HASH_FMT ".", HASH_VAL(hash)); - return -1; - case REG_NAME_AUTO_ACCEPT: - c_pid = malloc(sizeof(*c_pid)); - if (c_pid == NULL) { - pthread_rwlock_unlock(&irmd.reg_lock); - return -1; - } - - reg_entry_set_state(re, REG_NAME_AUTO_EXEC); - a = prog_table_get(&irmd.prog_table, - reg_entry_get_prog(re)); - - if (a == NULL || (c_pid->pid = auto_execute(a->argv)) < 0) { - reg_entry_set_state(re, REG_NAME_AUTO_ACCEPT); - pthread_rwlock_unlock(&irmd.reg_lock); - log_err("Could not start program for reg_entry %s.", - re->name); - free(c_pid); - return -1; - } - - list_add(&c_pid->next, &irmd.spawned_pids); - - pthread_rwlock_unlock(&irmd.reg_lock); + if (reg_create_flow(flow) < 0) { + log_err("Failed to create flow."); + err = -EBADF; + goto fail_flow; + } - if (reg_entry_leave_state(re, REG_NAME_AUTO_EXEC, NULL)) - return -1; + flow->uid = reg_get_proc_uid(flow->n_pid); - pthread_rwlock_wrlock(&irmd.reg_lock); - /* FALLTHRU */ - case REG_NAME_FLOW_ACCEPT: - h_pid = reg_entry_get_pid(re); - if (h_pid == -1) { - pthread_rwlock_unlock(&irmd.reg_lock); - log_err("Invalid process id returned."); - return -1; - } + log_info("Allocating flow for %d to %s (uid %d).", + flow->n_pid, dst, flow->uid); - break; - default: - pthread_rwlock_unlock(&irmd.reg_lock); - log_err("IRMd in wrong state."); - return -1; + if (get_ipcp_by_dst(dst, &flow->n_1_pid, &hash) < 0) { + log_err("Failed to find IPCP for %s.", dst); + err = -EIPCP; + goto fail_ipcp; } - pthread_rwlock_unlock(&irmd.reg_lock); - pthread_rwlock_wrlock(&irmd.flows_lock); - - flow_id = bmp_allocate(irmd.flow_ids); - if (!bmp_is_id_valid(irmd.flow_ids, flow_id)) { - pthread_rwlock_unlock(&irmd.flows_lock); - return -1; + if (reg_prepare_flow_alloc(flow) < 0) { + log_err("Failed to prepare flow allocation."); + err = -EBADF; + goto fail_prepare; } - f = irm_flow_create(h_pid, pid, flow_id, qs); - if (f == NULL) { - bmp_release(irmd.flow_ids, flow_id); - pthread_rwlock_unlock(&irmd.flows_lock); - log_err("Could not allocate flow_id."); - return -1; + if (oap_cli_prepare(&ctx, &info, &req_hdr, *data) < 0) { + log_err("Failed to prepare OAP request for %s.", dst); + err = -EBADF; + goto fail_prepare; } - if (len != 0) { - assert(data); - f->data = malloc(len); - if (f->data == NULL) { - bmp_release(irmd.flow_ids, flow_id); - pthread_rwlock_unlock(&irmd.flows_lock); - log_err("Could not piggyback data."); - return -1; - } - - f->len = len; - - memcpy(f->data, data, len); + if (ipcp_flow_alloc(flow, hash, req_hdr)) { + log_err("Flow allocation %d failed.", flow->id); + err = -EIPCP; + goto fail_alloc; } - list_add(&f->next, &irmd.irm_flows); + pthread_cleanup_push(__cleanup_flow, flow); + pthread_cleanup_push(free, hash.data); - pthread_rwlock_unlock(&irmd.flows_lock); - pthread_rwlock_rdlock(&irmd.reg_lock); + err = reg_wait_flow_allocated(flow, &resp_hdr, abstime); - reg_entry_set_state(re, REG_NAME_FLOW_ARRIVED); + pthread_cleanup_pop(false); + pthread_cleanup_pop(false); - pe = proc_table_get(&irmd.proc_table, h_pid); - if (pe == NULL) { - pthread_rwlock_unlock(&irmd.reg_lock); - pthread_rwlock_wrlock(&irmd.flows_lock); - clear_irm_flow(f); - bmp_release(irmd.flow_ids, f->flow_id); - list_del(&f->next); - pthread_rwlock_unlock(&irmd.flows_lock); - log_err("Could not get process table entry for %d.", h_pid); - free(f->data); - f->len = 0; - irm_flow_destroy(f); - return -1; + if (err == -ETIMEDOUT) { + log_err("Flow allocation timed out."); + goto fail_wait; } - proc_entry_wake(pe, re); + log_dbg("Response for flow %d to %s.", flow->id, dst); - pthread_rwlock_unlock(&irmd.reg_lock); + if (err < 0) { + log_warn("Allocation rejected: %s (%d).", dst, err); + goto fail_peer; + } - reg_entry_leave_state(re, REG_NAME_FLOW_ARRIVED, NULL); + err = oap_cli_complete(ctx, &info, resp_hdr, data, sk); + if (err < 0) { + log_err("OAP completion failed for %s.", dst); + goto fail_complete; + } - f_out->flow_id = flow_id; - f_out->n_pid = h_pid; + freebuf(req_hdr); + freebuf(resp_hdr); + freebuf(hash); return 0; + + fail_complete: + ctx = NULL; /* freee'd on complete */ + fail_peer: + flow->state = FLOW_DEALLOCATED; + fail_wait: + freebuf(resp_hdr); + fail_alloc: + freebuf(req_hdr); + oap_ctx_free(ctx); + fail_prepare: + freebuf(hash); + fail_ipcp: + reg_destroy_flow(flow->id); + fail_flow: + return err; } -static int flow_alloc_reply(int flow_id, - int response, - const void * data, - size_t len) +static int wait_for_accept(const char * name) { - struct irm_flow * f; + struct timespec timeo = TIMESPEC_INIT_MS(IRMD_REQ_ARR_TIMEOUT); + struct timespec abstime; + char ** exec; + int ret; + + clock_gettime(PTHREAD_COND_CLOCK, &abstime); + ts_add(&abstime, &timeo, &abstime); + + ret = reg_wait_flow_accepting(name, &abstime); + if (ret == -ETIMEDOUT) { + if (reg_get_exec(name, &exec) < 0) { + log_dbg("No program bound for %s.", name); + goto fail; + } - pthread_rwlock_wrlock(&irmd.flows_lock); + if (spawn_program(exec) < 0) { + log_err("Failed to start %s for %s.", exec[0], name); + goto fail_spawn; + } - f = get_irm_flow(flow_id); - if (f == NULL) { - pthread_rwlock_unlock(&irmd.flows_lock); - return -1; - } + log_info("Starting %s for %s.", exec[0], name); - if (!response) - irm_flow_set_state(f, FLOW_ALLOCATED); - else - irm_flow_set_state(f, FLOW_NULL); + ts_add(&abstime, &timeo, &abstime); - f->data = malloc(len); - if (f->data == NULL) { - pthread_rwlock_unlock(&irmd.flows_lock); - return -1; - } + ret = reg_wait_flow_accepting(name, &abstime); + if (ret == -ETIMEDOUT) + goto fail_spawn; - memcpy(f->data, data, len); - f->len = len; + argvfree(exec); + } - pthread_rwlock_unlock(&irmd.flows_lock); + return ret; - return 0; + fail_spawn: + argvfree(exec); + fail: + return -1; } -static void irm_fini(void) +static int flow_req_arr(struct flow_info * flow, + const uint8_t * hash, + buffer_t * data) { - struct list_head * p; - struct list_head * h; + struct ipcp_info info; + struct layer_info layer; + enum hash_algo algo; + int ret; + char name[NAME_SIZE + 1]; - if (irmd_get_state() != IRMD_NULL) - log_warn("Unsafe destroy."); + info.pid = flow->n_1_pid; - pthread_rwlock_wrlock(&irmd.reg_lock); + log_dbg("Flow req arrived from IPCP %d for " HASH_FMT32 ".", + info.pid, HASH_VAL32(hash)); - /* Clear the lists. */ - list_for_each_safe(p, h, &irmd.ipcps) { - struct ipcp_entry * e = list_entry(p, struct ipcp_entry, next); - list_del(&e->next); - ipcp_entry_destroy(e); + if (reg_get_ipcp(&info, &layer) < 0) { + log_err("No IPCP with pid %d.", info.pid); + ret = -EIPCP; + goto fail; } - list_for_each(p, &irmd.spawned_pids) { - struct pid_el * e = list_entry(p, struct pid_el, next); - if (kill(e->pid, SIGTERM)) - log_dbg("Could not send kill signal to %d.", e->pid); - } + algo = (enum hash_algo) layer.dir_hash_algo; - list_for_each_safe(p, h, &irmd.spawned_pids) { - struct pid_el * e = list_entry(p, struct pid_el, next); - int status; - if (waitpid(e->pid, &status, 0) < 0) - log_dbg("Error waiting for %d to exit.", e->pid); - list_del(&e->next); - registry_del_process(&irmd.registry, e->pid); - free(e); + if (reg_get_name_for_hash(name, algo, hash) < 0) { + log_warn("No name for " HASH_FMT32 ".", HASH_VAL32(hash)); + ret = -ENAME; + goto fail; } - list_for_each_safe(p, h, &irmd.prog_table) { - struct prog_entry * e = list_entry(p, struct prog_entry, next); - list_del(&e->next); - prog_entry_destroy(e); - } + log_info("Flow request arrived for %s.", name); - list_for_each_safe(p, h, &irmd.proc_table) { - struct proc_entry * e = list_entry(p, struct proc_entry, next); - list_del(&e->next); - e->state = PROC_INIT; /* sanitizer already joined */ - proc_entry_destroy(e); + ret = wait_for_accept(name); + if (ret < 0) { + log_err("No active process for %s.", name); + goto fail; } - registry_destroy(&irmd.registry); - - pthread_rwlock_unlock(&irmd.reg_lock); - - close(irmd.sockfd); - - if (unlink(IRM_SOCK_PATH)) - log_dbg("Failed to unlink %s.", IRM_SOCK_PATH); - - pthread_rwlock_wrlock(&irmd.flows_lock); - - if (irmd.flow_ids != NULL) - bmp_destroy(irmd.flow_ids); + flow->id = ret; + flow->state = FLOW_ALLOCATED; - list_for_each_safe(p, h, &irmd.irm_flows) { - struct irm_flow * f = list_entry(p, struct irm_flow, next); - list_del(&f->next); - irm_flow_destroy(f); + ret = reg_respond_accept(flow, data); + if (ret < 0) { + log_err("Failed to respond to flow %d.", flow->id); + goto fail; } - pthread_rwlock_unlock(&irmd.flows_lock); - - - if (irmd.rdrb != NULL) - shm_rdrbuff_destroy(irmd.rdrb); - - if (irmd.lf != NULL) - lockfile_destroy(irmd.lf); - - pthread_mutex_destroy(&irmd.cmd_lock); - pthread_cond_destroy(&irmd.cmd_cond); - pthread_rwlock_destroy(&irmd.flows_lock); - pthread_rwlock_destroy(&irmd.reg_lock); - pthread_rwlock_destroy(&irmd.state_lock); - -#ifdef HAVE_FUSE - if (rmdir(FUSE_PREFIX)) - log_dbg("Failed to remove " FUSE_PREFIX); -#endif + return 0; + fail: + return ret; } -void * irm_sanitize(void * o) +static int flow_alloc_reply(struct flow_info * flow, + int response, + buffer_t * data) { - struct timespec now; - struct list_head * p = NULL; - struct list_head * h = NULL; + flow->state = response != 0 ? FLOW_DEALLOCATED : FLOW_ALLOCATED; - struct timespec timeout = {IRMD_CLEANUP_TIMER / BILLION, - IRMD_CLEANUP_TIMER % BILLION}; - int s; + if (reg_respond_alloc(flow, data, response) < 0) { + log_err("Failed to reply to flow %d.", flow->id); + flow->state = FLOW_DEALLOCATED; + return -EBADF; + } - (void) o; + return 0; +} - while (true) { - if (clock_gettime(CLOCK_MONOTONIC, &now) < 0) - log_warn("Failed to get time."); - - if (irmd_get_state() != IRMD_RUNNING) - return (void *) 0; - - pthread_rwlock_wrlock(&irmd.reg_lock); - - list_for_each_safe(p, h, &irmd.spawned_pids) { - struct pid_el * e = list_entry(p, struct pid_el, next); - waitpid(e->pid, &s, WNOHANG); - if (kill(e->pid, 0) >= 0) - continue; - log_dbg("Child process %d died, error %d.", e->pid, s); - list_del(&e->next); - free(e); - } +static int flow_dealloc(struct flow_info * flow, + struct timespec * ts) +{ + log_info("Deallocating flow %d for process %d (timeout: %ld s).", + flow->id, flow->n_pid, ts->tv_sec); - list_for_each_safe(p, h, &irmd.proc_table) { - struct proc_entry * e = - list_entry(p, struct proc_entry, next); - if (kill(e->pid, 0) >= 0) - continue; - log_dbg("Dead process removed: %d.", e->pid); - list_del(&e->next); - proc_entry_destroy(e); - } + reg_dealloc_flow(flow); - list_for_each_safe(p, h, &irmd.ipcps) { - struct ipcp_entry * e = - list_entry(p, struct ipcp_entry, next); - if (kill(e->pid, 0) >= 0) - continue; - log_dbg("Dead IPCP removed: %d.", e->pid); - list_del(&e->next); - ipcp_entry_destroy(e); - } + if (ipcp_flow_dealloc(flow->n_1_pid, flow->id, ts->tv_sec) < 0) { + log_err("Failed to request dealloc from %d.", flow->n_1_pid); + return -EIPCP; + } - list_for_each_safe(p, h, &irmd.registry) { - struct list_head * p2; - struct list_head * h2; - struct reg_entry * e = - list_entry(p, struct reg_entry, next); - list_for_each_safe(p2, h2, &e->reg_pids) { - struct pid_el * a = - list_entry(p2, struct pid_el, next); - if (kill(a->pid, 0) >= 0) - continue; - log_dbg("Dead process removed from: %d %s.", - a->pid, e->name); - reg_entry_del_pid_el(e, a); - } - } + return 0; +} - pthread_rwlock_unlock(&irmd.reg_lock); - pthread_rwlock_wrlock(&irmd.flows_lock); - - list_for_each_safe(p, h, &irmd.irm_flows) { - int ipcpi; - int flow_id; - struct irm_flow * f = - list_entry(p, struct irm_flow, next); - - if (irm_flow_get_state(f) == FLOW_ALLOC_PENDING - && ts_diff_ms(&f->t0, &now) > IRMD_FLOW_TIMEOUT) { - log_dbg("Pending flow_id %d timed out.", - f->flow_id); - f->n_pid = -1; - irm_flow_set_state(f, FLOW_DEALLOC_PENDING); - continue; - } +static int flow_dealloc_resp(struct flow_info * flow) +{ + reg_dealloc_flow_resp(flow); - if (kill(f->n_pid, 0) < 0) { - log_dbg("Process %d gone, deallocating " - "flow %d.", - f->n_pid, f->flow_id); - f->n_pid = -1; - irm_flow_set_state(f, FLOW_DEALLOC_PENDING); - ipcpi = f->n_1_pid; - flow_id = f->flow_id; - pthread_rwlock_unlock(&irmd.flows_lock); - ipcp_flow_dealloc(ipcpi, flow_id, DEALLOC_TIME); - pthread_rwlock_wrlock(&irmd.flows_lock); - continue; - } + assert(flow->state == FLOW_DEALLOCATED); - if (kill(f->n_1_pid, 0) < 0) { - struct shm_flow_set * set; - log_err("IPCP %d gone, flow %d removed.", - f->n_1_pid, f->flow_id); - set = shm_flow_set_open(f->n_pid); - if (set != NULL) - shm_flow_set_destroy(set); - f->n_1_pid = -1; - irm_flow_set_state(f, FLOW_DEALLOC_PENDING); - } - } + reg_destroy_flow(flow->id); - pthread_rwlock_unlock(&irmd.flows_lock); + log_info("Completed deallocation of flow_id %d by process %d.", + flow->id, flow->n_1_pid); - nanosleep(&timeout, NULL); - } + return 0; } static void * acceptloop(void * o) { int csockfd; - struct timeval tv = {(SOCKET_TIMEOUT / 1000), - (SOCKET_TIMEOUT % 1000) * 1000}; (void) o; - while (irmd_get_state() == IRMD_RUNNING) { + while (true) { struct cmd * cmd; csockfd = accept(irmd.sockfd, 0, 0); if (csockfd < 0) continue; - if (setsockopt(csockfd, SOL_SOCKET, SO_RCVTIMEO, - (void *) &tv, sizeof(tv))) - log_warn("Failed to set timeout on socket."); - cmd = malloc(sizeof(*cmd)); if (cmd == NULL) { log_err("Out of memory."); @@ -2024,40 +1369,256 @@ static void * acceptloop(void * o) return (void *) 0; } -static void free_msg(void * o) +static void __cleanup_irm_msg(void * o) { irm_msg__free_unpacked((irm_msg_t *) o, NULL); } -static void * mainloop(void * o) +static irm_msg_t * do_command_msg(irm_msg_t * msg, + int fd) { - int sfd; - irm_msg_t * msg; - buffer_t buffer; + struct ipcp_config conf; + struct ipcp_info ipcp; + struct flow_info flow; + struct proc_info proc; + struct name_info name; + struct crypt_sk sk; + uint8_t kbuf[SYMMKEYSZ]; /* stack buffer for OAP */ + uint8_t * hbuf = NULL; /* heap copy for response */ + struct timespec * abstime; + struct timespec max = TIMESPEC_INIT_MS(FLOW_ALLOC_TIMEOUT); + struct timespec now; + struct timespec ts = TIMESPEC_INIT_S(0); /* static analysis */ + int res; + irm_msg_t * ret_msg; + buffer_t data; - (void) o; + memset(&flow, 0, sizeof(flow)); - while (true) { - irm_msg_t * ret_msg; - struct irm_flow e; - struct timespec * timeo = NULL; - struct timespec ts = {0, 0}; - struct cmd * cmd; - int result; + clock_gettime(PTHREAD_COND_CLOCK, &now); + + if (msg->timeo != NULL) { + ts = timespec_msg_to_s(msg->timeo); + ts_add(&ts, &now, &ts); + abstime = &ts; + } else { + ts_add(&max, &now, &max); + abstime = NULL; + } + + ret_msg = malloc(sizeof(*ret_msg)); + if (ret_msg == NULL) { + log_err("Failed to malloc return msg."); + return NULL; + } + + irm_msg__init(ret_msg); + + ret_msg->code = IRM_MSG_CODE__IRM_REPLY; + + pthread_cleanup_push(__cleanup_irm_msg, ret_msg); + + switch (msg->code) { + case IRM_MSG_CODE__IRM_CREATE_IPCP: + ipcp = ipcp_info_msg_to_s(msg->ipcp_info); + res = create_ipcp(&ipcp); + break; + case IRM_MSG_CODE__IPCP_CREATE_R: + ipcp = ipcp_info_msg_to_s(msg->ipcp_info); + res = create_ipcp_r(&ipcp); + break; + case IRM_MSG_CODE__IRM_DESTROY_IPCP: + res = destroy_ipcp(msg->pid); + break; + case IRM_MSG_CODE__IRM_BOOTSTRAP_IPCP: + conf = ipcp_config_msg_to_s(msg->conf); + res = bootstrap_ipcp(msg->pid, &conf); + break; + case IRM_MSG_CODE__IRM_ENROLL_IPCP: + res = enroll_ipcp(msg->pid, msg->dst); + break; + case IRM_MSG_CODE__IRM_CONNECT_IPCP: + flow.qs = qos_spec_msg_to_s(msg->qosspec); + res = connect_ipcp(msg->pid, msg->dst, msg->comp, flow.qs); + break; + case IRM_MSG_CODE__IRM_DISCONNECT_IPCP: + res = disconnect_ipcp(msg->pid, msg->dst, msg->comp); + break; + case IRM_MSG_CODE__IRM_BIND_PROGRAM: + /* Terminate with NULL instead of "" */ + free(msg->exec[msg->n_exec - 1]); + msg->exec[msg->n_exec - 1] = NULL; + res = bind_program(msg->exec, msg->name, msg->opts); + break; + case IRM_MSG_CODE__IRM_UNBIND_PROGRAM: + res = unbind_program(msg->prog, msg->name); + break; + case IRM_MSG_CODE__IRM_PROC_ANNOUNCE: + proc.pid = msg->pid; + strcpy(proc.prog, msg->prog); + res = get_peer_ids(fd, &proc.uid, &proc.gid); + if (res < 0) + log_err("Failed to get UID/GID for pid %d.", msg->pid); + else + res = proc_announce(&proc); + break; + case IRM_MSG_CODE__IRM_PROC_EXIT: + res = proc_exit(msg->pid); + break; + case IRM_MSG_CODE__IRM_BIND_PROCESS: + res = bind_process(msg->pid, msg->name); + break; + case IRM_MSG_CODE__IRM_UNBIND_PROCESS: + res = unbind_process(msg->pid, msg->name); + break; + case IRM_MSG_CODE__IRM_LIST_IPCPS: + res = list_ipcps(&ret_msg->ipcps, &ret_msg->n_ipcps); + break; + case IRM_MSG_CODE__IRM_CREATE_NAME: + name = name_info_msg_to_s(msg->name_info); + res = name_create(&name); + break; + case IRM_MSG_CODE__IRM_DESTROY_NAME: + res = name_destroy(msg->name); + break; + case IRM_MSG_CODE__IRM_LIST_NAMES: + res = list_names(&ret_msg->names, &ret_msg->n_names); + break; + case IRM_MSG_CODE__IRM_REG_NAME: + res = name_reg(msg->name, msg->pid); + break; + case IRM_MSG_CODE__IRM_UNREG_NAME: + res = name_unreg(msg->name, msg->pid); + break; + case IRM_MSG_CODE__IRM_FLOW_ACCEPT: + tpm_wait_work(irmd.tpm); + data.len = msg->pk.len; + data.data = msg->pk.data; + msg->has_pk = false; + assert(data.len > 0 ? data.data != NULL : data.data == NULL); + flow = flow_info_msg_to_s(msg->flow_info); + sk.key = kbuf; + res = flow_accept(&flow, &data, abstime, &sk); + if (res == 0) { + ret_msg->flow_info = flow_info_s_to_msg(&flow); + ret_msg->has_pk = data.len != 0; + ret_msg->pk.data = data.data; + ret_msg->pk.len = data.len; + ret_msg->has_cipher_nid = true; + ret_msg->cipher_nid = sk.nid; + if (sk.nid != NID_undef) { + hbuf = malloc(SYMMKEYSZ); + if (hbuf == NULL) { + log_err("Failed to malloc key buf"); + return NULL; + } + + memcpy(hbuf, kbuf, SYMMKEYSZ); + ret_msg->sym_key.data = hbuf; + ret_msg->sym_key.len = SYMMKEYSZ; + ret_msg->has_sym_key = true; + } + } + break; + case IRM_MSG_CODE__IRM_FLOW_ALLOC: + data.len = msg->pk.len; + data.data = msg->pk.data; + msg->has_pk = false; + assert(data.len > 0 ? data.data != NULL : data.data == NULL); + flow = flow_info_msg_to_s(msg->flow_info); + abstime = abstime == NULL ? &max : abstime; + sk.key = kbuf; + res = flow_alloc(msg->dst, &flow, &data, abstime, &sk); + if (res == 0) { + ret_msg->flow_info = flow_info_s_to_msg(&flow); + ret_msg->has_pk = data.len != 0; + ret_msg->pk.data = data.data; + ret_msg->pk.len = data.len; + ret_msg->has_cipher_nid = true; + ret_msg->cipher_nid = sk.nid; + if (sk.nid != NID_undef) { + hbuf = malloc(SYMMKEYSZ); + if (hbuf == NULL) { + log_err("Failed to malloc key buf"); + return NULL; + } + memcpy(hbuf, kbuf, SYMMKEYSZ); + ret_msg->sym_key.data = hbuf; + ret_msg->sym_key.len = SYMMKEYSZ; + ret_msg->has_sym_key = true; + } + } + break; + case IRM_MSG_CODE__IRM_FLOW_JOIN: + assert(msg->pk.len == 0 && msg->pk.data == NULL); + flow = flow_info_msg_to_s(msg->flow_info); + abstime = abstime == NULL ? &max : abstime; + res = flow_join(&flow, msg->dst, abstime); + if (res == 0) + ret_msg->flow_info = flow_info_s_to_msg(&flow); + break; + case IRM_MSG_CODE__IRM_FLOW_DEALLOC: + flow = flow_info_msg_to_s(msg->flow_info); + ts = timespec_msg_to_s(msg->timeo); + res = flow_dealloc(&flow, &ts); + break; + case IRM_MSG_CODE__IPCP_FLOW_DEALLOC: + flow = flow_info_msg_to_s(msg->flow_info); + res = flow_dealloc_resp(&flow); + break; + case IRM_MSG_CODE__IPCP_FLOW_REQ_ARR: + data.len = msg->pk.len; + data.data = msg->pk.data; + msg->pk.data = NULL; /* pass data */ + msg->pk.len = 0; + assert(data.len > 0 ? data.data != NULL : data.data == NULL); + flow = flow_info_msg_to_s(msg->flow_info); + res = flow_req_arr(&flow, msg->hash.data, &data); + if (res == 0) + ret_msg->flow_info = flow_info_s_to_msg(&flow); + break; + case IRM_MSG_CODE__IPCP_FLOW_ALLOC_REPLY: + data.len = msg->pk.len; + data.data = msg->pk.data; + msg->pk.data = NULL; /* pass data */ + msg->pk.len = 0; + assert(data.len > 0 ? data.data != NULL : data.data == NULL); + flow = flow_info_msg_to_s(msg->flow_info); + res = flow_alloc_reply(&flow, msg->response, &data); + break; + default: + log_err("Don't know that message code."); + res = -1; + break; + } + + pthread_cleanup_pop(false); + + ret_msg->has_result = true; + if (abstime == &max && res == -ETIMEDOUT) + ret_msg->result = -EPERM; /* No timeout requested */ + else + ret_msg->result = res; - memset(&e, 0, sizeof(e)); + crypt_secure_clear(kbuf, SYMMKEYSZ); - ret_msg = malloc(sizeof(*ret_msg)); - if (ret_msg == NULL) - return (void *) -1; + return ret_msg; +} + +static void * mainloop(void * o) +{ + int sfd; + irm_msg_t * msg; + buffer_t buffer; - irm_msg__init(ret_msg); + (void) o; - ret_msg->code = IRM_MSG_CODE__IRM_REPLY; + while (true) { + irm_msg_t * ret_msg; + struct cmd * cmd; pthread_mutex_lock(&irmd.cmd_lock); - pthread_cleanup_push(free_msg, ret_msg); pthread_cleanup_push(__cleanup_mutex_unlock, &irmd.cmd_lock); while (list_is_empty(&irmd.cmds)) @@ -2067,7 +1628,6 @@ static void * mainloop(void * o) list_del(&cmd->next); pthread_cleanup_pop(true); - pthread_cleanup_pop(false); msg = irm_msg__unpack(NULL, cmd->len, cmd->cbuf); sfd = cmd->fd; @@ -2075,177 +1635,35 @@ static void * mainloop(void * o) free(cmd); if (msg == NULL) { + log_err("Failed to unpack command message."); close(sfd); - irm_msg__free_unpacked(msg, NULL); continue; } - tpm_dec(irmd.tpm); - - if (msg->has_timeo_sec) { - assert(msg->has_timeo_nsec); - - ts.tv_sec = msg->timeo_sec; - ts.tv_nsec = msg->timeo_nsec; - timeo = &ts; - } + tpm_begin_work(irmd.tpm); pthread_cleanup_push(__cleanup_close_ptr, &sfd); - pthread_cleanup_push(free_msg, msg); - pthread_cleanup_push(free_msg, ret_msg); + pthread_cleanup_push(__cleanup_irm_msg, msg); - switch (msg->code) { - case IRM_MSG_CODE__IRM_CREATE_IPCP: - result = create_ipcp(msg->name, msg->ipcp_type); - break; - case IRM_MSG_CODE__IPCP_CREATE_R: - result = create_ipcp_r(msg->pid, msg->result); - break; - case IRM_MSG_CODE__IRM_DESTROY_IPCP: - result = destroy_ipcp(msg->pid); - break; - case IRM_MSG_CODE__IRM_BOOTSTRAP_IPCP: - result = bootstrap_ipcp(msg->pid, msg->conf); - break; - case IRM_MSG_CODE__IRM_ENROLL_IPCP: - result = enroll_ipcp(msg->pid, msg->dst); - break; - case IRM_MSG_CODE__IRM_CONNECT_IPCP: - result = connect_ipcp(msg->pid, msg->dst, msg->comp, - msg_to_spec(msg->qosspec)); - break; - case IRM_MSG_CODE__IRM_DISCONNECT_IPCP: - result = disconnect_ipcp(msg->pid, msg->dst, msg->comp); - break; - case IRM_MSG_CODE__IRM_BIND_PROGRAM: - result = bind_program(msg->prog, - msg->name, - msg->opts, - msg->n_args, - msg->args); - break; - case IRM_MSG_CODE__IRM_UNBIND_PROGRAM: - result = unbind_program(msg->prog, msg->name); - break; - case IRM_MSG_CODE__IRM_PROC_ANNOUNCE: - result = proc_announce(msg->pid, msg->prog); - break; - case IRM_MSG_CODE__IRM_BIND_PROCESS: - result = bind_process(msg->pid, msg->name); - break; - case IRM_MSG_CODE__IRM_UNBIND_PROCESS: - result = unbind_process(msg->pid, msg->name); - break; - case IRM_MSG_CODE__IRM_LIST_IPCPS: - result = list_ipcps(&ret_msg->ipcps, &ret_msg->n_ipcps); - break; - case IRM_MSG_CODE__IRM_CREATE_NAME: - result = name_create(msg->names[0]->name, - msg->names[0]->pol_lb); - break; - case IRM_MSG_CODE__IRM_DESTROY_NAME: - result = name_destroy(msg->name); - break; - case IRM_MSG_CODE__IRM_LIST_NAMES: - result = list_names(&ret_msg->names, &ret_msg->n_names); - break; - case IRM_MSG_CODE__IRM_REG_NAME: - result = name_reg(msg->name, msg->pid); - break; - case IRM_MSG_CODE__IRM_UNREG_NAME: - result = name_unreg(msg->name, msg->pid); - break; - case IRM_MSG_CODE__IRM_FLOW_ACCEPT: - assert(msg->pk.len > 0 ? msg->pk.data != NULL - : msg->pk.data == NULL); - result = flow_accept(msg->pid, timeo, &e, - msg->pk.data, msg->pk.len); - if (result == 0) { - qosspec_msg_t qs_msg; - ret_msg->has_flow_id = true; - ret_msg->flow_id = e.flow_id; - ret_msg->has_pid = true; - ret_msg->pid = e.n_1_pid; - qs_msg = spec_to_msg(&e.qs); - ret_msg->qosspec = &qs_msg; - ret_msg->has_pk = true; - ret_msg->pk.data = e.data; - ret_msg->pk.len = e.len; - } - break; - case IRM_MSG_CODE__IRM_FLOW_ALLOC: - assert(msg->pk.len > 0 ? msg->pk.data != NULL - : msg->pk.data == NULL); - result = flow_alloc(msg->pid, msg->dst, - msg_to_spec(msg->qosspec), - timeo, &e, false, msg->pk.data, - msg->pk.len); - if (result == 0) { - ret_msg->has_flow_id = true; - ret_msg->flow_id = e.flow_id; - ret_msg->has_pid = true; - ret_msg->pid = e.n_1_pid; - ret_msg->has_pk = true; - ret_msg->pk.data = e.data; - ret_msg->pk.len = e.len; - } - break; - case IRM_MSG_CODE__IRM_FLOW_JOIN: - assert(msg->pk.len == 0 && msg->pk.data == NULL); - result = flow_alloc(msg->pid, msg->dst, - msg_to_spec(msg->qosspec), - timeo, &e, true, NULL, 0); - if (result == 0) { - ret_msg->has_flow_id = true; - ret_msg->flow_id = e.flow_id; - ret_msg->has_pid = true; - ret_msg->pid = e.n_1_pid; - } - break; - case IRM_MSG_CODE__IRM_FLOW_DEALLOC: - result = flow_dealloc(msg->pid, - msg->flow_id, - msg->timeo_sec); - break; - case IRM_MSG_CODE__IPCP_FLOW_REQ_ARR: - assert(msg->pk.len > 0 ? msg->pk.data != NULL - : msg->pk.data == NULL); - result = flow_req_arr(msg->pid, - &e, - msg->hash.data, - msg_to_spec(msg->qosspec), - msg->pk.data, - msg->pk.len); - if (result == 0) { - ret_msg->has_flow_id = true; - ret_msg->flow_id = e.flow_id; - ret_msg->has_pid = true; - ret_msg->pid = e.n_pid; - } - break; - case IRM_MSG_CODE__IPCP_FLOW_ALLOC_REPLY: - assert(msg->pk.len > 0 ? msg->pk.data != NULL - : msg->pk.data == NULL); - result = flow_alloc_reply(msg->flow_id, - msg->response, - msg->pk.data, - msg->pk.len); - break; - default: - log_err("Don't know that message code."); - result = -1; - break; - } + ret_msg = do_command_msg(msg, sfd); - pthread_cleanup_pop(false); pthread_cleanup_pop(true); pthread_cleanup_pop(false); - if (result == -EPIPE) + if (ret_msg == NULL) { + log_err("Failed to create return message."); + goto fail_msg; + } + + if (ret_msg->result == -EPIPE) { + log_dbg("Terminated command: remote closed socket."); goto fail; + } - ret_msg->has_result = true; - ret_msg->result = result; + if (ret_msg->result == -EIRMD) { + log_dbg("Terminated command: IRMd not running."); + goto fail; + } buffer.len = irm_msg__get_packed_size(ret_msg); if (buffer.len == 0) { @@ -2261,55 +1679,231 @@ static void * mainloop(void * o) irm_msg__pack(ret_msg, buffer.data); - /* Can't free the qosspec. */ - ret_msg->qosspec = NULL; irm_msg__free_unpacked(ret_msg, NULL); pthread_cleanup_push(__cleanup_close_ptr, &sfd); + pthread_cleanup_push(free, buffer.data); + + if (write(sfd, buffer.data, buffer.len) == -1) { + if (errno != EPIPE) + log_warn("Failed to send reply message: %s.", + strerror(errno)); + else + log_dbg("Failed to send reply message: %s.", + strerror(errno)); + } - if (write(sfd, buffer.data, buffer.len) == -1) - if (result != -EIRMD) - log_warn("Failed to send reply message."); - - free(buffer.data); - + pthread_cleanup_pop(true); pthread_cleanup_pop(true); - tpm_inc(irmd.tpm); + tpm_end_work(irmd.tpm); continue; fail: irm_msg__free_unpacked(ret_msg, NULL); + fail_msg: close(sfd); - tpm_inc(irmd.tpm); + tpm_end_work(irmd.tpm); continue; } return (void *) 0; } +#ifdef HAVE_FUSE +static void destroy_mount(char * mnt) +{ + struct stat st; + + if (stat(mnt, &st) == -1){ + switch(errno) { + case ENOENT: + log_dbg("Fuse mountpoint %s not found: %s", + mnt, strerror(errno)); + break; + case ENOTCONN: + /* FALLTHRU */ + case ECONNABORTED: + log_dbg("Cleaning up fuse mountpoint %s.", + mnt); + rib_cleanup(mnt); + break; + default: + log_err("Unhandled fuse error on mnt %s: %s.", + mnt, strerror(errno)); + } + } +} +#endif + +static int ouroboros_reset(void) +{ + ssm_pool_gspp_purge(); + lockfile_destroy(irmd.lf); + + return 0; +} + +static void cleanup_pid(pid_t pid) +{ +#ifdef HAVE_FUSE + char mnt[RIB_PATH_LEN + 1]; + + if (reg_has_ipcp(pid)) { + struct ipcp_info info; + info.pid = pid; + reg_get_ipcp(&info, NULL); + sprintf(mnt, FUSE_PREFIX "/%s", info.name); + } else { + sprintf(mnt, FUSE_PREFIX "/proc.%d", pid); + } + + destroy_mount(mnt); +#endif + ssm_pool_reclaim_orphans(irmd.gspp, pid); +} + +void * irm_sanitize(void * o) +{ + pid_t pid; + struct timespec ts = TIMESPEC_INIT_MS(FLOW_ALLOC_TIMEOUT / 20); + + (void) o; + + while (true) { + while((pid = reg_get_dead_proc()) != -1) { + log_info("Process %d died.", pid); + cleanup_pid(pid); + reg_destroy_proc(pid); + } + + nanosleep(&ts, NULL); + } + + return (void *) 0; +} + +static int irm_load_store(char * dpath) +{ + struct stat st; + struct dirent * dent; + DIR * dir; + void * crt; + + if (stat(dpath, &st) == -1) { + log_dbg("Store directory %s not found.", dpath); + return 0; + } + + if (!S_ISDIR(st.st_mode)) { + log_err("%s is not a directory.", dpath); + goto fail_dir; + } + + /* loop through files in directory and load certificates */ + dir = opendir(dpath); + if (dir == NULL) { + log_err("Failed to open %s.", dpath); + goto fail_dir; + } + + while ((dent = readdir(dir)) != NULL) { + char path[NAME_PATH_SIZE + 1]; + + if (strcmp(dent->d_name, ".") == 0 || + strcmp(dent->d_name, "..") == 0) + continue; + + snprintf(path, sizeof(path), "%s/%s", dpath, + dent->d_name); + + if (stat(path, &st) == -1) { + log_dbg("Failed to stat %s.", path); + continue; + } + + if (!S_ISREG(st.st_mode)) { + log_dbg("%s is not a regular file.", path); + goto fail_file; + } + + if (crypt_load_crt_file(path, &crt) < 0) { + log_err("Failed to load certificate from %s.", path); + goto fail_file; + } + + if (oap_auth_add_ca_crt(crt) < 0) { + log_err("Failed to add certificate from %s to store.", + path); + goto fail_crt_add; + } + + log_dbg("Loaded certificate: %s.", path); + + crypt_free_crt(crt); + } + + closedir(dir); + + log_info("Loaded certificates from %s.", dpath); + + return 0; + + fail_crt_add: + crypt_free_crt(crt); + fail_file: + closedir(dir); + fail_dir: + return -1; +} + static int irm_init(void) { struct stat st; + struct group * grp; + gid_t gid; pthread_condattr_t cattr; #ifdef HAVE_FUSE mode_t mask; #endif memset(&st, 0, sizeof(st)); - if (pthread_rwlock_init(&irmd.state_lock, NULL)) { - log_err("Failed to initialize rwlock."); - goto fail_state_lock; + log_init(!irmd.log_stdout); + + irmd.lf = lockfile_create(); + if (irmd.lf == NULL) { + irmd.lf = lockfile_open(); + if (irmd.lf == NULL) { + log_err("Lockfile error."); + goto fail_lockfile; + } + + if (kill(lockfile_owner(irmd.lf), 0) < 0) { + log_warn("IRMd didn't properly shut down last time."); + if (ouroboros_reset() < 0) { + log_err("Failed to clean stale resources."); + lockfile_close(irmd.lf); + goto fail_lockfile; + } + + log_warn("Stale resources cleaned."); + irmd.lf = lockfile_create(); + } else { + log_warn("IRMd already running (%d), exiting.", + lockfile_owner(irmd.lf)); + lockfile_close(irmd.lf); + goto fail_lockfile; + } } - if (pthread_rwlock_init(&irmd.reg_lock, NULL)) { - log_err("Failed to initialize rwlock."); - goto fail_reg_lock; + if (irmd.lf == NULL) { + log_err("Failed to create lockfile."); + goto fail_lockfile; } - if (pthread_rwlock_init(&irmd.flows_lock, NULL)) { + if (pthread_rwlock_init(&irmd.state_lock, NULL)) { log_err("Failed to initialize rwlock."); - goto fail_flows_lock; + goto fail_state_lock; } if (pthread_mutex_init(&irmd.cmd_lock, NULL)) { @@ -2333,45 +1927,8 @@ static int irm_init(void) pthread_condattr_destroy(&cattr); - list_head_init(&irmd.ipcps); - list_head_init(&irmd.proc_table); - list_head_init(&irmd.prog_table); - list_head_init(&irmd.spawned_pids); - list_head_init(&irmd.registry); - list_head_init(&irmd.irm_flows); list_head_init(&irmd.cmds); - irmd.flow_ids = bmp_create(SYS_MAX_FLOWS, 0); - if (irmd.flow_ids == NULL) { - log_err("Failed to create flow_ids bitmap."); - goto fail_flow_ids; - } - - if ((irmd.lf = lockfile_create()) == NULL) { - if ((irmd.lf = lockfile_open()) == NULL) { - log_err("Lockfile error."); - goto fail_lockfile; - } - - if (kill(lockfile_owner(irmd.lf), 0) < 0) { - log_info("IRMd didn't properly shut down last time."); - shm_rdrbuff_purge(); - log_info("Stale resources cleaned."); - lockfile_destroy(irmd.lf); - irmd.lf = lockfile_create(); - } else { - log_info("IRMd already running (%d), exiting.", - lockfile_owner(irmd.lf)); - lockfile_close(irmd.lf); - goto fail_lockfile; - } - } - - if (irmd.lf == NULL) { - log_err("Failed to create lockfile."); - goto fail_lockfile; - } - if (stat(SOCK_PATH, &st) == -1) { if (mkdir(SOCK_PATH, 0777)) { log_err("Failed to create sockets directory."); @@ -2390,10 +1947,45 @@ static int irm_init(void) goto fail_sock_path; } - if ((irmd.rdrb = shm_rdrbuff_create()) == NULL) { - log_err("Failed to create rdrbuff."); - goto fail_rdrbuff; + grp = getgrnam("ouroboros"); + if (grp == NULL) { + log_warn("ouroboros group not found, using gid %d.", getgid()); + gid = getgid(); + } else { + gid = grp->gr_gid; + } + + irmd.gspp = ssm_pool_create(getuid(), gid); + if (irmd.gspp == NULL) { + log_err("Failed to create GSPP."); + goto fail_pool; + } + + if (ssm_pool_mlock(irmd.gspp) < 0) + log_warn("Failed to mlock pool."); + + irmd.tpm = tpm_create(IRMD_MIN_THREADS, IRMD_ADD_THREADS, + mainloop, NULL); + if (irmd.tpm == NULL) { + log_err("Failed to greate thread pool."); + goto fail_tpm_create; + } + + if (oap_auth_init() < 0) { + log_err("Failed to initialize OAP module."); + goto fail_oap; } + + if (irm_load_store(OUROBOROS_CA_CRT_DIR) < 0) { + log_err("Failed to load CA certificates."); + goto fail_load_store; + } + + if (irm_load_store(OUROBOROS_CHAIN_DIR) < 0) { + log_err("Failed to load intermediate certificates."); + goto fail_load_store; + } + #ifdef HAVE_FUSE mask = umask(0); @@ -2418,68 +2010,180 @@ static int irm_init(void) gcry_control(GCRYCTL_INITIALIZATION_FINISHED); #endif - irmd_set_state(IRMD_RUNNING); - log_info("Ouroboros IPC Resource Manager daemon started..."); + irmd_set_state(IRMD_INIT); return 0; #ifdef HAVE_LIBGCRYPT fail_gcry_version: -#ifdef HAVE_FUSE + #ifdef HAVE_FUSE rmdir(FUSE_PREFIX); + #endif #endif - shm_rdrbuff_destroy(irmd.rdrb); -#endif - fail_rdrbuff: + fail_load_store: + oap_auth_fini(); + fail_oap: + tpm_destroy(irmd.tpm); + fail_tpm_create: + ssm_pool_destroy(irmd.gspp); + fail_pool: close(irmd.sockfd); fail_sock_path: unlink(IRM_SOCK_PATH); fail_stat: - lockfile_destroy(irmd.lf); - fail_lockfile: - bmp_destroy(irmd.flow_ids); - fail_flow_ids: pthread_cond_destroy(&irmd.cmd_cond); fail_cmd_cond: pthread_mutex_destroy(&irmd.cmd_lock); fail_cmd_lock: - pthread_rwlock_destroy(&irmd.flows_lock); - fail_flows_lock: - pthread_rwlock_destroy(&irmd.reg_lock); - fail_reg_lock: pthread_rwlock_destroy(&irmd.state_lock); fail_state_lock: + lockfile_destroy(irmd.lf); + fail_lockfile: + log_fini(); return -1; } +static void irm_fini(void) +{ + struct list_head * p; + struct list_head * h; +#ifdef HAVE_FUSE + struct timespec wait = TIMESPEC_INIT_MS(1); + int retries = 5; +#endif + if (irmd_get_state() != IRMD_INIT) + log_warn("Unsafe destroy."); + + oap_auth_fini(); + + tpm_destroy(irmd.tpm); + + close(irmd.sockfd); + + if (unlink(IRM_SOCK_PATH)) + log_dbg("Failed to unlink %s.", IRM_SOCK_PATH); + + ssm_pool_destroy(irmd.gspp); + + if (irmd.lf != NULL) + lockfile_destroy(irmd.lf); + + pthread_mutex_lock(&irmd.cmd_lock); + + list_for_each_safe(p, h, &irmd.cmds) { + struct cmd * cmd = list_entry(p, struct cmd, next); + list_del(&cmd->next); + close(cmd->fd); + free(cmd); + } + + pthread_mutex_unlock(&irmd.cmd_lock); + + pthread_mutex_destroy(&irmd.cmd_lock); + pthread_cond_destroy(&irmd.cmd_cond); + pthread_rwlock_destroy(&irmd.state_lock); + +#ifdef HAVE_FUSE + while (rmdir(FUSE_PREFIX) < 0 && retries-- > 0) + nanosleep(&wait, NULL); + if (retries < 0) + log_err("Failed to remove " FUSE_PREFIX); +#endif + assert(list_is_empty(&irmd.cmds)); + + irmd.state = IRMD_NULL; +} + static void usage(void) { printf("Usage: irmd \n" +#ifdef HAVE_TOML + " [--config <path> (Path to configuration file)]\n" +#endif " [--stdout (Log to stdout instead of system log)]\n" " [--version (Print version number and exit)]\n" "\n"); } -int main(int argc, - char ** argv) +static int irm_start(void) { - sigset_t sigset; - bool use_stdout = false; - int sig; + irmd_set_state(IRMD_RUNNING); - sigemptyset(&sigset); - sigaddset(&sigset, SIGINT); - sigaddset(&sigset, SIGQUIT); - sigaddset(&sigset, SIGHUP); - sigaddset(&sigset, SIGTERM); - sigaddset(&sigset, SIGPIPE); + if (tpm_start(irmd.tpm)) + goto fail_tpm_start; + + if (pthread_create(&irmd.irm_sanitize, NULL, irm_sanitize, NULL)) + goto fail_irm_sanitize; + if (pthread_create(&irmd.acceptor, NULL, acceptloop, NULL)) + goto fail_acceptor; + + log_info("Ouroboros IPC Resource Manager daemon started..."); + + return 0; + + fail_acceptor: + pthread_cancel(irmd.irm_sanitize); + pthread_join(irmd.irm_sanitize, NULL); + fail_irm_sanitize: + tpm_stop(irmd.tpm); + fail_tpm_start: + irmd_set_state(IRMD_INIT); + return -1; +} + +static void irm_sigwait(sigset_t sigset) +{ + int sig; + + while (irmd_get_state() != IRMD_SHUTDOWN) { + if (sigwait(&sigset, &sig) != 0) { + log_warn("Bad signal."); + continue; + } + + switch(sig) { + case SIGINT: + case SIGQUIT: + case SIGTERM: + case SIGHUP: + log_info("IRMd shutting down..."); + irmd_set_state(IRMD_SHUTDOWN); + break; + case SIGPIPE: + log_dbg("Ignored SIGPIPE."); + break; + default: + break; + } + } +} + +static void irm_stop(void) +{ + pthread_cancel(irmd.acceptor); + pthread_cancel(irmd.irm_sanitize); + + pthread_join(irmd.acceptor, NULL); + pthread_join(irmd.irm_sanitize, NULL); + + tpm_stop(irmd.tpm); + + irmd_set_state(IRMD_INIT); +} + +static void irm_argparse(int argc, + char ** argv) +{ +#ifdef HAVE_TOML + irmd.cfg_file = NULL; +#endif argc--; argv++; while (argc > 0) { if (strcmp(*argv, "--stdout") == 0) { - use_stdout = true; + irmd.log_stdout = true; argc--; argv++; } else if (strcmp(*argv, "--version") == 0) { @@ -2488,96 +2192,148 @@ int main(int argc, OUROBOROS_VERSION_MINOR, OUROBOROS_VERSION_PATCH); exit(EXIT_SUCCESS); +#ifdef HAVE_TOML + } else if (strcmp (*argv, "--config") == 0) { + irmd.cfg_file = *(argv + 1); + argc -= 2; + argv += 2; +#endif } else { usage(); exit(EXIT_FAILURE); } } +} - if (geteuid() != 0) { - printf("IPC Resource Manager must be run as root.\n"); - exit(EXIT_FAILURE); +static void * kill_dash_nine(void * o) +{ + time_t slept = 0; +#ifdef IRMD_KILL_ALL_PROCESSES + struct timespec ts = TIMESPEC_INIT_MS(FLOW_ALLOC_TIMEOUT / 19); +#endif + (void) o; + + while (slept < IRMD_PKILL_TIMEOUT) { + time_t intv = 1; + if (reg_first_spawned() == -1) + goto finish; + sleep(intv); + slept += intv; } - log_init(!use_stdout); + log_dbg("I guess I’ll have to shut you down for good this time,"); + log_dbg("already tried a SIGQUIT, so now it’s KILL DASH 9."); +#ifdef IRMD_KILL_ALL_PROCESSES + reg_kill_all_proc(SIGKILL); + nanosleep(&ts, NULL); +#else + reg_kill_all_spawned(SIGKILL); +#endif + finish: + return (void *) 0; +} - if (irm_init() < 0) - goto fail_irm_init; +static void kill_all_spawned(void) +{ + pid_t pid; + pthread_t grimreaper; - irmd.tpm = tpm_create(IRMD_MIN_THREADS, IRMD_ADD_THREADS, - mainloop, NULL); - if (irmd.tpm == NULL) { - irmd_set_state(IRMD_NULL); - goto fail_tpm_create; +#ifdef IRMD_KILL_ALL_PROCESSES + reg_kill_all_proc(SIGTERM); +#else + reg_kill_all_spawned(SIGTERM); +#endif + pthread_create(&grimreaper, NULL, kill_dash_nine, NULL); + + pid = reg_first_spawned(); + while (pid != -1) { + int s; + if (kill(pid, 0) == 0) + waitpid(pid, &s, 0); + else { + log_warn("Child process %d died.", pid); + cleanup_pid(pid); + reg_destroy_proc(pid); + } + pid = reg_first_spawned(); } - pthread_sigmask(SIG_BLOCK, &sigset, NULL); + pthread_join(grimreaper, NULL); +} - if (tpm_start(irmd.tpm)) { - irmd_set_state(IRMD_NULL); - goto fail_tpm_start; +int main(int argc, + char ** argv) +{ + sigset_t sigset; + int ret = EXIT_SUCCESS; + + sigemptyset(&sigset); + sigaddset(&sigset, SIGINT); + sigaddset(&sigset, SIGQUIT); + sigaddset(&sigset, SIGHUP); + sigaddset(&sigset, SIGTERM); + sigaddset(&sigset, SIGPIPE); + + irm_argparse(argc, argv); + + if (irmd.log_stdout) + printf(O7S_ASCII_ART); + + if (geteuid() != 0) { + printf("IPC Resource Manager must be run as root.\n"); + goto fail_irm_init; } - if (pthread_create(&irmd.irm_sanitize, NULL, irm_sanitize, NULL)) { - irmd_set_state(IRMD_NULL); - goto fail_irm_sanitize; + if (irm_init() < 0) + goto fail_irm_init; + + if (reg_init() < 0) { + log_err("Failed to initialize registry."); + goto fail_reg; } - if (pthread_create(&irmd.acceptor, NULL, acceptloop, NULL)) { - irmd_set_state(IRMD_NULL); - goto fail_acceptor; + if (crypt_secure_malloc_init(IRMD_SECMEM_MAX) < 0) { + log_err("Failed to initialize secure memory allocation."); + goto fail_reg; } - while (irmd_get_state() != IRMD_NULL) { - if (sigwait(&sigset, &sig) != 0) { - log_warn("Bad signal."); - continue; - } + pthread_sigmask(SIG_BLOCK, &sigset, NULL); - switch(sig) { - case SIGINT: - case SIGQUIT: - case SIGTERM: - case SIGHUP: - log_info("IRMd shutting down..."); - irmd_set_state(IRMD_NULL); - break; - case SIGPIPE: - log_dbg("Ignored SIGPIPE."); - break; - default: - break; - } + if (irm_start() < 0) + goto fail_irm_start; + +#ifdef HAVE_TOML + if (irm_configure(irmd.cfg_file) < 0) { + irmd_set_state(IRMD_SHUTDOWN); + ret = EXIT_FAILURE; } +#endif + irm_sigwait(sigset); - pthread_cancel(irmd.acceptor); + kill_all_spawned(); - pthread_join(irmd.acceptor, NULL); - pthread_join(irmd.irm_sanitize, NULL); + irm_stop(); - tpm_stop(irmd.tpm); + pthread_sigmask(SIG_UNBLOCK, &sigset, NULL); - tpm_destroy(irmd.tpm); + crypt_secure_malloc_fini(); - irm_fini(); + reg_clear(); - pthread_sigmask(SIG_UNBLOCK, &sigset, NULL); + reg_fini(); + + irm_fini(); - log_info("Bye."); + log_info("Ouroboros IPC Resource Manager daemon exited. Bye."); log_fini(); - exit(EXIT_SUCCESS); + exit(ret); - fail_acceptor: - pthread_join(irmd.irm_sanitize, NULL); - fail_irm_sanitize: - tpm_stop(irmd.tpm); - fail_tpm_start: - tpm_destroy(irmd.tpm); - fail_tpm_create: + fail_irm_start: + reg_fini(); + fail_reg: irm_fini(); fail_irm_init: - log_fini(); exit(EXIT_FAILURE); } diff --git a/src/irmd/oap.c b/src/irmd/oap.c new file mode 100644 index 00000000..085e06a3 --- /dev/null +++ b/src/irmd/oap.c @@ -0,0 +1,130 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OAP - Shared credential and configuration loading + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#if defined(__linux__) || defined(__CYGWIN__) + #define _DEFAULT_SOURCE +#else + #define _POSIX_C_SOURCE 200809L +#endif + +#define OUROBOROS_PREFIX "irmd/oap" + +#include <ouroboros/crypt.h> +#include <ouroboros/errno.h> +#include <ouroboros/logs.h> + +#include "config.h" + +#include <assert.h> +#include <string.h> +#include <sys/stat.h> + +/* + * Shared credential and configuration loading helpers + */ + +#ifndef OAP_TEST_MODE + +static bool file_exists(const char * path) +{ + struct stat s; + + if (stat(path, &s) < 0 && errno == ENOENT) { + log_dbg("File %s does not exist.", path); + return false; + } + + return true; +} + +int load_credentials(const char * name, + const struct name_sec_paths * paths, + void ** pkp, + void ** crt) +{ + assert(paths != NULL); + assert(pkp != NULL); + assert(crt != NULL); + + *pkp = NULL; + *crt = NULL; + + if (!file_exists(paths->crt) || !file_exists(paths->key)) { + log_info("No authentication certificates for %s.", name); + return 0; + } + + if (crypt_load_crt_file(paths->crt, crt) < 0) { + log_err("Failed to load %s for %s.", paths->crt, name); + goto fail_crt; + } + + if (crypt_load_privkey_file(paths->key, pkp) < 0) { + log_err("Failed to load %s for %s.", paths->key, name); + goto fail_key; + } + + log_info("Loaded authentication certificates for %s.", name); + + return 0; + + fail_key: + crypt_free_crt(*crt); + *crt = NULL; + fail_crt: + return -EAUTH; +} + +int load_kex_config(const char * name, + const char * path, + struct sec_config * cfg) +{ + assert(name != NULL); + assert(cfg != NULL); + + memset(cfg, 0, sizeof(*cfg)); + + /* Load encryption config */ + if (!file_exists(path)) + log_dbg("No encryption %s for %s.", path, name); + + if (load_sec_config_file(cfg, path) < 0) { + log_warn("Failed to load %s for %s.", path, name); + return -1; + } + + if (!IS_KEX_ALGO_SET(cfg)) { + log_info("Key exchange not configured for %s.", name); + return 0; + } + + if (cfg->c.nid == NID_undef || crypt_nid_to_str(cfg->c.nid) == NULL) { + log_err("Invalid cipher NID %d for %s.", cfg->c.nid, name); + return -ECRYPT; + } + + log_info("Encryption enabled for %s.", name); + + return 0; +} + +#endif /* OAP_TEST_MODE */ diff --git a/src/irmd/oap.h b/src/irmd/oap.h new file mode 100644 index 00000000..25c07408 --- /dev/null +++ b/src/irmd/oap.h @@ -0,0 +1,67 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * Ouroboros Allocation Protocol (OAP) Component + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#ifndef OUROBOROS_IRMD_OAP_H +#define OUROBOROS_IRMD_OAP_H + +#include <ouroboros/crypt.h> +#include <ouroboros/flow.h> +#include <ouroboros/name.h> +#include <ouroboros/utils.h> + +/* OAP authentication state (in oap/auth.c) */ +int oap_auth_init(void); + +void oap_auth_fini(void); + +int oap_auth_add_ca_crt(void * crt); + +/* +* Prepare OAP request header for server, returns context +* Passes client data for srv, returns srv data for client +*/ +int oap_cli_prepare(void ** ctx, + const struct name_info * info, + buffer_t * req_buf, + buffer_t data); + +/* + * Server processes header, creates response header, returns secret key. + * data is in/out: input=srv data to send, output=cli data received. + */ +int oap_srv_process(const struct name_info * info, + buffer_t req_buf, + buffer_t * rsp_buf, + buffer_t * data, + struct crypt_sk * sk); + +/* Complete OAP, returns secret key and server data, frees ctx */ +int oap_cli_complete(void * ctx, + const struct name_info * info, + buffer_t rsp_buf, + buffer_t * data, + struct crypt_sk * sk); + +/* Free OAP state (on failure before complete) */ +void oap_ctx_free(void * ctx); + +#endif /* OUROBOROS_IRMD_OAP_H */ diff --git a/src/irmd/oap/auth.c b/src/irmd/oap/auth.c new file mode 100644 index 00000000..cea7b7a0 --- /dev/null +++ b/src/irmd/oap/auth.c @@ -0,0 +1,252 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OAP - Authentication, replay detection, and validation + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#if defined(__linux__) || defined(__CYGWIN__) + #define _DEFAULT_SOURCE +#else + #define _POSIX_C_SOURCE 200809L +#endif + +#define OUROBOROS_PREFIX "irmd/oap" + +#include <ouroboros/crypt.h> +#include <ouroboros/errno.h> +#include <ouroboros/list.h> +#include <ouroboros/logs.h> +#include <ouroboros/pthread.h> +#include <ouroboros/time.h> + +#include "config.h" + +#include "auth.h" +#include "hdr.h" + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +struct oap_replay_entry { + struct list_head next; + uint64_t timestamp; + uint8_t id[OAP_ID_SIZE]; +}; + +static struct { + struct auth_ctx * ca_ctx; + struct { + struct list_head list; + pthread_mutex_t mtx; + } replay; +} oap_auth; + +int oap_auth_init(void) +{ + oap_auth.ca_ctx = auth_create_ctx(); + if (oap_auth.ca_ctx == NULL) { + log_err("Failed to create OAP auth context."); + goto fail_ctx; + } + + list_head_init(&oap_auth.replay.list); + + if (pthread_mutex_init(&oap_auth.replay.mtx, NULL)) { + log_err("Failed to init OAP replay mutex."); + goto fail_mtx; + } + + return 0; + + fail_mtx: + auth_destroy_ctx(oap_auth.ca_ctx); + fail_ctx: + return -1; +} + +void oap_auth_fini(void) +{ + struct list_head * p; + struct list_head * h; + + pthread_mutex_lock(&oap_auth.replay.mtx); + + list_for_each_safe(p, h, &oap_auth.replay.list) { + struct oap_replay_entry * e; + e = list_entry(p, struct oap_replay_entry, next); + list_del(&e->next); + free(e); + } + + pthread_mutex_unlock(&oap_auth.replay.mtx); + pthread_mutex_destroy(&oap_auth.replay.mtx); + + auth_destroy_ctx(oap_auth.ca_ctx); +} + +int oap_auth_add_ca_crt(void * crt) +{ + return auth_add_crt_to_store(oap_auth.ca_ctx, crt); +} + +#define TIMESYNC_SLACK 100 /* ms */ +#define ID_IS_EQUAL(id1, id2) (memcmp(id1, id2, OAP_ID_SIZE) == 0) +int oap_check_hdr(const struct oap_hdr * hdr) +{ + struct list_head * p; + struct list_head * h; + struct timespec now; + struct oap_replay_entry * new; + uint64_t stamp; + uint64_t cur; + uint8_t * id; + ssize_t delta; + + assert(hdr != NULL); + + stamp = hdr->timestamp; + id = hdr->id.data; + + clock_gettime(CLOCK_REALTIME, &now); + + cur = TS_TO_UINT64(now); + + delta = (ssize_t)(cur - stamp) / MILLION; + if (delta < -TIMESYNC_SLACK) { + log_err_id(id, "OAP header from %zd ms into future.", -delta); + goto fail_stamp; + } + + if (delta > OAP_REPLAY_TIMER * 1000) { + log_err_id(id, "OAP header too old (%zd ms).", delta); + goto fail_stamp; + } + + new = malloc(sizeof(*new)); + if (new == NULL) { + log_err_id(id, "Failed to allocate memory for OAP element."); + goto fail_stamp; + } + + pthread_mutex_lock(&oap_auth.replay.mtx); + + list_for_each_safe(p, h, &oap_auth.replay.list) { + struct oap_replay_entry * e; + e = list_entry(p, struct oap_replay_entry, next); + if (cur > e->timestamp + OAP_REPLAY_TIMER * BILLION) { + list_del(&e->next); + free(e); + continue; + } + + if (e->timestamp == stamp && ID_IS_EQUAL(e->id, id)) { + log_warn_id(id, "OAP header already known."); + goto fail_replay; + } + } + + memcpy(new->id, id, OAP_ID_SIZE); + new->timestamp = stamp; + + list_add_tail(&new->next, &oap_auth.replay.list); + + pthread_mutex_unlock(&oap_auth.replay.mtx); + + return 0; + + fail_replay: + pthread_mutex_unlock(&oap_auth.replay.mtx); + free(new); + fail_stamp: + return -EAUTH; +} + +int oap_auth_peer(char * name, + const struct oap_hdr * local_hdr, + const struct oap_hdr * peer_hdr) +{ + void * crt; + void * pk; + buffer_t sign; /* Signed region */ + uint8_t * id = peer_hdr->id.data; + + assert(name != NULL); + assert(local_hdr != NULL); + assert(peer_hdr != NULL); + + if (memcmp(peer_hdr->id.data, local_hdr->id.data, OAP_ID_SIZE) != 0) { + log_err_id(id, "OAP ID mismatch in flow allocation."); + goto fail_check; + } + + if (peer_hdr->crt.len == 0) { + log_dbg_id(id, "No crt provided."); + name[0] = '\0'; + return 0; + } + + if (crypt_load_crt_der(peer_hdr->crt, &crt) < 0) { + log_err_id(id, "Failed to load crt."); + goto fail_check; + } + + log_dbg_id(id, "Loaded peer crt."); + + if (crypt_get_pubkey_crt(crt, &pk) < 0) { + log_err_id(id, "Failed to get pubkey from crt."); + goto fail_crt; + } + + log_dbg_id(id, "Got public key from crt."); + + if (auth_verify_crt(oap_auth.ca_ctx, crt) < 0) { + log_err_id(id, "Failed to verify peer with CA store."); + goto fail_crt; + } + + log_dbg_id(id, "Successfully verified peer crt."); + + sign = peer_hdr->hdr; + sign.len -= peer_hdr->sig.len; + + if (auth_verify_sig(pk, peer_hdr->md_nid, sign, peer_hdr->sig) < 0) { + log_err_id(id, "Failed to verify signature."); + goto fail_check_sig; + } + + if (crypt_get_crt_name(crt, name) < 0) { + log_warn_id(id, "Failed to extract name from certificate."); + name[0] = '\0'; + } + + crypt_free_key(pk); + crypt_free_crt(crt); + + log_dbg_id(id, "Successfully authenticated peer."); + + return 0; + + fail_check_sig: + crypt_free_key(pk); + fail_crt: + crypt_free_crt(crt); + fail_check: + return -EAUTH; +} diff --git a/src/irmd/utils.h b/src/irmd/oap/auth.h index 5af918fd..07c33a23 100644 --- a/src/irmd/utils.h +++ b/src/irmd/oap/auth.h @@ -1,7 +1,7 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * - * Utils of the IPC Resource Manager + * OAP - Authentication functions * * Dimitri Staessens <dimitri@ouroboros.rocks> * Sander Vrijders <sander@ouroboros.rocks> @@ -20,24 +20,16 @@ * Foundation, Inc., http://www.fsf.org/about/contact/. */ -#ifndef OUROBOROS_IRMD_UTILS_H -#define OUROBOROS_IRMD_UTILS_H +#ifndef OUROBOROS_IRMD_OAP_AUTH_H +#define OUROBOROS_IRMD_OAP_AUTH_H -#include <sys/types.h> +#include "hdr.h" -struct str_el { - struct list_head next; - char * str; -}; +int oap_check_hdr(const struct oap_hdr * hdr); -struct pid_el { - struct list_head next; - pid_t pid; -}; +/* name is updated with the peer's certificate name if available */ +int oap_auth_peer(char * name, + const struct oap_hdr * local_hdr, + const struct oap_hdr * peer_hdr); -/* functions for copying and destroying arguments list */ -char ** argvdup(char ** argv); - -void argvfree(char ** argv); - -#endif /* OUROBOROS_IRM_UTILS_H */ +#endif /* OUROBOROS_IRMD_OAP_AUTH_H */ diff --git a/src/irmd/oap/cli.c b/src/irmd/oap/cli.c new file mode 100644 index 00000000..ea2a25d1 --- /dev/null +++ b/src/irmd/oap/cli.c @@ -0,0 +1,553 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OAP - Client-side processing + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#if defined(__linux__) || defined(__CYGWIN__) + #define _DEFAULT_SOURCE +#else + #define _POSIX_C_SOURCE 200809L +#endif + +#define OUROBOROS_PREFIX "irmd/oap" + +#include <ouroboros/crypt.h> +#include <ouroboros/errno.h> +#include <ouroboros/logs.h> +#include <ouroboros/random.h> + +#include "config.h" + +#include "auth.h" +#include "hdr.h" +#include "io.h" +#include "../oap.h" + +#include <assert.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* Client context between oap_cli_prepare and oap_cli_complete */ +struct oap_cli_ctx { + uint8_t __id[OAP_ID_SIZE]; + buffer_t id; + uint8_t kex_buf[MSGBUFSZ]; + uint8_t req_hash[MAX_HASH_SIZE]; + size_t req_hash_len; + int req_md_nid; + struct sec_config kcfg; + struct oap_hdr local_hdr; + void * pkp; /* Ephemeral keypair */ + uint8_t * key; /* For client-encap KEM */ +}; + +#define OAP_CLI_CTX_INIT(s) \ + do { s->id.len = OAP_ID_SIZE; s->id.data = s->__id; } while (0) + +/* Client-side credential loading, mocked in tests */ + +#ifdef OAP_TEST_MODE +extern int load_cli_credentials(const struct name_info * info, + void ** pkp, + void ** crt); +extern int load_cli_kex_config(const struct name_info * info, + struct sec_config * cfg); +extern int load_server_kem_pk(const char * name, + struct sec_config * cfg, + buffer_t * buf); +#else + +int load_cli_credentials(const struct name_info * info, + void ** pkp, + void ** crt) +{ + assert(info != NULL); + assert(pkp != NULL); + assert(crt != NULL); + + return load_credentials(info->name, &info->c, pkp, crt); +} + +int load_cli_kex_config(const struct name_info * info, + struct sec_config * cfg) +{ + assert(info != NULL); + assert(cfg != NULL); + + return load_kex_config(info->name, info->c.enc, cfg); +} + +int load_server_kem_pk(const char * name, + struct sec_config * cfg, + buffer_t * pk) +{ + char path[PATH_MAX]; + const char * ext; + + assert(name != NULL); + assert(cfg != NULL); + assert(pk != NULL); + + ext = IS_HYBRID_KEM(cfg->x.str) ? "raw" : "pem"; + + snprintf(path, sizeof(path), + OUROBOROS_CLI_CRT_DIR "/%s/kex.srv.pub.%s", name, ext); + + if (IS_HYBRID_KEM(cfg->x.str)) { + if (crypt_load_pubkey_raw_file(path, pk) < 0) { + log_err("Failed to load %s pubkey from %s.", ext, path); + return -1; + } + } else { + if (crypt_load_pubkey_file_to_der(path, pk) < 0) { + log_err("Failed to load %s pubkey from %s.", ext, path); + return -1; + } + } + + log_dbg("Loaded %s pubkey from %s (%zu bytes).", ext, path, pk->len); + + return 0; +} + +#endif /* OAP_TEST_MODE */ + +static int do_client_kex_prepare_dhe(struct oap_cli_ctx * s) +{ + struct sec_config * kcfg = &s->kcfg; + buffer_t * kex = &s->local_hdr.kex; + uint8_t * id = s->id.data; + ssize_t len; + + /* Generate ephemeral keypair, send PK */ + len = kex_pkp_create(kcfg, &s->pkp, kex->data); + if (len < 0) { + log_err_id(id, "Failed to generate DHE keypair."); + return -ECRYPT; + } + + kex->len = (size_t) len; + log_dbg_id(id, "Generated ephemeral %s keys (%zd bytes).", + kcfg->x.str, len); + + return 0; +} + +static int do_client_kex_prepare_kem_encap(const char * server_name, + struct oap_cli_ctx * s) +{ + struct sec_config * kcfg = &s->kcfg; + buffer_t * kex = &s->local_hdr.kex; + uint8_t * id = s->id.data; + buffer_t server_pk = BUF_INIT; + uint8_t key_buf[SYMMKEYSZ]; + ssize_t len; + + if (load_server_kem_pk(server_name, kcfg, &server_pk) < 0) { + log_err_id(id, "Failed to load server KEM pk."); + return -ECRYPT; + } + + if (IS_HYBRID_KEM(kcfg->x.str)) + len = kex_kem_encap_raw(server_pk, kex->data, + kcfg->k.nid, key_buf); + else + len = kex_kem_encap(server_pk, kex->data, + kcfg->k.nid, key_buf); + + freebuf(server_pk); + + if (len < 0) { + log_err_id(id, "Failed to encapsulate KEM."); + return -ECRYPT; + } + + kex->len = (size_t) len; + log_dbg_id(id, "Client encaps: CT len=%zd.", len); + + /* Store derived key */ + s->key = crypt_secure_malloc(SYMMKEYSZ); + if (s->key == NULL) { + log_err_id(id, "Failed to allocate secure key."); + return -ENOMEM; + } + memcpy(s->key, key_buf, SYMMKEYSZ); + crypt_secure_clear(key_buf, SYMMKEYSZ); + + return 0; +} + +static int do_client_kex_prepare_kem_decap(struct oap_cli_ctx * s) +{ + struct sec_config * kcfg = &s->kcfg; + buffer_t * kex = &s->local_hdr.kex; + uint8_t * id = s->id.data; + ssize_t len; + + /* Server encaps: generate keypair, send PK */ + len = kex_pkp_create(kcfg, &s->pkp, kex->data); + if (len < 0) { + log_err_id(id, "Failed to generate KEM keypair."); + return -ECRYPT; + } + + kex->len = (size_t) len; + log_dbg_id(id, "Client PK for server encaps (%zd bytes).", len); + + return 0; +} + +static int do_client_kex_prepare(const char * server_name, + struct oap_cli_ctx * s) +{ + struct sec_config * kcfg = &s->kcfg; + + if (!IS_KEX_ALGO_SET(kcfg)) + return 0; + + if (IS_KEM_ALGORITHM(kcfg->x.str)) { + if (kcfg->x.mode == KEM_MODE_CLIENT_ENCAP) + return do_client_kex_prepare_kem_encap(server_name, s); + else + return do_client_kex_prepare_kem_decap(s); + } + + return do_client_kex_prepare_dhe(s); +} + +int oap_cli_prepare(void ** ctx, + const struct name_info * info, + buffer_t * req_buf, + buffer_t data) +{ + struct oap_cli_ctx * s; + void * pkp = NULL; + void * crt = NULL; + ssize_t ret; + + assert(ctx != NULL); + assert(info != NULL); + assert(req_buf != NULL); + + clrbuf(*req_buf); + *ctx = NULL; + + /* Allocate ctx to carry between prepare and complete */ + s = malloc(sizeof(*s)); + if (s == NULL) { + log_err("Failed to allocate OAP client ctx."); + return -ENOMEM; + } + + memset(s, 0, sizeof(*s)); + OAP_CLI_CTX_INIT(s); + + /* Generate session ID */ + if (random_buffer(s->__id, OAP_ID_SIZE) < 0) { + log_err("Failed to generate OAP session ID."); + goto fail_id; + } + + log_dbg_id(s->id.data, "Preparing OAP request for %s.", info->name); + + /* Load client credentials */ + if (load_cli_credentials(info, &pkp, &crt) < 0) { + log_err_id(s->id.data, "Failed to load credentials for %s.", + info->name); + goto fail_id; + } + + /* Load KEX config */ + if (load_cli_kex_config(info, &s->kcfg) < 0) { + log_err_id(s->id.data, "Failed to load KEX config for %s.", + info->name); + goto fail_kex; + } + + log_dbg_id(s->id.data, "KEX config: algo=%s, mode=%s, cipher=%s.", + s->kcfg.x.str != NULL ? s->kcfg.x.str : "none", + s->kcfg.x.mode == KEM_MODE_CLIENT_ENCAP ? "client-encap" : + s->kcfg.x.mode == KEM_MODE_SERVER_ENCAP ? "server-encap" : + "none", + s->kcfg.c.str != NULL ? s->kcfg.c.str : "none"); + + oap_hdr_init(&s->local_hdr, s->id, s->kex_buf, data, s->kcfg.c.nid); + + if (do_client_kex_prepare(info->name, s) < 0) { + log_err_id(s->id.data, "Failed to prepare client KEX."); + goto fail_kex; + } + + if (oap_hdr_encode(&s->local_hdr, pkp, crt, &s->kcfg, + (buffer_t) BUF_INIT, NID_undef)) { + log_err_id(s->id.data, "Failed to create OAP request header."); + goto fail_hdr; + } + + debug_oap_hdr_snd(&s->local_hdr); + + /* Compute and store hash of request for verification in complete */ + s->req_md_nid = s->kcfg.d.nid != NID_undef ? s->kcfg.d.nid : NID_sha384; + ret = md_digest(s->req_md_nid, s->local_hdr.hdr, s->req_hash); + if (ret < 0) { + log_err_id(s->id.data, "Failed to hash request."); + goto fail_hash; + } + s->req_hash_len = (size_t) ret; + + /* Transfer ownership of request buffer */ + *req_buf = s->local_hdr.hdr; + clrbuf(s->local_hdr.hdr); + + crypt_free_crt(crt); + crypt_free_key(pkp); + + *ctx = s; + + log_dbg_id(s->id.data, "OAP request prepared for %s.", info->name); + + return 0; + + fail_hash: + fail_hdr: + crypt_secure_free(s->key, SYMMKEYSZ); + crypt_free_key(s->pkp); + fail_kex: + crypt_free_crt(crt); + crypt_free_key(pkp); + fail_id: + free(s); + return -ECRYPT; +} + +void oap_ctx_free(void * ctx) +{ + struct oap_cli_ctx * s = ctx; + + if (s == NULL) + return; + + oap_hdr_fini(&s->local_hdr); + + if (s->pkp != NULL) + crypt_free_key(s->pkp); + + if (s->key != NULL) + crypt_secure_free(s->key, SYMMKEYSZ); + + memset(s, 0, sizeof(*s)); + free(s); +} + +static int do_client_kex_complete_kem(struct oap_cli_ctx * s, + const struct oap_hdr * peer_hdr, + struct crypt_sk * sk) +{ + struct sec_config * kcfg = &s->kcfg; + uint8_t * id = s->id.data; + uint8_t key_buf[SYMMKEYSZ]; + + if (kcfg->x.mode == KEM_MODE_SERVER_ENCAP) { + buffer_t ct; + + if (peer_hdr->kex.len == 0) { + log_err_id(id, "Server did not send KEM CT."); + return -ECRYPT; + } + + ct.data = peer_hdr->kex.data; + ct.len = peer_hdr->kex.len; + + if (kex_kem_decap(s->pkp, ct, kcfg->k.nid, key_buf) < 0) { + log_err_id(id, "Failed to decapsulate KEM."); + return -ECRYPT; + } + + log_dbg_id(id, "Client decapsulated server CT."); + + } else if (kcfg->x.mode == KEM_MODE_CLIENT_ENCAP) { + /* Key already derived during prepare */ + memcpy(sk->key, s->key, SYMMKEYSZ); + sk->nid = kcfg->c.nid; + log_info_id(id, "Negotiated %s + %s.", kcfg->x.str, + kcfg->c.str); + return 0; + } + + memcpy(sk->key, key_buf, SYMMKEYSZ); + sk->nid = kcfg->c.nid; + crypt_secure_clear(key_buf, SYMMKEYSZ); + + log_info_id(id, "Negotiated %s + %s.", kcfg->x.str, kcfg->c.str); + + return 0; +} + +static int do_client_kex_complete_dhe(struct oap_cli_ctx * s, + const struct oap_hdr * peer_hdr, + struct crypt_sk * sk) +{ + struct sec_config * kcfg = &s->kcfg; + uint8_t * id = s->id.data; + uint8_t key_buf[SYMMKEYSZ]; + + /* DHE: derive from server's public key */ + if (peer_hdr->kex.len == 0) { + log_err_id(id, "Server did not send DHE public key."); + return -ECRYPT; + } + + if (kex_dhe_derive(kcfg, s->pkp, peer_hdr->kex, key_buf) < 0) { + log_err_id(id, "Failed to derive DHE secret."); + return -ECRYPT; + } + + log_dbg_id(id, "DHE: derived shared secret."); + + memcpy(sk->key, key_buf, SYMMKEYSZ); + sk->nid = kcfg->c.nid; + crypt_secure_clear(key_buf, SYMMKEYSZ); + + log_info_id(id, "Negotiated %s + %s.", kcfg->x.str, kcfg->c.str); + + return 0; +} + + +static int do_client_kex_complete(struct oap_cli_ctx * s, + const struct oap_hdr * peer_hdr, + struct crypt_sk * sk) +{ + struct sec_config * kcfg = &s->kcfg; + uint8_t * id = s->id.data; + + if (!IS_KEX_ALGO_SET(kcfg)) + return 0; + + /* Accept server's cipher choice */ + if (peer_hdr->cipher_str == NULL) { + log_err_id(id, "Server did not provide cipher."); + return -ECRYPT; + } + + SET_KEX_CIPHER(kcfg, peer_hdr->cipher_str); + if (crypt_validate_nid(kcfg->c.nid) < 0) { + log_err_id(id, "Server cipher '%s' not supported.", + peer_hdr->cipher_str); + return -ENOTSUP; + } + + log_dbg_id(id, "Accepted server cipher %s.", peer_hdr->cipher_str); + + /* Derive shared secret */ + if (IS_KEM_ALGORITHM(kcfg->x.str)) + return do_client_kex_complete_kem(s, peer_hdr, sk); + + return do_client_kex_complete_dhe(s, peer_hdr, sk); +} + +int oap_cli_complete(void * ctx, + const struct name_info * info, + buffer_t rsp_buf, + buffer_t * data, + struct crypt_sk * sk) +{ + struct oap_cli_ctx * s = ctx; + struct oap_hdr peer_hdr; + char peer[NAME_SIZE + 1]; + uint8_t * id; + + assert(ctx != NULL); + assert(info != NULL); + assert(data != NULL); + assert(sk != NULL); + + sk->nid = NID_undef; + + clrbuf(*data); + + memset(&peer_hdr, 0, sizeof(peer_hdr)); + + id = s->id.data; + + log_dbg_id(id, "Completing OAP for %s.", info->name); + + /* Decode response header using client's md_nid for hash length */ + if (oap_hdr_decode(&peer_hdr, rsp_buf, s->req_md_nid) < 0) { + log_err_id(id, "Failed to decode OAP response header."); + goto fail_oap; + } + + debug_oap_hdr_rcv(&peer_hdr); + + /* Verify response ID matches request */ + if (memcmp(peer_hdr.id.data, id, OAP_ID_SIZE) != 0) { + log_err_id(id, "OAP response ID mismatch."); + goto fail_oap; + } + + /* Authenticate server */ + if (oap_auth_peer(peer, &s->local_hdr, &peer_hdr) < 0) { + log_err_id(id, "Failed to authenticate server."); + goto fail_oap; + } + + /* Verify request hash in authenticated response */ + if (peer_hdr.req_hash.len == 0) { + log_err_id(id, "Response missing req_hash."); + goto fail_oap; + } + + if (memcmp(peer_hdr.req_hash.data, s->req_hash, s->req_hash_len) != 0) { + log_err_id(id, "Response req_hash mismatch."); + goto fail_oap; + } + + /* Verify peer certificate name matches expected destination */ + if (peer_hdr.crt.len > 0 && strcmp(peer, info->name) != 0) { + log_err_id(id, "Peer crt for '%s' does not match '%s'.", + peer, info->name); + goto fail_oap; + } + + /* Complete key exchange */ + if (do_client_kex_complete(s, &peer_hdr, sk) < 0) { + log_err_id(id, "Failed to complete key exchange."); + goto fail_oap; + } + + /* Copy piggybacked data from server response */ + if (oap_hdr_copy_data(&peer_hdr, data) < 0) { + log_err_id(id, "Failed to copy server data."); + goto fail_oap; + } + + log_info_id(id, "OAP completed for %s.", info->name); + + oap_ctx_free(s); + + return 0; + + fail_oap: + oap_ctx_free(s); + return -ECRYPT; +} diff --git a/src/irmd/oap/hdr.c b/src/irmd/oap/hdr.c new file mode 100644 index 00000000..cdff7ab6 --- /dev/null +++ b/src/irmd/oap/hdr.c @@ -0,0 +1,456 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OAP - Header encoding, decoding, and debugging + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#if defined(__linux__) || defined(__CYGWIN__) + #define _DEFAULT_SOURCE +#else + #define _POSIX_C_SOURCE 200809L +#endif + +#define OUROBOROS_PREFIX "irmd/oap" + +#include <ouroboros/crypt.h> +#include <ouroboros/endian.h> +#include <ouroboros/hash.h> +#include <ouroboros/logs.h> +#include <ouroboros/rib.h> +#include <ouroboros/time.h> + +#include "config.h" + +#include "hdr.h" + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +int oap_hdr_decode(struct oap_hdr * oap_hdr, + buffer_t hdr, + int req_md_nid) +{ + off_t offset; + uint16_t kex_len; + uint16_t ciph_nid; + size_t crt_len; + size_t data_len; + size_t hash_len; + size_t sig_len; + + assert(oap_hdr != NULL); + memset(oap_hdr, 0, sizeof(*oap_hdr)); + + if (hdr.len < OAP_HDR_MIN_SIZE) + goto fail_decode; + + /* Parse fixed header (36 bytes) */ + oap_hdr->id.data = hdr.data; + oap_hdr->id.len = OAP_ID_SIZE; + + offset = OAP_ID_SIZE; + + oap_hdr->timestamp = ntoh64(*(uint64_t *)(hdr.data + offset)); + offset += sizeof(uint64_t); + + /* cipher NID */ + ciph_nid = ntoh16(*(uint16_t *)(hdr.data + offset)); + oap_hdr->nid = ciph_nid; + oap_hdr->cipher_str = crypt_nid_to_str(ciph_nid); + offset += sizeof(uint16_t); + + /* kdf NID */ + oap_hdr->kdf_nid = ntoh16(*(uint16_t *)(hdr.data + offset)); + oap_hdr->kdf_str = md_nid_to_str(oap_hdr->kdf_nid); + offset += sizeof(uint16_t); + + /* md NID (signature hash) */ + oap_hdr->md_nid = ntoh16(*(uint16_t *)(hdr.data + offset)); + oap_hdr->md_str = md_nid_to_str(oap_hdr->md_nid); + offset += sizeof(uint16_t); + + /* Validate NIDs: NID_undef is valid at parse time, else must be known. + * Note: md_nid=NID_undef only valid for PQC; enforced at sign/verify. + */ + if (ciph_nid != NID_undef && crypt_validate_nid(ciph_nid) < 0) + goto fail_decode; + if (oap_hdr->kdf_nid != NID_undef && + md_validate_nid(oap_hdr->kdf_nid) < 0) + goto fail_decode; + if (oap_hdr->md_nid != NID_undef && + md_validate_nid(oap_hdr->md_nid) < 0) + goto fail_decode; + + /* crt_len */ + crt_len = (size_t) ntoh16(*(uint16_t *)(hdr.data + offset)); + offset += sizeof(uint16_t); + + /* kex_len + flags */ + kex_len = ntoh16(*(uint16_t *)(hdr.data + offset)); + oap_hdr->kex.len = (size_t) (kex_len & OAP_KEX_LEN_MASK); + oap_hdr->kex_flags.fmt = (kex_len & OAP_KEX_FMT_BIT) ? 1 : 0; + oap_hdr->kex_flags.role = (kex_len & OAP_KEX_ROLE_BIT) ? 1 : 0; + offset += sizeof(uint16_t); + + /* data_len */ + data_len = (size_t) ntoh16(*(uint16_t *)(hdr.data + offset)); + offset += sizeof(uint16_t); + + /* Response includes req_hash when md_nid is set */ + hash_len = (req_md_nid != NID_undef) ? + (size_t) md_len(req_md_nid) : 0; + + /* Validate total length */ + if (hdr.len < (size_t) offset + crt_len + oap_hdr->kex.len + + data_len + hash_len) + goto fail_decode; + + /* Derive sig_len from remaining bytes */ + sig_len = hdr.len - offset - crt_len - oap_hdr->kex.len - + data_len - hash_len; + + /* Unsigned packets must not have trailing bytes */ + if (crt_len == 0 && sig_len != 0) + goto fail_decode; + + /* Parse variable fields */ + oap_hdr->crt.data = hdr.data + offset; + oap_hdr->crt.len = crt_len; + offset += crt_len; + + oap_hdr->kex.data = hdr.data + offset; + offset += oap_hdr->kex.len; + + oap_hdr->data.data = hdr.data + offset; + oap_hdr->data.len = data_len; + offset += data_len; + + oap_hdr->req_hash.data = hdr.data + offset; + oap_hdr->req_hash.len = hash_len; + offset += hash_len; + + oap_hdr->sig.data = hdr.data + offset; + oap_hdr->sig.len = sig_len; + + oap_hdr->hdr = hdr; + + return 0; + + fail_decode: + memset(oap_hdr, 0, sizeof(*oap_hdr)); + return -1; +} + +void oap_hdr_fini(struct oap_hdr * oap_hdr) +{ + assert(oap_hdr != NULL); + + freebuf(oap_hdr->hdr); + memset(oap_hdr, 0, sizeof(*oap_hdr)); +} + +int oap_hdr_copy_data(const struct oap_hdr * hdr, + buffer_t * out) +{ + assert(hdr != NULL); + assert(out != NULL); + + if (hdr->data.len == 0) { + clrbuf(*out); + return 0; + } + + out->data = malloc(hdr->data.len); + if (out->data == NULL) + return -ENOMEM; + + memcpy(out->data, hdr->data.data, hdr->data.len); + out->len = hdr->data.len; + + return 0; +} + +void oap_hdr_init(struct oap_hdr * hdr, + buffer_t id, + uint8_t * kex_buf, + buffer_t data, + uint16_t nid) +{ + assert(hdr != NULL); + assert(id.data != NULL && id.len == OAP_ID_SIZE); + + memset(hdr, 0, sizeof(*hdr)); + + hdr->id = id; + hdr->kex.data = kex_buf; + hdr->kex.len = 0; + hdr->data = data; + hdr->nid = nid; +} + +int oap_hdr_encode(struct oap_hdr * hdr, + void * pkp, + void * crt, + struct sec_config * kcfg, + buffer_t req_hash, + int req_md_nid) +{ + struct timespec now; + uint64_t stamp; + buffer_t out; + buffer_t der = BUF_INIT; + buffer_t sig = BUF_INIT; + buffer_t sign; + uint16_t len; + uint16_t ciph_nid; + uint16_t kdf_nid; + uint16_t md_nid; + uint16_t kex_len; + off_t offset; + + assert(hdr != NULL); + assert(hdr->id.data != NULL && hdr->id.len == OAP_ID_SIZE); + assert(kcfg != NULL); + + clock_gettime(CLOCK_REALTIME, &now); + stamp = hton64(TS_TO_UINT64(now)); + + if (crt != NULL && crypt_crt_der(crt, &der) < 0) + goto fail_der; + + ciph_nid = hton16(hdr->nid); + kdf_nid = hton16(kcfg->k.nid); + md_nid = hton16(kcfg->d.nid); + + /* Build kex_len with flags */ + kex_len = (uint16_t) hdr->kex.len; + if (hdr->kex.len > 0 && IS_KEM_ALGORITHM(kcfg->x.str)) { + if (IS_HYBRID_KEM(kcfg->x.str)) + kex_len |= OAP_KEX_FMT_BIT; + if (kcfg->x.mode == KEM_MODE_CLIENT_ENCAP) + kex_len |= OAP_KEX_ROLE_BIT; + } + kex_len = hton16(kex_len); + + /* Fixed header (36 bytes) + variable fields + req_hash (if auth) */ + out.len = OAP_HDR_MIN_SIZE + der.len + hdr->kex.len + hdr->data.len + + req_hash.len; + + out.data = malloc(out.len); + if (out.data == NULL) + goto fail_out; + + offset = 0; + + /* id (16 bytes) */ + memcpy(out.data + offset, hdr->id.data, hdr->id.len); + offset += hdr->id.len; + + /* timestamp (8 bytes) */ + memcpy(out.data + offset, &stamp, sizeof(stamp)); + offset += sizeof(stamp); + + /* cipher_nid (2 bytes) */ + memcpy(out.data + offset, &ciph_nid, sizeof(ciph_nid)); + offset += sizeof(ciph_nid); + + /* kdf_nid (2 bytes) */ + memcpy(out.data + offset, &kdf_nid, sizeof(kdf_nid)); + offset += sizeof(kdf_nid); + + /* md_nid (2 bytes) */ + memcpy(out.data + offset, &md_nid, sizeof(md_nid)); + offset += sizeof(md_nid); + + /* crt_len (2 bytes) */ + len = hton16((uint16_t) der.len); + memcpy(out.data + offset, &len, sizeof(len)); + offset += sizeof(len); + + /* kex_len + flags (2 bytes) */ + memcpy(out.data + offset, &kex_len, sizeof(kex_len)); + offset += sizeof(kex_len); + + /* data_len (2 bytes) */ + len = hton16((uint16_t) hdr->data.len); + memcpy(out.data + offset, &len, sizeof(len)); + offset += sizeof(len); + + /* Fixed header complete (36 bytes) */ + assert((size_t) offset == OAP_HDR_MIN_SIZE); + + /* certificate (variable) */ + if (der.len != 0) + memcpy(out.data + offset, der.data, der.len); + offset += der.len; + + /* kex data (variable) */ + if (hdr->kex.len != 0) + memcpy(out.data + offset, hdr->kex.data, hdr->kex.len); + offset += hdr->kex.len; + + /* data (variable) */ + if (hdr->data.len != 0) + memcpy(out.data + offset, hdr->data.data, hdr->data.len); + offset += hdr->data.len; + + /* req_hash (variable, only for authenticated responses) */ + if (req_hash.len != 0) + memcpy(out.data + offset, req_hash.data, req_hash.len); + offset += req_hash.len; + + assert((size_t) offset == out.len); + + /* Sign the entire header (fixed + variable, excluding signature) */ + sign.data = out.data; + sign.len = out.len; + + if (pkp != NULL && auth_sign(pkp, kcfg->d.nid, sign, &sig) < 0) + goto fail_sig; + + hdr->hdr = out; + + /* Append signature */ + if (sig.len > 0) { + hdr->hdr.len += sig.len; + hdr->hdr.data = realloc(out.data, hdr->hdr.len); + if (hdr->hdr.data == NULL) + goto fail_realloc; + + memcpy(hdr->hdr.data + offset, sig.data, sig.len); + clrbuf(out); + } + + if (oap_hdr_decode(hdr, hdr->hdr, req_md_nid) < 0) + goto fail_decode; + + freebuf(der); + freebuf(sig); + + return 0; + + fail_decode: + oap_hdr_fini(hdr); + fail_realloc: + freebuf(sig); + fail_sig: + freebuf(out); + fail_out: + freebuf(der); + fail_der: + return -1; +} + +#ifdef DEBUG_PROTO_OAP +static void debug_oap_hdr(const struct oap_hdr * hdr) +{ + assert(hdr); + + if (hdr->crt.len > 0) + log_proto(" crt: [%zu bytes]", hdr->crt.len); + else + log_proto(" crt: <none>"); + + if (hdr->kex.len > 0) + log_proto(" Key Exchange Data: [%zu bytes] [%s]", + hdr->kex.len, hdr->kex_flags.role ? + "Client encaps" : "Server encaps"); + else + log_proto(" Ephemeral Public Key: <none>"); + + if (hdr->cipher_str != NULL) + log_proto(" Cipher: %s", hdr->cipher_str); + else + log_proto(" Cipher: <none>"); + + if (hdr->kdf_str != NULL) + log_proto(" KDF: HKDF-%s", hdr->kdf_str); + else + log_proto(" KDF: <none>"); + + if (hdr->md_str != NULL) + log_proto(" Digest: %s", hdr->md_str); + else + log_proto(" Digest: <none>"); + + if (hdr->data.len > 0) + log_proto(" Data: [%zu bytes]", hdr->data.len); + else + log_proto(" Data: <none>"); + + if (hdr->req_hash.len > 0) + log_proto(" Req Hash: [%zu bytes]", hdr->req_hash.len); + else + log_proto(" Req Hash: <none>"); + + if (hdr->sig.len > 0) + log_proto(" Signature: [%zu bytes]", hdr->sig.len); + else + log_proto(" Signature: <none>"); +} +#endif + +void debug_oap_hdr_rcv(const struct oap_hdr * hdr) +{ +#ifdef DEBUG_PROTO_OAP + struct tm * tm; + char tmstr[RIB_TM_STRLEN]; + time_t stamp; + + assert(hdr); + + stamp = (time_t) hdr->timestamp / BILLION; + + tm = gmtime(&stamp); + strftime(tmstr, sizeof(tmstr), RIB_TM_FORMAT, tm); + + log_proto("OAP_HDR [" HASH_FMT64 " @ %s ] <--", + HASH_VAL64(hdr->id.data), tmstr); + + debug_oap_hdr(hdr); +#else + (void) hdr; +#endif +} + +void debug_oap_hdr_snd(const struct oap_hdr * hdr) +{ +#ifdef DEBUG_PROTO_OAP + struct tm * tm; + char tmstr[RIB_TM_STRLEN]; + time_t stamp; + + assert(hdr); + + stamp = (time_t) hdr->timestamp / BILLION; + + tm = gmtime(&stamp); + strftime(tmstr, sizeof(tmstr), RIB_TM_FORMAT, tm); + + log_proto("OAP_HDR [" HASH_FMT64 " @ %s ] -->", + HASH_VAL64(hdr->id.data), tmstr); + + debug_oap_hdr(hdr); +#else + (void) hdr; +#endif +} diff --git a/src/irmd/oap/hdr.h b/src/irmd/oap/hdr.h new file mode 100644 index 00000000..f603b169 --- /dev/null +++ b/src/irmd/oap/hdr.h @@ -0,0 +1,159 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OAP - Header definitions and functions + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#ifndef OUROBOROS_IRMD_OAP_HDR_H +#define OUROBOROS_IRMD_OAP_HDR_H + +#include <ouroboros/crypt.h> +#include <ouroboros/utils.h> + +#include <stdbool.h> +#include <stdint.h> + +#define OAP_ID_SIZE (16) +#define OAP_HDR_MIN_SIZE (OAP_ID_SIZE + sizeof(uint64_t) + 6 * sizeof(uint16_t)) + +#define OAP_KEX_FMT_BIT 0x8000 /* bit 15: 0=X.509 DER, 1=Raw */ +#define OAP_KEX_ROLE_BIT 0x4000 /* bit 14: 0=Server encaps, 1=Client encaps */ +#define OAP_KEX_LEN_MASK 0x3FFF /* bits 0-13: Length (0-16383 bytes) */ + +#define OAP_KEX_ROLE(hdr) (hdr->kex_flags.role) +#define OAP_KEX_FMT(hdr) (hdr->kex_flags.fmt) + +#define OAP_KEX_IS_X509_FMT(hdr) (((hdr)->kex_flags.fmt) == 0) +#define OAP_KEX_IS_RAW_FMT(hdr) (((hdr)->kex_flags.fmt) == 1) + +/* + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ---+ + * | | | + * + + | + * | | | + * + id (128 bits) + | + * | Unique flow allocation ID | | + * + + | + * | | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + * | | | + * + timestamp (64 bits) + | + * | UTC nanoseconds since epoch | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + * | cipher_nid (16 bits) | kdf_nid (16 bits) | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + * | md_nid (16 bits) | crt_len (16 bits) | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + * |F|R| kex_len (14 bits) | data_len (16 bits) | | Signed + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Region + * | | | + * + certificate (variable) + | + * | X.509 certificate, DER encoded | | + * + + | + * | | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + * | | | + * + kex_data (variable) + | + * | public key (DER/raw) or ciphertext (KEM) | | + * + + | + * | | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + * | | | + * + data (variable) + | + * | Piggybacked application data | | + * + + | + * | | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + * | | | + * + req_hash (variable, response only) + | + * | H(request) using req md_nid / sha384 | | + * | | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ---+ + * | | + * + signature (variable) + + * | DSA signature over signed region | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * + * cipher_nid: NID value for symmetric cipher (0 = none) + * kdf_nid: NID value for KDF function (0 = none) + * md_nid: NID value for signature hash (0 = PQC/no signature) + * + * kex_len field bit layout: + * F (bit 15): Format - 0 = X.509 DER, 1 = Raw/Hybrid + * R (bit 14): Role - 0 = Server encaps, 1 = Client encaps + * (R is ignored for non-KEM algorithms) + * Bits 0-13: Length (0-16383 bytes) + * + * Request: sig_len = total - 36 - crt_len - kex_len - data_len + * Response: sig_len = total - 36 - crt_len - kex_len - data_len - hash_len + * where hash_len = md_len(req_md_nid / sha384) + */ + +/* Parsed OAP header - buffers pointing to a single memory region */ +struct oap_hdr { + const char * cipher_str; + const char * kdf_str; + const char * md_str; + uint64_t timestamp; + uint16_t nid; + uint16_t kdf_nid; + uint16_t md_nid; + struct { + bool fmt; /* Format */ + bool role; /* Role */ + } kex_flags; + buffer_t id; + buffer_t crt; + buffer_t kex; + buffer_t data; + buffer_t req_hash; /* H(request) - response only */ + buffer_t sig; + buffer_t hdr; +}; + + +void oap_hdr_init(struct oap_hdr * hdr, + buffer_t id, + uint8_t * kex_buf, + buffer_t data, + uint16_t nid); + +void oap_hdr_fini(struct oap_hdr * oap_hdr); + +int oap_hdr_encode(struct oap_hdr * hdr, + void * pkp, + void * crt, + struct sec_config * kcfg, + buffer_t req_hash, + int req_md_nid); + +int oap_hdr_decode(struct oap_hdr * hdr, + buffer_t buf, + int req_md_nid); + +void debug_oap_hdr_rcv(const struct oap_hdr * hdr); + +void debug_oap_hdr_snd(const struct oap_hdr * hdr); + +int oap_hdr_copy_data(const struct oap_hdr * hdr, + buffer_t * out); + +#endif /* OUROBOROS_IRMD_OAP_HDR_H */ diff --git a/src/irmd/oap/internal.h b/src/irmd/oap/internal.h new file mode 100644 index 00000000..8363e3a2 --- /dev/null +++ b/src/irmd/oap/internal.h @@ -0,0 +1,133 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OAP internal definitions + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#ifndef OUROBOROS_IRMD_OAP_INTERNAL_H +#define OUROBOROS_IRMD_OAP_INTERNAL_H + +#include <ouroboros/crypt.h> +#include <ouroboros/list.h> +#include <ouroboros/name.h> +#include <ouroboros/pthread.h> +#include <ouroboros/utils.h> + +#include "hdr.h" + +#include <stdbool.h> +#include <stdint.h> + +/* + * Authentication functions (auth.c) + */ +int oap_check_hdr(const struct oap_hdr * hdr); + +int oap_auth_peer(char * name, + const struct oap_hdr * local_hdr, + const struct oap_hdr * peer_hdr); + +/* + * Key exchange functions (kex.c) + */ +int oap_negotiate_cipher(const struct oap_hdr * peer_hdr, + struct sec_config * kcfg); + +/* + * Credential loading (oap.c) - shared between client and server + */ +#ifndef OAP_TEST_MODE +int load_credentials(const char * name, + const struct name_sec_paths * paths, + void ** pkp, + void ** crt); + +int load_kex_config(const char * name, + const char * path, + struct sec_config * cfg); +#endif + +/* + * Server functions (srv.c) + */ +#ifndef OAP_TEST_MODE +int load_srv_credentials(const struct name_info * info, + void ** pkp, + void ** crt); + +int load_srv_kex_config(const struct name_info * info, + struct sec_config * cfg); + +int load_server_kem_keypair(const char * name, + struct sec_config * cfg, + void ** pkp); +#else +extern int load_srv_credentials(const struct name_info * info, + void ** pkp, + void ** crt); +extern int load_srv_kex_config(const struct name_info * info, + struct sec_config * cfg); +extern int load_server_kem_keypair(const char * name, + struct sec_config * cfg, + void ** pkp); +#endif + +int do_server_kex(const struct name_info * info, + struct oap_hdr * peer_hdr, + struct sec_config * kcfg, + buffer_t * kex, + struct crypt_sk * sk); + +/* + * Client functions (cli.c) + */ +#ifndef OAP_TEST_MODE +int load_cli_credentials(const struct name_info * info, + void ** pkp, + void ** crt); + +int load_cli_kex_config(const struct name_info * info, + struct sec_config * cfg); + +int load_server_kem_pk(const char * name, + struct sec_config * cfg, + buffer_t * pk); +#else +extern int load_cli_credentials(const struct name_info * info, + void ** pkp, + void ** crt); +extern int load_cli_kex_config(const struct name_info * info, + struct sec_config * cfg); +extern int load_server_kem_pk(const char * name, + struct sec_config * cfg, + buffer_t * pk); +#endif + +int oap_client_kex_prepare(struct sec_config * kcfg, + buffer_t server_pk, + buffer_t * kex, + uint8_t * key, + void ** ephemeral_pkp); + +int oap_client_kex_complete(const struct oap_hdr * peer_hdr, + struct sec_config * kcfg, + void * pkp, + uint8_t * key); + +#endif /* OUROBOROS_IRMD_OAP_INTERNAL_H */ diff --git a/src/irmd/oap/io.c b/src/irmd/oap/io.c new file mode 100644 index 00000000..e4189d4d --- /dev/null +++ b/src/irmd/oap/io.c @@ -0,0 +1,132 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OAP - File I/O for credentials and configuration + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#if defined(__linux__) || defined(__CYGWIN__) + #define _DEFAULT_SOURCE +#else + #define _POSIX_C_SOURCE 200809L +#endif + +#define OUROBOROS_PREFIX "irmd/oap" + +#include <ouroboros/crypt.h> +#include <ouroboros/errno.h> +#include <ouroboros/logs.h> + +#include "config.h" + +#include "io.h" + +#include <assert.h> +#include <string.h> +#include <sys/stat.h> + +/* + * Shared credential and configuration loading helpers + */ + +#ifndef OAP_TEST_MODE + +static bool file_exists(const char * path) +{ + struct stat s; + + if (stat(path, &s) < 0 && errno == ENOENT) { + log_dbg("File %s does not exist.", path); + return false; + } + + return true; +} + +int load_credentials(const char * name, + const struct name_sec_paths * paths, + void ** pkp, + void ** crt) +{ + assert(paths != NULL); + assert(pkp != NULL); + assert(crt != NULL); + + *pkp = NULL; + *crt = NULL; + + if (!file_exists(paths->crt) || !file_exists(paths->key)) { + log_info("No authentication certificates for %s.", name); + return 0; + } + + if (crypt_load_crt_file(paths->crt, crt) < 0) { + log_err("Failed to load %s for %s.", paths->crt, name); + goto fail_crt; + } + + if (crypt_load_privkey_file(paths->key, pkp) < 0) { + log_err("Failed to load %s for %s.", paths->key, name); + goto fail_key; + } + + log_info("Loaded authentication certificates for %s.", name); + + return 0; + + fail_key: + crypt_free_crt(*crt); + *crt = NULL; + fail_crt: + return -EAUTH; +} + +int load_kex_config(const char * name, + const char * path, + struct sec_config * cfg) +{ + assert(name != NULL); + assert(cfg != NULL); + + memset(cfg, 0, sizeof(*cfg)); + + /* Load encryption config */ + if (!file_exists(path)) + log_dbg("No encryption %s for %s.", path, name); + + if (load_sec_config_file(cfg, path) < 0) { + log_warn("Failed to load %s for %s.", path, name); + return -1; + } + + if (!IS_KEX_ALGO_SET(cfg)) { + log_info("Key exchange not configured for %s.", name); + return 0; + } + + if (cfg->c.nid == NID_undef || crypt_nid_to_str(cfg->c.nid) == NULL) { + log_err("Invalid cipher NID %d for %s.", cfg->c.nid, name); + return -ECRYPT; + } + + log_info("Encryption enabled for %s.", name); + + return 0; +} + +#endif /* OAP_TEST_MODE */ diff --git a/src/irmd/oap/io.h b/src/irmd/oap/io.h new file mode 100644 index 00000000..a31ddf85 --- /dev/null +++ b/src/irmd/oap/io.h @@ -0,0 +1,40 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OAP - Credential and configuration file I/O + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#ifndef OUROBOROS_IRMD_OAP_IO_H +#define OUROBOROS_IRMD_OAP_IO_H + +#include <ouroboros/crypt.h> +#include <ouroboros/name.h> + +#ifndef OAP_TEST_MODE +int load_credentials(const char * name, + const struct name_sec_paths * paths, + void ** pkp, + void ** crt); + +int load_kex_config(const char * name, + const char * path, + struct sec_config * cfg); +#endif + +#endif /* OUROBOROS_IRMD_OAP_IO_H */ diff --git a/src/irmd/oap/srv.c b/src/irmd/oap/srv.c new file mode 100644 index 00000000..c5a4453f --- /dev/null +++ b/src/irmd/oap/srv.c @@ -0,0 +1,462 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OAP - Server-side processing + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#if defined(__linux__) || defined(__CYGWIN__) + #define _DEFAULT_SOURCE +#else + #define _POSIX_C_SOURCE 200809L +#endif + +#define OUROBOROS_PREFIX "irmd/oap" + +#include <ouroboros/crypt.h> +#include <ouroboros/errno.h> +#include <ouroboros/logs.h> + +#include "config.h" + +#include "auth.h" +#include "hdr.h" +#include "io.h" +#include "oap.h" + +#include <assert.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifdef OAP_TEST_MODE +extern int load_srv_credentials(const struct name_info * info, + void ** pkp, + void ** crt); +extern int load_srv_kex_config(const struct name_info * info, + struct sec_config * cfg); +extern int load_server_kem_keypair(const char * name, + bool raw_fmt, + void ** pkp); +#else + +int load_srv_credentials(const struct name_info * info, + void ** pkp, + void ** crt) +{ + assert(info != NULL); + assert(pkp != NULL); + assert(crt != NULL); + + return load_credentials(info->name, &info->s, pkp, crt); +} + +int load_srv_kex_config(const struct name_info * info, + struct sec_config * cfg) +{ + assert(info != NULL); + assert(cfg != NULL); + + return load_kex_config(info->name, info->s.enc, cfg); +} + +int load_server_kem_keypair(const char * name, + bool raw_fmt, + void ** pkp) +{ + char path[PATH_MAX]; + const char * ext; + + assert(name != NULL); + assert(pkp != NULL); + + ext = raw_fmt ? "raw" : "pem"; + + snprintf(path, sizeof(path), + OUROBOROS_SRV_CRT_DIR "/%s/kex.key.%s", name, ext); + + if (raw_fmt) { + if (crypt_load_privkey_raw_file(path, pkp) < 0) { + log_err("Failed to load %s keypair from %s.", + ext, path); + return -ECRYPT; + } + } else { + if (crypt_load_privkey_file(path, pkp) < 0) { + log_err("Failed to load %s keypair from %s.", + ext, path); + return -ECRYPT; + } + } + + log_dbg("Loaded server KEM keypair from %s.", path); + return 0; +} + +#endif /* OAP_TEST_MODE */ + +static int get_algo_from_peer_key(const struct oap_hdr * peer_hdr, + char * algo_buf) +{ + uint8_t * id = peer_hdr->id.data; + int ret; + + if (OAP_KEX_IS_RAW_FMT(peer_hdr)) { + ret = kex_get_algo_from_pk_raw(peer_hdr->kex, algo_buf); + if (ret < 0) { + log_err_id(id, "Failed to get algo from raw key."); + return -ECRYPT; + } + } else { + ret = kex_get_algo_from_pk_der(peer_hdr->kex, algo_buf); + if (ret < 0) { + log_err_id(id, "Failed to get algo from DER key."); + return -ECRYPT; + } + } + + return 0; +} + +static int negotiate_kex(const struct oap_hdr * peer_hdr, + struct sec_config * kcfg) +{ + uint8_t * id = peer_hdr->id.data; + + if (kcfg->c.nid == NID_undef) { + if (peer_hdr->cipher_str != NULL) { + SET_KEX_CIPHER(kcfg, peer_hdr->cipher_str); + if (kcfg->c.nid == NID_undef) { + log_err_id(id, "Unsupported cipher '%s'.", + peer_hdr->cipher_str); + return -ENOTSUP; + } + log_dbg_id(id, "Peer requested cipher %s.", + peer_hdr->cipher_str); + } else { + log_err_id(id, "Encryption requested, no cipher."); + return -ECRYPT; + } + } else { + log_dbg_id(id, "Using local cipher %s.", kcfg->c.str); + } + + /* Negotiate KDF - server overrides client if configured */ + if (kcfg->k.nid != NID_undef) { + log_dbg_id(id, "Using local KDF %s.", + md_nid_to_str(kcfg->k.nid)); + } else if (peer_hdr->kdf_nid != NID_undef) { + if (md_validate_nid(peer_hdr->kdf_nid) == 0) { + kcfg->k.nid = peer_hdr->kdf_nid; + log_dbg_id(id, "Using peer KDF %s.", + md_nid_to_str(peer_hdr->kdf_nid)); + } else { + log_err_id(id, "Unsupported KDF NID %d.", + peer_hdr->kdf_nid); + return -ENOTSUP; + } + } + + if (IS_KEX_ALGO_SET(kcfg)) + log_info_id(id, "Negotiated %s + %s.", + kcfg->x.str, kcfg->c.str); + else + log_info_id(id, "No key exchange."); + + return 0; +} + +static int do_server_kem_decap(const struct name_info * info, + const struct oap_hdr * peer_hdr, + struct sec_config * kcfg, + struct crypt_sk * sk) +{ + buffer_t ct; + void * server_pkp = NULL; + int ret; + uint8_t * id = peer_hdr->id.data; + + ret = load_server_kem_keypair(info->name, + peer_hdr->kex_flags.fmt, + &server_pkp); + if (ret < 0) + return ret; + + ct.data = peer_hdr->kex.data; + ct.len = peer_hdr->kex.len; + + ret = kex_kem_decap(server_pkp, ct, kcfg->k.nid, sk->key); + + crypt_free_key(server_pkp); + + if (ret < 0) { + log_err_id(id, "Failed to decapsulate KEM."); + return -ECRYPT; + } + + log_dbg_id(id, "Client encaps: decapsulated CT."); + + return 0; +} + +static int do_server_kem_encap(const struct oap_hdr * peer_hdr, + struct sec_config * kcfg, + buffer_t * kex, + struct crypt_sk * sk) +{ + buffer_t client_pk; + ssize_t ct_len; + uint8_t * id = peer_hdr->id.data; + + client_pk.data = peer_hdr->kex.data; + client_pk.len = peer_hdr->kex.len; + + if (IS_HYBRID_KEM(kcfg->x.str)) + ct_len = kex_kem_encap_raw(client_pk, kex->data, + kcfg->k.nid, sk->key); + else + ct_len = kex_kem_encap(client_pk, kex->data, + kcfg->k.nid, sk->key); + + if (ct_len < 0) { + log_err_id(id, "Failed to encapsulate KEM."); + return -ECRYPT; + } + + kex->len = (size_t) ct_len; + + log_dbg_id(id, "Server encaps: generated CT, len=%zd.", ct_len); + + return 0; +} + +static int do_server_kex_kem(const struct name_info * info, + struct oap_hdr * peer_hdr, + struct sec_config * kcfg, + buffer_t * kex, + struct crypt_sk * sk) +{ + int ret; + + kcfg->x.mode = peer_hdr->kex_flags.role; + + if (kcfg->x.mode == KEM_MODE_CLIENT_ENCAP) { + ret = do_server_kem_decap(info, peer_hdr, kcfg, sk); + kex->len = 0; + } else { + ret = do_server_kem_encap(peer_hdr, kcfg, kex, sk); + } + + return ret; +} + +static int do_server_kex_dhe(const struct oap_hdr * peer_hdr, + struct sec_config * kcfg, + buffer_t * kex, + struct crypt_sk * sk) +{ + ssize_t key_len; + void * epkp; + int ret; + uint8_t * id = peer_hdr->id.data; + + key_len = kex_pkp_create(kcfg, &epkp, kex->data); + if (key_len < 0) { + log_err_id(id, "Failed to generate key pair."); + return -ECRYPT; + } + + kex->len = (size_t) key_len; + + log_dbg_id(id, "Generated %s ephemeral keys.", kcfg->x.str); + + ret = kex_dhe_derive(kcfg, epkp, peer_hdr->kex, sk->key); + if (ret < 0) { + log_err_id(id, "Failed to derive secret."); + kex_pkp_destroy(epkp); + return -ECRYPT; + } + + kex_pkp_destroy(epkp); + + return 0; +} + +int do_server_kex(const struct name_info * info, + struct oap_hdr * peer_hdr, + struct sec_config * kcfg, + buffer_t * kex, + struct crypt_sk * sk) +{ + char algo_buf[KEX_ALGO_BUFSZ]; + uint8_t * id; + + id = peer_hdr->id.data; + + /* No KEX data from client */ + if (peer_hdr->kex.len == 0) { + if (IS_KEX_ALGO_SET(kcfg)) { + log_warn_id(id, "KEX requested without info."); + return -ECRYPT; + } + return 0; + } + + if (negotiate_kex(peer_hdr, kcfg) < 0) + return -ECRYPT; + + if (OAP_KEX_ROLE(peer_hdr) != KEM_MODE_CLIENT_ENCAP) { + /* Server encapsulation or DHE: extract algo from DER PK */ + if (get_algo_from_peer_key(peer_hdr, algo_buf) < 0) + return -ECRYPT; + + SET_KEX_ALGO(kcfg, algo_buf); + } + + /* Dispatch based on algorithm type */ + if (IS_KEM_ALGORITHM(kcfg->x.str)) + return do_server_kex_kem(info, peer_hdr, kcfg, kex, sk); + else + return do_server_kex_dhe(peer_hdr, kcfg, kex, sk); +} + +int oap_srv_process(const struct name_info * info, + buffer_t req_buf, + buffer_t * rsp_buf, + buffer_t * data, + struct crypt_sk * sk) +{ + struct oap_hdr peer_hdr; + struct oap_hdr local_hdr; + struct sec_config kcfg; + uint8_t kex_buf[MSGBUFSZ]; + uint8_t hash_buf[MAX_HASH_SIZE]; + buffer_t req_hash = BUF_INIT; + ssize_t hash_ret; + char cli_name[NAME_SIZE + 1]; /* TODO */ + uint8_t * id; + void * pkp = NULL; + void * crt = NULL; + int req_md_nid; + + assert(info != NULL); + assert(rsp_buf != NULL); + assert(data != NULL); + assert(sk != NULL); + + sk->nid = NID_undef; + + memset(&peer_hdr, 0, sizeof(peer_hdr)); + memset(&local_hdr, 0, sizeof(local_hdr)); + clrbuf(*rsp_buf); + + log_dbg("Processing OAP request for %s.", info->name); + + /* Load server credentials */ + if (load_srv_credentials(info, &pkp, &crt) < 0) { + log_err("Failed to load security keys for %s.", info->name); + goto fail_cred; + } + + /* Load KEX config */ + if (load_srv_kex_config(info, &kcfg) < 0) { + log_err("Failed to load KEX config for %s.", info->name); + goto fail_kex; + } + + sk->nid = kcfg.c.nid; + + /* Decode incoming header (NID_undef = request, no hash) */ + if (oap_hdr_decode(&peer_hdr, req_buf, NID_undef) < 0) { + log_err("Failed to decode OAP header."); + goto fail_auth; + } + + debug_oap_hdr_rcv(&peer_hdr); + + id = peer_hdr.id.data; /* Logging */ + + /* Check for replay */ + if (oap_check_hdr(&peer_hdr) < 0) { + log_err_id(id, "OAP header failed replay check."); + goto fail_auth; + } + + /* Authenticate client before processing KEX data */ + oap_hdr_init(&local_hdr, peer_hdr.id, kex_buf, *data, NID_undef); + + if (oap_auth_peer(cli_name, &local_hdr, &peer_hdr) < 0) { + log_err_id(id, "Failed to authenticate client."); + goto fail_auth; + } + + if (do_server_kex(info, &peer_hdr, &kcfg, &local_hdr.kex, sk) < 0) + goto fail_kex; + + /* Build response header with hash of client request */ + local_hdr.nid = sk->nid; + + /* Use client's md_nid, defaulting to SHA-384 for PQC */ + req_md_nid = peer_hdr.md_nid != NID_undef ? peer_hdr.md_nid : NID_sha384; + + /* Compute request hash using client's md_nid */ + hash_ret = md_digest(req_md_nid, req_buf, hash_buf); + if (hash_ret < 0) { + log_err_id(id, "Failed to hash request."); + goto fail_auth; + } + req_hash.data = hash_buf; + req_hash.len = (size_t) hash_ret; + + if (oap_hdr_encode(&local_hdr, pkp, crt, &kcfg, + req_hash, req_md_nid) < 0) { + log_err_id(id, "Failed to create OAP response header."); + goto fail_auth; + } + + debug_oap_hdr_snd(&local_hdr); + + if (oap_hdr_copy_data(&peer_hdr, data) < 0) { + log_err_id(id, "Failed to copy client data."); + goto fail_data; + } + + /* Transfer ownership of response buffer */ + *rsp_buf = local_hdr.hdr; + + log_info_id(id, "OAP request processed for %s.", info->name); + + crypt_free_crt(crt); + crypt_free_key(pkp); + + return 0; + + fail_data: + oap_hdr_fini(&local_hdr); + fail_auth: + crypt_free_crt(crt); + crypt_free_key(pkp); + fail_cred: + return -EAUTH; + + fail_kex: + crypt_free_crt(crt); + crypt_free_key(pkp); + return -ECRYPT; +} diff --git a/src/irmd/oap/tests/CMakeLists.txt b/src/irmd/oap/tests/CMakeLists.txt new file mode 100644 index 00000000..2bf23821 --- /dev/null +++ b/src/irmd/oap/tests/CMakeLists.txt @@ -0,0 +1,64 @@ +get_filename_component(PARENT_PATH ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) +get_filename_component(PARENT_DIR ${PARENT_PATH} NAME) + +get_filename_component(OAP_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" DIRECTORY) +get_filename_component(OAP_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}" DIRECTORY) +get_filename_component(IRMD_SOURCE_DIR "${OAP_SOURCE_DIR}" DIRECTORY) +get_filename_component(IRMD_BINARY_DIR "${OAP_BINARY_DIR}" DIRECTORY) + +compute_test_prefix() + +create_test_sourcelist(${PARENT_DIR}_tests test_suite.c + # Add new tests here + oap_test.c +) + +create_test_sourcelist(${PARENT_DIR}_pqc_tests test_suite_pqc.c + # PQC-specific tests + oap_test_pqc.c +) + +# OAP test needs io.c compiled with OAP_TEST_MODE +set(OAP_TEST_SOURCES + ${OAP_SOURCE_DIR}/io.c + ${OAP_SOURCE_DIR}/hdr.c + ${OAP_SOURCE_DIR}/auth.c + ${OAP_SOURCE_DIR}/srv.c + ${OAP_SOURCE_DIR}/cli.c + ${CMAKE_CURRENT_SOURCE_DIR}/common.c +) + +# Regular test executable (ECDSA) +add_executable(${PARENT_DIR}_test ${${PARENT_DIR}_tests} ${OAP_TEST_SOURCES}) +set_source_files_properties(${OAP_TEST_SOURCES} + PROPERTIES COMPILE_DEFINITIONS "OAP_TEST_MODE" +) + +disable_test_logging_for_target(${PARENT_DIR}_test) +target_link_libraries(${PARENT_DIR}_test ouroboros-irm) +target_include_directories(${PARENT_DIR}_test PRIVATE + ${IRMD_SOURCE_DIR} + ${IRMD_BINARY_DIR} +) + +# PQC test executable (ML-DSA) +add_executable(${PARENT_DIR}_pqc_test ${${PARENT_DIR}_pqc_tests} ${OAP_TEST_SOURCES}) +set_source_files_properties(${OAP_TEST_SOURCES} + TARGET_DIRECTORY ${PARENT_DIR}_pqc_test + PROPERTIES COMPILE_DEFINITIONS "OAP_TEST_MODE" +) + +disable_test_logging_for_target(${PARENT_DIR}_pqc_test) +target_link_libraries(${PARENT_DIR}_pqc_test ouroboros-irm) +target_include_directories(${PARENT_DIR}_pqc_test PRIVATE + ${IRMD_SOURCE_DIR} + ${IRMD_BINARY_DIR} +) + +add_dependencies(build_tests ${PARENT_DIR}_test ${PARENT_DIR}_pqc_test) + +# Regular tests +ouroboros_register_tests(TARGET ${PARENT_DIR}_test TESTS ${${PARENT_DIR}_tests}) + +# PQC tests +ouroboros_register_tests(TARGET ${PARENT_DIR}_pqc_test TESTS ${${PARENT_DIR}_pqc_tests}) diff --git a/src/irmd/oap/tests/common.c b/src/irmd/oap/tests/common.c new file mode 100644 index 00000000..0a1af100 --- /dev/null +++ b/src/irmd/oap/tests/common.c @@ -0,0 +1,457 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Common test helper functions for OAP tests + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#include "common.h" + +#include <ouroboros/crypt.h> + +#include "oap.h" + +#include <string.h> +#include <stdio.h> + +int load_srv_kex_config(const struct name_info * info, + struct sec_config * cfg) +{ + (void) info; + + memset(cfg, 0, sizeof(*cfg)); + + if (test_cfg.srv.kex == NID_undef) + return 0; + + SET_KEX_ALGO_NID(cfg, test_cfg.srv.kex); + SET_KEX_CIPHER_NID(cfg, test_cfg.srv.cipher); + SET_KEX_KDF_NID(cfg, test_cfg.srv.kdf); + SET_KEX_DIGEST_NID(cfg, test_cfg.srv.md); + SET_KEX_KEM_MODE(cfg, test_cfg.srv.kem_mode); + + return 0; +} + +int load_cli_kex_config(const struct name_info * info, + struct sec_config * cfg) +{ + (void) info; + + memset(cfg, 0, sizeof(*cfg)); + + if (test_cfg.cli.kex == NID_undef) + return 0; + + SET_KEX_ALGO_NID(cfg, test_cfg.cli.kex); + SET_KEX_CIPHER_NID(cfg, test_cfg.cli.cipher); + SET_KEX_KDF_NID(cfg, test_cfg.cli.kdf); + SET_KEX_DIGEST_NID(cfg, test_cfg.cli.md); + SET_KEX_KEM_MODE(cfg, test_cfg.cli.kem_mode); + + return 0; +} + +int load_srv_credentials(const struct name_info * info, + void ** pkp, + void ** crt) +{ + (void) info; + + *pkp = NULL; + *crt = NULL; + + if (!test_cfg.srv.auth) + return 0; + + return mock_load_credentials(pkp, crt); +} + +int load_cli_credentials(const struct name_info * info, + void ** pkp, + void ** crt) +{ + (void) info; + + *pkp = NULL; + *crt = NULL; + + if (!test_cfg.cli.auth) + return 0; + + return mock_load_credentials(pkp, crt); +} + +int oap_test_setup(struct oap_test_ctx * ctx, + const char * root_ca_str, + const char * im_ca_str) +{ + memset(ctx, 0, sizeof(*ctx)); + + strcpy(ctx->srv.info.name, "test-1.unittest.o7s"); + strcpy(ctx->cli.info.name, "test-1.unittest.o7s"); + + if (oap_auth_init() < 0) { + printf("Failed to init OAP.\n"); + goto fail_init; + } + + if (crypt_load_crt_str(root_ca_str, &ctx->root_ca) < 0) { + printf("Failed to load root CA cert.\n"); + goto fail_root_ca; + } + + if (crypt_load_crt_str(im_ca_str, &ctx->im_ca) < 0) { + printf("Failed to load intermediate CA cert.\n"); + goto fail_im_ca; + } + + if (oap_auth_add_ca_crt(ctx->root_ca) < 0) { + printf("Failed to add root CA cert to store.\n"); + goto fail_add_ca; + } + + if (oap_auth_add_ca_crt(ctx->im_ca) < 0) { + printf("Failed to add intermediate CA cert to store.\n"); + goto fail_add_ca; + } + + return 0; + + fail_add_ca: + crypt_free_crt(ctx->im_ca); + fail_im_ca: + crypt_free_crt(ctx->root_ca); + fail_root_ca: + oap_auth_fini(); + fail_init: + memset(ctx, 0, sizeof(*ctx)); + return -1; +} + +void oap_test_teardown(struct oap_test_ctx * ctx) +{ + struct crypt_sk res; + buffer_t dummy = BUF_INIT; + + if (ctx->cli.state != NULL) { + res.key = ctx->cli.key; + oap_cli_complete(ctx->cli.state, &ctx->cli.info, dummy, + &ctx->data, &res); + ctx->cli.state = NULL; + } + + freebuf(ctx->data); + freebuf(ctx->resp_hdr); + freebuf(ctx->req_hdr); + + crypt_free_crt(ctx->im_ca); + crypt_free_crt(ctx->root_ca); + + oap_auth_fini(); + memset(ctx, 0, sizeof(*ctx)); +} + +int oap_cli_prepare_ctx(struct oap_test_ctx * ctx) +{ + return oap_cli_prepare(&ctx->cli.state, &ctx->cli.info, &ctx->req_hdr, + ctx->data); +} + +int oap_srv_process_ctx(struct oap_test_ctx * ctx) +{ + struct crypt_sk res = { .nid = NID_undef, .key = ctx->srv.key }; + int ret; + + ret = oap_srv_process(&ctx->srv.info, ctx->req_hdr, + &ctx->resp_hdr, &ctx->data, &res); + if (ret == 0) + ctx->srv.nid = res.nid; + + return ret; +} + +int oap_cli_complete_ctx(struct oap_test_ctx * ctx) +{ + struct crypt_sk res = { .nid = NID_undef, .key = ctx->cli.key }; + int ret; + + ret = oap_cli_complete(ctx->cli.state, &ctx->cli.info, ctx->resp_hdr, + &ctx->data, &res); + ctx->cli.state = NULL; + + if (ret == 0) + ctx->cli.nid = res.nid; + + return ret; +} + +int roundtrip_auth_only(const char * root_ca, + const char * im_ca_str) +{ + struct oap_test_ctx ctx; + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca, im_ca_str) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Server process failed.\n"); + goto fail_cleanup; + } + + if (oap_cli_complete_ctx(&ctx) < 0) { + printf("Client complete failed.\n"); + goto fail_cleanup; + } + + if (ctx.cli.nid != NID_undef || ctx.srv.nid != NID_undef) { + printf("Cipher should not be set for auth-only.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int roundtrip_kex_only(void) +{ + struct name_info cli_info; + struct name_info srv_info; + struct crypt_sk res; + uint8_t cli_key[SYMMKEYSZ]; + uint8_t srv_key[SYMMKEYSZ]; + int cli_nid; + int srv_nid; + buffer_t req_hdr = BUF_INIT; + buffer_t resp_hdr = BUF_INIT; + buffer_t data = BUF_INIT; + void * cli_state = NULL; + + TEST_START(); + + memset(&cli_info, 0, sizeof(cli_info)); + memset(&srv_info, 0, sizeof(srv_info)); + + strcpy(cli_info.name, "test-1.unittest.o7s"); + strcpy(srv_info.name, "test-1.unittest.o7s"); + + if (oap_auth_init() < 0) { + printf("Failed to init OAP.\n"); + goto fail; + } + + if (oap_cli_prepare(&cli_state, &cli_info, &req_hdr, + data) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + res.key = srv_key; + + if (oap_srv_process(&srv_info, req_hdr, &resp_hdr, &data, &res) < 0) { + printf("Server process failed.\n"); + goto fail_cleanup; + } + + srv_nid = res.nid; + + res.key = cli_key; + + if (oap_cli_complete(cli_state, &cli_info, resp_hdr, &data, &res) < 0) { + printf("Client complete failed.\n"); + cli_state = NULL; + goto fail_cleanup; + } + + cli_nid = res.nid; + cli_state = NULL; + + if (memcmp(cli_key, srv_key, SYMMKEYSZ) != 0) { + printf("Client and server keys do not match!\n"); + goto fail_cleanup; + } + + if (cli_nid == NID_undef || srv_nid == NID_undef) { + printf("Cipher should be set for kex-only.\n"); + goto fail_cleanup; + } + + freebuf(resp_hdr); + freebuf(req_hdr); + oap_auth_fini(); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + if (cli_state != NULL) { + res.key = cli_key; + oap_cli_complete(cli_state, &cli_info, resp_hdr, &data, &res); + } + freebuf(resp_hdr); + freebuf(req_hdr); + oap_auth_fini(); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int corrupted_request(const char * root_ca, + const char * im_ca_str) +{ + struct oap_test_ctx ctx; + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca, im_ca_str) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + /* Corrupt the request */ + if (ctx.req_hdr.len > 100) { + ctx.req_hdr.data[50] ^= 0xFF; + ctx.req_hdr.data[51] ^= 0xAA; + ctx.req_hdr.data[52] ^= 0x55; + } + + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject corrupted request.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int corrupted_response(const char * root_ca, + const char * im_ca_str) +{ + struct oap_test_ctx ctx; + struct crypt_sk res; + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca, im_ca_str) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Server process failed.\n"); + goto fail_cleanup; + } + + /* Corrupt the response */ + if (ctx.resp_hdr.len > 100) { + ctx.resp_hdr.data[50] ^= 0xFF; + ctx.resp_hdr.data[51] ^= 0xAA; + ctx.resp_hdr.data[52] ^= 0x55; + } + + res.key = ctx.cli.key; + + if (oap_cli_complete(ctx.cli.state, &ctx.cli.info, ctx.resp_hdr, + &ctx.data, &res) == 0) { + printf("Client should reject corrupted response.\n"); + ctx.cli.state = NULL; + goto fail_cleanup; + } + + ctx.cli.state = NULL; + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int truncated_request(const char * root_ca, + const char * im_ca_str) +{ + struct oap_test_ctx ctx; + size_t orig_len; + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca, im_ca_str) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + /* Truncate the request buffer */ + orig_len = ctx.req_hdr.len; + ctx.req_hdr.len = orig_len / 2; + + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject truncated request.\n"); + ctx.req_hdr.len = orig_len; + goto fail_cleanup; + } + + ctx.req_hdr.len = orig_len; + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} diff --git a/src/irmd/oap/tests/common.h b/src/irmd/oap/tests/common.h new file mode 100644 index 00000000..d4b6733a --- /dev/null +++ b/src/irmd/oap/tests/common.h @@ -0,0 +1,100 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Common test helper functions for OAP tests + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#ifndef IRMD_TESTS_COMMON_H +#define IRMD_TESTS_COMMON_H + +#include <ouroboros/utils.h> +#include <ouroboros/flow.h> +#include <ouroboros/name.h> +#include <test/test.h> + +#include <stdbool.h> + +/* Per-side security configuration for tests */ +struct test_sec_cfg { + int kex; /* KEX algorithm NID */ + int cipher; /* Cipher NID for encryption */ + int kdf; /* KDF NID for key derivation */ + int md; /* Digest NID for signatures */ + int kem_mode; /* KEM encapsulation mode (0 for ECDH) */ + bool auth; /* Use authentication (certificates) */ +}; + +/* Test configuration - set by each test before running roundtrip */ +extern struct test_cfg { + struct test_sec_cfg srv; + struct test_sec_cfg cli; +} test_cfg; + +/* Each test file defines this with its own certificates */ +extern int mock_load_credentials(void ** pkp, + void ** crt); + +/* Per-side test context */ +struct oap_test_side { + struct name_info info; + struct flow_info flow; + uint8_t key[SYMMKEYSZ]; + int nid; + void * state; +}; + +/* Test context - holds all common state for OAP tests */ +struct oap_test_ctx { + struct oap_test_side srv; + struct oap_test_side cli; + + buffer_t req_hdr; + buffer_t resp_hdr; + buffer_t data; + void * root_ca; + void * im_ca; +}; + +int oap_test_setup(struct oap_test_ctx * ctx, + const char * root_ca_str, + const char * im_ca_str); + +void oap_test_teardown(struct oap_test_ctx * ctx); + +int oap_cli_prepare_ctx(struct oap_test_ctx * ctx); + +int oap_srv_process_ctx(struct oap_test_ctx * ctx); + +int oap_cli_complete_ctx(struct oap_test_ctx * ctx); + +int roundtrip_auth_only(const char * root_ca, + const char * im_ca_str); + +int roundtrip_kex_only(void); + +int corrupted_request(const char * root_ca, + const char * im_ca_str); + +int corrupted_response(const char * root_ca, + const char * im_ca_str); + +int truncated_request(const char * root_ca, + const char * im_ca_str); + +#endif /* IRMD_TESTS_COMMON_H */ diff --git a/src/irmd/oap/tests/oap_test.c b/src/irmd/oap/tests/oap_test.c new file mode 100644 index 00000000..70f0a248 --- /dev/null +++ b/src/irmd/oap/tests/oap_test.c @@ -0,0 +1,951 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * Unit tests of Ouroboros Allocation Protocol (OAP) + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#if defined(__linux__) || defined(__CYGWIN__) + #ifndef _DEFAULT_SOURCE + #define _DEFAULT_SOURCE + #endif +#else +#define _POSIX_C_SOURCE 200809L +#endif + +#include "config.h" + +#include <ouroboros/crypt.h> +#include <ouroboros/endian.h> +#include <ouroboros/flow.h> +#include <ouroboros/name.h> +#include <ouroboros/random.h> +#include <ouroboros/time.h> + +#include <test/test.h> +#include <test/certs.h> + +#include "oap.h" +#include "common.h" + +#include <stdbool.h> +#include <string.h> + +#ifdef HAVE_OPENSSL +#include <openssl/evp.h> +#endif + +#define AUTH true +#define NO_AUTH false + +extern const uint16_t kex_supported_nids[]; +extern const uint16_t md_supported_nids[]; + +struct test_cfg test_cfg; + +/* Mock load - called by load_*_credentials in common.c */ +int mock_load_credentials(void ** pkp, + void ** crt) +{ + *crt = NULL; + + if (crypt_load_privkey_str(server_pkp_ec, pkp) < 0) + goto fail_privkey; + + if (crypt_load_crt_str(signed_server_crt_ec, crt) < 0) + goto fail_crt; + + return 0; + + fail_crt: + crypt_free_key(*pkp); + fail_privkey: + *pkp = NULL; + return -1; +} + +/* Stub KEM functions - ECDSA tests don't use KEM */ +int load_server_kem_keypair(__attribute__((unused)) const char * name, + __attribute__((unused)) bool raw_fmt, + __attribute__((unused)) void ** pkp) +{ + return -1; +} + +int load_server_kem_pk(__attribute__((unused)) const char * name, + __attribute__((unused)) struct sec_config * cfg, + __attribute__((unused)) buffer_t * pk) +{ + return -1; +} + +static void test_default_cfg(void) +{ + memset(&test_cfg, 0, sizeof(test_cfg)); + + /* Server: X25519, AES-256-GCM, SHA-256, with auth */ + test_cfg.srv.kex = NID_X25519; + test_cfg.srv.cipher = NID_aes_256_gcm; + test_cfg.srv.kdf = NID_sha256; + test_cfg.srv.md = NID_sha256; + test_cfg.srv.auth = AUTH; + + /* Client: same KEX/cipher/kdf/md, no auth */ + test_cfg.cli.kex = NID_X25519; + test_cfg.cli.cipher = NID_aes_256_gcm; + test_cfg.cli.kdf = NID_sha256; + test_cfg.cli.md = NID_sha256; + test_cfg.cli.auth = NO_AUTH; +} + +static int test_oap_auth_init_fini(void) +{ + TEST_START(); + + if (oap_auth_init() < 0) { + printf("Failed to init OAP.\n"); + goto fail; + } + + oap_auth_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_oap_roundtrip(int kex) +{ + struct oap_test_ctx ctx; + const char * kex_str = kex_nid_to_str(kex); + + TEST_START("(%s)", kex_str); + + test_default_cfg(); + test_cfg.srv.kex = kex; + test_cfg.cli.kex = kex; + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Server process failed.\n"); + goto fail_cleanup; + } + + if (oap_cli_complete_ctx(&ctx) < 0) { + printf("Client complete failed.\n"); + goto fail_cleanup; + } + + if (memcmp(ctx.cli.key, ctx.srv.key, SYMMKEYSZ) != 0) { + printf("Client and server keys do not match!\n"); + goto fail_cleanup; + } + + if (ctx.cli.nid == NID_undef || ctx.srv.nid == NID_undef) { + printf("Cipher not set in flow.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS("(%s)", kex_str); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL("(%s)", kex_str); + return TEST_RC_FAIL; +} + +static int test_oap_roundtrip_auth_only(void) +{ + memset(&test_cfg, 0, sizeof(test_cfg)); + + /* Server: auth only, no encryption */ + test_cfg.srv.md = NID_sha256; + test_cfg.srv.auth = AUTH; + + /* Client: no auth, no encryption */ + test_cfg.cli.md = NID_sha256; + test_cfg.cli.auth = NO_AUTH; + + return roundtrip_auth_only(root_ca_crt_ec, im_ca_crt_ec); +} + +static int test_oap_roundtrip_kex_only(void) +{ + memset(&test_cfg, 0, sizeof(test_cfg)); + + /* Server: KEX only, no auth */ + test_cfg.srv.kex = NID_X25519; + test_cfg.srv.cipher = NID_aes_256_gcm; + test_cfg.srv.kdf = NID_sha256; + test_cfg.srv.md = NID_sha256; + test_cfg.srv.auth = NO_AUTH; + + /* Client: KEX only, no auth */ + test_cfg.cli.kex = NID_X25519; + test_cfg.cli.cipher = NID_aes_256_gcm; + test_cfg.cli.kdf = NID_sha256; + test_cfg.cli.md = NID_sha256; + test_cfg.cli.auth = NO_AUTH; + + return roundtrip_kex_only(); +} + +static int test_oap_piggyback_data(void) +{ + struct oap_test_ctx ctx; + const char * cli_data_str = "client_data"; + const char * srv_data_str = "server_data"; + buffer_t srv_data = BUF_INIT; + + TEST_START(); + + test_default_cfg(); + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + /* Client prepares request with piggybacked data */ + ctx.data.len = strlen(cli_data_str); + ctx.data.data = malloc(ctx.data.len); + if (ctx.data.data == NULL) + goto fail_cleanup; + memcpy(ctx.data.data, cli_data_str, ctx.data.len); + + if (oap_cli_prepare_ctx(&ctx) < 0) + goto fail_cleanup; + + /* Set server's response data (ctx.data will take cli data) */ + srv_data.len = strlen(srv_data_str); + srv_data.data = (uint8_t *) srv_data_str; + + freebuf(ctx.data); + ctx.data.data = srv_data.data; + ctx.data.len = srv_data.len; + srv_data.data = NULL; + srv_data.len = 0; + + if (oap_srv_process_ctx(&ctx) < 0) + goto fail_cleanup; + + /* Verify server received client's piggybacked data */ + if (ctx.data.len != strlen(cli_data_str) || + memcmp(ctx.data.data, cli_data_str, ctx.data.len) != 0) { + printf("Server did not receive correct client data.\n"); + goto fail_cleanup; + } + + freebuf(ctx.data); + + if (oap_cli_complete_ctx(&ctx) < 0) + goto fail_cleanup; + + /* Verify client received server's piggybacked data */ + if (ctx.data.len != strlen(srv_data_str) || + memcmp(ctx.data.data, srv_data_str, ctx.data.len) != 0) { + printf("Client did not receive correct server data.\n"); + goto fail_cleanup; + } + + /* Free the copied data */ + free(ctx.data.data); + ctx.data.data = NULL; + ctx.data.len = 0; + + if (memcmp(ctx.cli.key, ctx.srv.key, SYMMKEYSZ) != 0) { + printf("Client and server keys do not match!\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + freebuf(srv_data); + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_oap_corrupted_request(void) +{ + test_default_cfg(); + test_cfg.cli.auth = AUTH; + + return corrupted_request(root_ca_crt_ec, im_ca_crt_ec); +} + +static int test_oap_corrupted_response(void) +{ + test_default_cfg(); + + return corrupted_response(root_ca_crt_ec, im_ca_crt_ec); +} + +static int test_oap_truncated_request(void) +{ + test_default_cfg(); + + return truncated_request(root_ca_crt_ec, im_ca_crt_ec); +} + +/* After ID (16), timestamp (8), cipher_nid (2), kdf_nid (2), md (2) */ +#define OAP_CERT_LEN_OFFSET 30 +static int test_oap_inflated_length_field(void) +{ + struct oap_test_ctx ctx; + uint16_t fake; + + test_default_cfg(); + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (ctx.req_hdr.len < OAP_CERT_LEN_OFFSET + 2) { + printf("Request too short for test.\n"); + goto fail_cleanup; + } + + /* Set cert length to claim more bytes than packet contains */ + fake = hton16(60000); + memcpy(ctx.req_hdr.data + OAP_CERT_LEN_OFFSET, &fake, sizeof(fake)); + + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject inflated length field.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Attacker claims cert is smaller - causes misparse of subsequent fields */ +static int test_oap_deflated_length_field(void) +{ + struct oap_test_ctx ctx; + uint16_t fake; + + test_default_cfg(); + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (ctx.req_hdr.len < OAP_CERT_LEN_OFFSET + 2) { + printf("Request too short for test.\n"); + goto fail_cleanup; + } + + /* Set cert length to claim fewer bytes - will misparse rest */ + fake = hton16(1); + memcpy(ctx.req_hdr.data + OAP_CERT_LEN_OFFSET, &fake, sizeof(fake)); + + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject deflated length field.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Header field offsets for byte manipulation */ +#define OAP_CIPHER_NID_OFFSET 24 +#define OAP_KEX_LEN_OFFSET 32 + +/* Server rejects request when cipher NID set but no KEX data provided */ +static int test_oap_nid_without_kex(void) +{ + struct oap_test_ctx ctx; + uint16_t cipher_nid; + uint16_t zero = 0; + + TEST_START(); + + /* Configure unsigned KEX-only mode */ + memset(&test_cfg, 0, sizeof(test_cfg)); + test_cfg.srv.kex = NID_X25519; + test_cfg.srv.cipher = NID_aes_256_gcm; + test_cfg.srv.kdf = NID_sha256; + test_cfg.srv.md = NID_sha256; + test_cfg.srv.auth = NO_AUTH; + test_cfg.cli.kex = NID_X25519; + test_cfg.cli.cipher = NID_aes_256_gcm; + test_cfg.cli.kdf = NID_sha256; + test_cfg.cli.md = NID_sha256; + test_cfg.cli.auth = NO_AUTH; + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + /* Tamper: keep cipher_nid but set kex_len=0, truncate KEX data */ + cipher_nid = hton16(NID_aes_256_gcm); + memcpy(ctx.req_hdr.data + OAP_CIPHER_NID_OFFSET, &cipher_nid, + sizeof(cipher_nid)); + memcpy(ctx.req_hdr.data + OAP_KEX_LEN_OFFSET, &zero, sizeof(zero)); + ctx.req_hdr.len = 36; /* Fixed header only, no KEX data */ + + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject cipher NID without KEX data.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Server rejects OAP request with unsupported cipher NID */ +static int test_oap_unsupported_nid(void) +{ + struct oap_test_ctx ctx; + uint16_t bad_nid; + + TEST_START(); + + /* Configure unsigned KEX-only mode */ + memset(&test_cfg, 0, sizeof(test_cfg)); + test_cfg.srv.kex = NID_X25519; + test_cfg.srv.cipher = NID_aes_256_gcm; + test_cfg.srv.kdf = NID_sha256; + test_cfg.srv.md = NID_sha256; + test_cfg.srv.auth = NO_AUTH; + test_cfg.cli.kex = NID_X25519; + test_cfg.cli.cipher = NID_aes_256_gcm; + test_cfg.cli.kdf = NID_sha256; + test_cfg.cli.md = NID_sha256; + test_cfg.cli.auth = NO_AUTH; + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + /* Tamper: set cipher_nid to unsupported value */ + bad_nid = hton16(9999); + memcpy(ctx.req_hdr.data + OAP_CIPHER_NID_OFFSET, &bad_nid, + sizeof(bad_nid)); + + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject unsupported cipher NID.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_oap_roundtrip_all(void) +{ + int ret = 0; + int i; + + for (i = 0; kex_supported_nids[i] != NID_undef; i++) { + const char * algo = kex_nid_to_str(kex_supported_nids[i]); + + /* Skip KEM algorithms - they're tested in oap_test_pqc */ + if (IS_KEM_ALGORITHM(algo)) + continue; + + ret |= test_oap_roundtrip(kex_supported_nids[i]); + } + + return ret; +} + +/* Cipher negotiation - client should accept server's chosen cipher */ +static int test_oap_cipher_mismatch(void) +{ + struct oap_test_ctx ctx; + + TEST_START(); + + memset(&test_cfg, 0, sizeof(test_cfg)); + + /* Server: ChaCha20-Poly1305, SHA3-256, SHA-384 */ + test_cfg.srv.kex = NID_X25519; + test_cfg.srv.cipher = NID_chacha20_poly1305; + test_cfg.srv.kdf = NID_sha3_256; + test_cfg.srv.md = NID_sha384; + test_cfg.srv.auth = AUTH; + + /* Client: AES-256-GCM, SHA-256, SHA-256 */ + test_cfg.cli.kex = NID_X25519; + test_cfg.cli.cipher = NID_aes_256_gcm; + test_cfg.cli.kdf = NID_sha256; + test_cfg.cli.md = NID_sha256; + test_cfg.cli.auth = NO_AUTH; + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Server process failed.\n"); + goto fail_cleanup; + } + + if (oap_cli_complete_ctx(&ctx) < 0) { + printf("Client complete failed.\n"); + goto fail_cleanup; + } + + /* Verify: both should have the server's chosen cipher and KDF */ + if (ctx.srv.nid != test_cfg.srv.cipher) { + printf("Server cipher mismatch: expected %s, got %s\n", + crypt_nid_to_str(test_cfg.srv.cipher), + crypt_nid_to_str(ctx.srv.nid)); + goto fail_cleanup; + } + + if (ctx.cli.nid != test_cfg.srv.cipher) { + printf("Client cipher mismatch: expected %s, got %s\n", + crypt_nid_to_str(test_cfg.srv.cipher), + crypt_nid_to_str(ctx.cli.nid)); + goto fail_cleanup; + } + + /* Parse response header to check negotiated KDF */ + if (ctx.resp_hdr.len > 26) { + uint16_t resp_kdf_nid; + /* KDF NID at offset 26: ID(16) + ts(8) + cipher(2) */ + resp_kdf_nid = ntoh16(*(uint16_t *)(ctx.resp_hdr.data + 26)); + + if (resp_kdf_nid != test_cfg.srv.kdf) { + printf("Response KDF mismatch: expected %s, got %s\n", + md_nid_to_str(test_cfg.srv.kdf), + md_nid_to_str(resp_kdf_nid)); + goto fail_cleanup; + } + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Test roundtrip with different signature digest algorithms */ +static int test_oap_roundtrip_md(int md) +{ + struct oap_test_ctx ctx; + const char * md_str = md_nid_to_str(md); + + TEST_START("(%s)", md_str ? md_str : "default"); + + memset(&test_cfg, 0, sizeof(test_cfg)); + + /* Server: auth + KEX with specified md */ + test_cfg.srv.kex = NID_X25519; + test_cfg.srv.cipher = NID_aes_256_gcm; + test_cfg.srv.kdf = NID_sha256; + test_cfg.srv.md = md; + test_cfg.srv.auth = AUTH; + + /* Client: no auth */ + test_cfg.cli.kex = NID_X25519; + test_cfg.cli.cipher = NID_aes_256_gcm; + test_cfg.cli.kdf = NID_sha256; + test_cfg.cli.md = md; + test_cfg.cli.auth = NO_AUTH; + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Server process failed.\n"); + goto fail_cleanup; + } + + if (oap_cli_complete_ctx(&ctx) < 0) { + printf("Client complete failed.\n"); + goto fail_cleanup; + } + + if (memcmp(ctx.cli.key, ctx.srv.key, SYMMKEYSZ) != 0) { + printf("Client and server keys do not match!\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS("(%s)", md_str ? md_str : "default"); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL("(%s)", md_str ? md_str : "default"); + return TEST_RC_FAIL; +} + +static int test_oap_roundtrip_md_all(void) +{ + int ret = 0; + int i; + + /* Test with default (0) */ + ret |= test_oap_roundtrip_md(0); + + /* Test with all supported digest NIDs */ + for (i = 0; md_supported_nids[i] != NID_undef; i++) + ret |= test_oap_roundtrip_md(md_supported_nids[i]); + + return ret; +} + +/* Timestamp is at offset 16 (after the 16-byte ID) */ +#define OAP_TIMESTAMP_OFFSET 16 +/* Test that packets with outdated timestamps are rejected */ +static int test_oap_outdated_packet(void) +{ + struct oap_test_ctx ctx; + struct timespec old_ts; + uint64_t old_stamp; + + test_default_cfg(); + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (ctx.req_hdr.len < OAP_TIMESTAMP_OFFSET + sizeof(uint64_t)) { + printf("Request too short for test.\n"); + goto fail_cleanup; + } + + /* Set timestamp to 30 seconds in the past (> 20s replay timer) */ + clock_gettime(CLOCK_REALTIME, &old_ts); + old_ts.tv_sec -= OAP_REPLAY_TIMER + 10; + old_stamp = hton64(TS_TO_UINT64(old_ts)); + memcpy(ctx.req_hdr.data + OAP_TIMESTAMP_OFFSET, &old_stamp, + sizeof(old_stamp)); + + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject outdated packet.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Test that packets from the future are rejected */ +static int test_oap_future_packet(void) +{ + struct oap_test_ctx ctx; + struct timespec future_ts; + uint64_t future_stamp; + + test_default_cfg(); + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (ctx.req_hdr.len < OAP_TIMESTAMP_OFFSET + sizeof(uint64_t)) { + printf("Request too short for test.\n"); + goto fail_cleanup; + } + + /* Set timestamp to 1 second in the future (> 100ms slack) */ + clock_gettime(CLOCK_REALTIME, &future_ts); + future_ts.tv_sec += 1; + future_stamp = hton64(TS_TO_UINT64(future_ts)); + memcpy(ctx.req_hdr.data + OAP_TIMESTAMP_OFFSET, &future_stamp, + sizeof(future_stamp)); + + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject future packet.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Test that replayed packets (same ID + timestamp) are rejected */ +static int test_oap_replay_packet(void) +{ + struct oap_test_ctx ctx; + buffer_t saved_req; + + test_default_cfg(); + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + /* Save the request for replay */ + saved_req.len = ctx.req_hdr.len; + saved_req.data = malloc(saved_req.len); + if (saved_req.data == NULL) { + printf("Failed to allocate saved request.\n"); + goto fail_cleanup; + } + memcpy(saved_req.data, ctx.req_hdr.data, saved_req.len); + + /* First request should succeed */ + if (oap_srv_process_ctx(&ctx) < 0) { + printf("First request should succeed.\n"); + free(saved_req.data); + goto fail_cleanup; + } + + /* Free response from first request before replay */ + freebuf(ctx.resp_hdr); + + /* Restore the saved request for replay */ + freebuf(ctx.req_hdr); + ctx.req_hdr = saved_req; + + /* Replayed request should fail */ + if (oap_srv_process_ctx(&ctx) == 0) { + printf("Server should reject replayed packet.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Test that client rejects server with wrong certificate name */ +static int test_oap_server_name_mismatch(void) +{ + struct oap_test_ctx ctx; + + test_default_cfg(); + + TEST_START(); + + if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0) + goto fail; + + /* Set client's expected name to something different from cert name */ + strcpy(ctx.cli.info.name, "wrong.server.name"); + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Server process failed.\n"); + goto fail_cleanup; + } + + /* Client should reject due to name mismatch */ + if (oap_cli_complete_ctx(&ctx) == 0) { + printf("Client should reject server with wrong cert name.\n"); + goto fail_cleanup; + } + + oap_test_teardown(&ctx); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown(&ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int oap_test(int argc, + char **argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + + ret |= test_oap_auth_init_fini(); + +#ifdef HAVE_OPENSSL + ret |= test_oap_roundtrip_auth_only(); + ret |= test_oap_roundtrip_kex_only(); + ret |= test_oap_piggyback_data(); + + ret |= test_oap_roundtrip_all(); + ret |= test_oap_roundtrip_md_all(); + + ret |= test_oap_corrupted_request(); + ret |= test_oap_corrupted_response(); + ret |= test_oap_truncated_request(); + ret |= test_oap_inflated_length_field(); + ret |= test_oap_deflated_length_field(); + ret |= test_oap_nid_without_kex(); + ret |= test_oap_unsupported_nid(); + + ret |= test_oap_cipher_mismatch(); + + ret |= test_oap_outdated_packet(); + ret |= test_oap_future_packet(); + ret |= test_oap_replay_packet(); + ret |= test_oap_server_name_mismatch(); +#else + (void) test_oap_roundtrip_auth_only; + (void) test_oap_roundtrip_kex_only; + (void) test_oap_piggyback_data; + (void) test_oap_roundtrip; + (void) test_oap_roundtrip_all; + (void) test_oap_roundtrip_md; + (void) test_oap_roundtrip_md_all; + (void) test_oap_corrupted_request; + (void) test_oap_corrupted_response; + (void) test_oap_truncated_request; + (void) test_oap_inflated_length_field; + (void) test_oap_deflated_length_field; + (void) test_oap_nid_without_kex; + (void) test_oap_unsupported_nid; + (void) test_oap_cipher_mismatch; + (void) test_oap_outdated_packet; + (void) test_oap_future_packet; + (void) test_oap_replay_packet; + (void) test_oap_server_name_mismatch; + + ret = TEST_RC_SKIP; +#endif + return ret; +} diff --git a/src/irmd/oap/tests/oap_test_pqc.c b/src/irmd/oap/tests/oap_test_pqc.c new file mode 100644 index 00000000..ed51a6b4 --- /dev/null +++ b/src/irmd/oap/tests/oap_test_pqc.c @@ -0,0 +1,363 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Unit tests of OAP post-quantum key exchange + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#if defined(__linux__) || defined(__CYGWIN__) +#define _DEFAULT_SOURCE +#else +#define _POSIX_C_SOURCE 200809L +#endif + +#include "config.h" + +#include <ouroboros/crypt.h> +#include <ouroboros/flow.h> +#include <ouroboros/name.h> +#include <ouroboros/random.h> +#include <test/test.h> + +#include <test/certs_pqc.h> + +#include "oap.h" +#include "common.h" + +#include <stdbool.h> +#include <string.h> + +#ifdef HAVE_OPENSSL +#include <openssl/evp.h> +#endif + +#define CLI_AUTH 1 +#define NO_CLI_AUTH 0 +#define CLI_ENCAP KEM_MODE_CLIENT_ENCAP +#define SRV_ENCAP KEM_MODE_SERVER_ENCAP + +extern const uint16_t kex_supported_nids[]; +extern const uint16_t md_supported_nids[]; + +static int get_random_kdf(void) +{ + static int idx = 0; + int count; + + if (md_supported_nids[0] == NID_undef) + return NID_undef; + + for (count = 0; md_supported_nids[count] != NID_undef; count++) + ; + + return md_supported_nids[(idx++) % count]; +} + +struct test_cfg test_cfg; + +/* KEM keypair storage for tests (server-side keypair for KEM modes) */ +static void * test_kem_pkp = NULL; /* Private key pair */ +static uint8_t test_kem_pk[4096]; /* Public key buffer */ +static size_t test_kem_pk_len = 0; + +/* Mock load - called by load_*_credentials in common.c */ +int mock_load_credentials(void ** pkp, + void ** crt) +{ + *pkp = NULL; + *crt = NULL; + + if (crypt_load_privkey_str(server_pkp_ml, pkp) < 0) + return -1; + + if (crypt_load_crt_str(signed_server_crt_ml, crt) < 0) { + crypt_free_key(*pkp); + *pkp = NULL; + return -1; + } + + return 0; +} + +int load_server_kem_keypair(const char * name, + bool raw_fmt, + void ** pkp) +{ +#ifdef HAVE_OPENSSL + struct sec_config local_cfg; + ssize_t pk_len; + + (void) name; + (void) raw_fmt; + + /* + * Uses reference counting. The caller will call + * EVP_PKEY_free which decrements the count. + */ + if (test_kem_pkp != NULL) { + if (EVP_PKEY_up_ref((EVP_PKEY *)test_kem_pkp) != 1) + return -1; + + *pkp = test_kem_pkp; + return 0; + } + + /* + * Generate a new KEM keypair from test_cfg.srv.kex. + */ + memset(&local_cfg, 0, sizeof(local_cfg)); + if (test_cfg.srv.kex == NID_undef) + goto fail; + + SET_KEX_ALGO_NID(&local_cfg, test_cfg.srv.kex); + + pk_len = kex_pkp_create(&local_cfg, &test_kem_pkp, test_kem_pk); + if (pk_len < 0) + goto fail; + + test_kem_pk_len = (size_t) pk_len; + + if (EVP_PKEY_up_ref((EVP_PKEY *)test_kem_pkp) != 1) + goto fail_ref; + + *pkp = test_kem_pkp; + + return 0; + fail_ref: + kex_pkp_destroy(test_kem_pkp); + test_kem_pkp = NULL; + test_kem_pk_len = 0; + fail: + return -1; + +#else + (void) name; + (void) raw_fmt; + (void) pkp; + return -1; +#endif +} + +int load_server_kem_pk(const char * name, + struct sec_config * cfg, + buffer_t * pk) +{ + ssize_t len; + + (void) name; + + if (test_kem_pk_len > 0) { + pk->data = malloc(test_kem_pk_len); + if (pk->data == NULL) + return -1; + memcpy(pk->data, test_kem_pk, test_kem_pk_len); + pk->len = test_kem_pk_len; + return 0; + } + + /* Generate keypair on demand if not already done */ + len = kex_pkp_create(cfg, &test_kem_pkp, test_kem_pk); + if (len < 0) + return -1; + + test_kem_pk_len = (size_t) len; + pk->data = malloc(test_kem_pk_len); + if (pk->data == NULL) + return -1; + memcpy(pk->data, test_kem_pk, test_kem_pk_len); + pk->len = test_kem_pk_len; + + return 0; +} + +static void reset_kem_state(void) +{ + if (test_kem_pkp != NULL) { + kex_pkp_destroy(test_kem_pkp); + test_kem_pkp = NULL; + } + test_kem_pk_len = 0; +} + +static void test_cfg_init(int kex, + int cipher, + int kdf, + int kem_mode, + bool cli_auth) +{ + memset(&test_cfg, 0, sizeof(test_cfg)); + + /* Server config */ + test_cfg.srv.kex = kex; + test_cfg.srv.cipher = cipher; + test_cfg.srv.kdf = kdf; + test_cfg.srv.kem_mode = kem_mode; + test_cfg.srv.auth = true; + + /* Client config */ + test_cfg.cli.kex = kex; + test_cfg.cli.cipher = cipher; + test_cfg.cli.kdf = kdf; + test_cfg.cli.kem_mode = kem_mode; + test_cfg.cli.auth = cli_auth; +} + +static int oap_test_setup_kem(struct oap_test_ctx * ctx, + const char * root_ca, + const char * im_ca) +{ + reset_kem_state(); + return oap_test_setup(ctx, root_ca, im_ca); +} + +static void oap_test_teardown_kem(struct oap_test_ctx * ctx) +{ + oap_test_teardown(ctx); +} + +static int test_oap_roundtrip_auth_only(void) +{ + test_cfg_init(NID_undef, NID_undef, NID_undef, 0, false); + + return roundtrip_auth_only(root_ca_crt_ml, im_ca_crt_ml); +} + +static int test_oap_corrupted_request(void) +{ + test_cfg_init(NID_MLKEM768, NID_aes_256_gcm, get_random_kdf(), + SRV_ENCAP, CLI_AUTH); + + return corrupted_request(root_ca_crt_ml, im_ca_crt_ml); +} + +static int test_oap_corrupted_response(void) +{ + test_cfg_init(NID_MLKEM768, NID_aes_256_gcm, get_random_kdf(), + SRV_ENCAP, NO_CLI_AUTH); + + return corrupted_response(root_ca_crt_ml, im_ca_crt_ml); +} + +static int test_oap_truncated_request(void) +{ + test_cfg_init(NID_MLKEM768, NID_aes_256_gcm, get_random_kdf(), + SRV_ENCAP, NO_CLI_AUTH); + + return truncated_request(root_ca_crt_ml, im_ca_crt_ml); +} + +static int test_oap_roundtrip_kem(int kex, + int kem_mode) +{ + struct oap_test_ctx ctx; + const char * kex_str = kex_nid_to_str(kex); + const char * mode_str = kem_mode == CLI_ENCAP ? "cli" : "srv"; + + test_cfg_init(kex, NID_aes_256_gcm, get_random_kdf(), + kem_mode, NO_CLI_AUTH); + + TEST_START("(%s, %s encaps)", kex_str, mode_str); + + if (oap_test_setup_kem(&ctx, root_ca_crt_ml, im_ca_crt_ml) < 0) + goto fail; + + if (oap_cli_prepare_ctx(&ctx) < 0) { + printf("Client prepare failed.\n"); + goto fail_cleanup; + } + + if (oap_srv_process_ctx(&ctx) < 0) { + printf("Server process failed.\n"); + goto fail_cleanup; + } + + if (oap_cli_complete_ctx(&ctx) < 0) { + printf("Client complete failed.\n"); + goto fail_cleanup; + } + + if (memcmp(ctx.cli.key, ctx.srv.key, SYMMKEYSZ) != 0) { + printf("Client and server keys do not match!\n"); + goto fail_cleanup; + } + + if (ctx.cli.nid == NID_undef || + ctx.srv.nid == NID_undef) { + printf("Cipher not set in flow.\n"); + goto fail_cleanup; + } + + oap_test_teardown_kem(&ctx); + + TEST_SUCCESS("(%s, %s encaps)", kex_str, mode_str); + return TEST_RC_SUCCESS; + + fail_cleanup: + oap_test_teardown_kem(&ctx); + fail: + TEST_FAIL("(%s, %s encaps)", kex_str, mode_str); + return TEST_RC_FAIL; +} + +static int test_oap_roundtrip_kem_all(void) +{ + int ret = 0; + int i; + + for (i = 0; kex_supported_nids[i] != NID_undef; i++) { + const char * algo = kex_nid_to_str(kex_supported_nids[i]); + + if (!IS_KEM_ALGORITHM(algo)) + continue; + + ret |= test_oap_roundtrip_kem(kex_supported_nids[i], SRV_ENCAP); + ret |= test_oap_roundtrip_kem(kex_supported_nids[i], CLI_ENCAP); + } + + return ret; +} + +int oap_test_pqc(int argc, + char **argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + +#ifdef HAVE_OPENSSL_PQC + ret |= test_oap_roundtrip_auth_only(); + + ret |= test_oap_roundtrip_kem_all(); + + ret |= test_oap_corrupted_request(); + ret |= test_oap_corrupted_response(); + ret |= test_oap_truncated_request(); +#else + (void) test_oap_roundtrip_auth_only; + (void) test_oap_roundtrip_kem; + (void) test_oap_roundtrip_kem_all; + (void) test_oap_corrupted_request; + (void) test_oap_corrupted_response; + (void) test_oap_truncated_request; + + ret = TEST_RC_SKIP; +#endif + + return ret; +} diff --git a/src/irmd/proc_table.c b/src/irmd/proc_table.c deleted file mode 100644 index a80e8d27..00000000 --- a/src/irmd/proc_table.c +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2021 - * - * The IPC Resource Manager - Process Table - * - * Dimitri Staessens <dimitri@ouroboros.rocks> - * Sander Vrijders <sander@ouroboros.rocks> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., http://www.fsf.org/about/contact/. - */ - -#if defined(__linux__) || defined(__CYGWIN__) -#define _DEFAULT_SOURCE -#else -#define _POSIX_C_SOURCE 200112L -#endif - -#include "config.h" - -#include <ouroboros/list.h> -#include <ouroboros/errno.h> -#include <ouroboros/time_utils.h> - -#include "proc_table.h" -#include "registry.h" - -#include <stdlib.h> -#include <unistd.h> -#include <limits.h> -#include <assert.h> - -struct proc_entry * proc_entry_create(pid_t pid, - char * prog) -{ - struct proc_entry * e; - pthread_condattr_t cattr; - - assert(prog); - - e = malloc(sizeof(*e)); - if (e == NULL) - goto fail_malloc; - - if (pthread_condattr_init(&cattr)) - goto fail_condattr; - -#ifndef __APPLE__ - pthread_condattr_setclock(&cattr, PTHREAD_COND_CLOCK); -#endif - - if (pthread_mutex_init(&e->lock, NULL)) - goto fail_mutex; - - if (pthread_cond_init(&e->cond, &cattr)) - goto fail_cond; - - e->set = shm_flow_set_create(pid); - if (e->set == NULL) - goto fail_set; - - list_head_init(&e->next); - list_head_init(&e->names); - - e->pid = pid; - e->prog = prog; - e->re = NULL; - e->state = PROC_INIT; - - return e; - fail_set: - pthread_cond_destroy(&e->cond);; - fail_cond: - pthread_mutex_destroy(&e->lock); - fail_mutex: - pthread_condattr_destroy(&cattr); - fail_condattr: - free(e); - fail_malloc: - return NULL; -} - -static void cancel_proc_entry(void * o) -{ - struct proc_entry * e = (struct proc_entry *) o; - - e->state = PROC_NULL; - - pthread_mutex_unlock(&e->lock); -} - -void proc_entry_destroy(struct proc_entry * e) -{ - struct list_head * p; - struct list_head * h; - - assert(e); - - pthread_mutex_lock(&e->lock); - - if (e->state == PROC_DESTROY) { - pthread_mutex_unlock(&e->lock); - return; - } - - if (e->state == PROC_SLEEP) - e->state = PROC_DESTROY; - - pthread_cond_signal(&e->cond); - - pthread_cleanup_push(cancel_proc_entry, e); - - while (e->state != PROC_INIT) - pthread_cond_wait(&e->cond, &e->lock); - - pthread_cleanup_pop(false); - - pthread_mutex_unlock(&e->lock); - - shm_flow_set_destroy(e->set); - - pthread_cond_destroy(&e->cond); - pthread_mutex_destroy(&e->lock); - - if (e->prog != NULL) - free(e->prog); - - list_for_each_safe(p, h, &e->names) { - struct str_el * n = list_entry(p, struct str_el, next); - list_del(&n->next); - if (n->str != NULL) - free(n->str); - free(n); - } - - free(e); -} - -int proc_entry_add_name(struct proc_entry * e, - char * name) -{ - struct str_el * s; - - assert(e); - assert(name); - - s = malloc(sizeof(*s)); - if (s == NULL) - return -ENOMEM; - - s->str = name; - list_add(&s->next, &e->names); - - return 0; -} - -void proc_entry_del_name(struct proc_entry * e, - const char * name) -{ - struct list_head * p = NULL; - struct list_head * h = NULL; - - assert(e); - assert(name); - - list_for_each_safe(p, h, &e->names) { - struct str_el * s = list_entry(p, struct str_el, next); - if (!strcmp(name, s->str)) { - list_del(&s->next); - free(s->str); - free(s); - } - } -} - -int proc_entry_sleep(struct proc_entry * e, - struct timespec * timeo) -{ - struct timespec dl; - - int ret = 0; - - assert(e); - - if (timeo != NULL) { - clock_gettime(PTHREAD_COND_CLOCK, &dl); - ts_add(&dl, timeo, &dl); - } - - pthread_mutex_lock(&e->lock); - - if (e->state != PROC_WAKE && e->state != PROC_DESTROY) - e->state = PROC_SLEEP; - - pthread_cleanup_push(cancel_proc_entry, e); - - while (e->state == PROC_SLEEP && ret != -ETIMEDOUT) - if (timeo) - ret = -pthread_cond_timedwait(&e->cond, &e->lock, &dl); - else - ret = -pthread_cond_wait(&e->cond, &e->lock); - - pthread_cleanup_pop(false); - - if (e->state == PROC_DESTROY) { - if (e->re != NULL) - reg_entry_del_pid(e->re, e->pid); - ret = -1; - } - - e->state = PROC_INIT; - - pthread_cond_broadcast(&e->cond); - pthread_mutex_unlock(&e->lock); - - return ret; -} - -void proc_entry_wake(struct proc_entry * e, - struct reg_entry * re) -{ - assert(e); - assert(re); - - pthread_mutex_lock(&e->lock); - - if (e->state != PROC_SLEEP) { - pthread_mutex_unlock(&e->lock); - return; - } - - e->state = PROC_WAKE; - e->re = re; - - pthread_cond_broadcast(&e->cond); - - pthread_cleanup_push(cancel_proc_entry, e); - - while (e->state == PROC_WAKE) - pthread_cond_wait(&e->cond, &e->lock); - - pthread_cleanup_pop(false); - - if (e->state == PROC_DESTROY) - e->state = PROC_INIT; - - pthread_mutex_unlock(&e->lock); -} - -int proc_table_add(struct list_head * proc_table, - struct proc_entry * e) -{ - - assert(proc_table); - assert(e); - - list_add(&e->next, proc_table); - - return 0; -} - -void proc_table_del(struct list_head * proc_table, - pid_t pid) -{ - struct list_head * p; - struct list_head * h; - - assert(proc_table); - - list_for_each_safe(p, h, proc_table) { - struct proc_entry * e = list_entry(p, struct proc_entry, next); - if (pid == e->pid) { - list_del(&e->next); - proc_entry_destroy(e); - } - } -} - -struct proc_entry * proc_table_get(struct list_head * proc_table, - pid_t pid) -{ - struct list_head * h; - - assert(proc_table); - - list_for_each(h, proc_table) { - struct proc_entry * e = list_entry(h, struct proc_entry, next); - if (pid == e->pid) - return e; - } - - return NULL; -} diff --git a/src/irmd/proc_table.h b/src/irmd/proc_table.h deleted file mode 100644 index 9b81a111..00000000 --- a/src/irmd/proc_table.h +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2021 - * - * The IPC Resource Manager - Process Table - * - * Dimitri Staessens <dimitri@ouroboros.rocks> - * Sander Vrijders <sander@ouroboros.rocks> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., http://www.fsf.org/about/contact/. - */ - -#ifndef OUROBOROS_IRMD_PROC_TABLE_H -#define OUROBOROS_IRMD_PROC_TABLE_H - -#include <ouroboros/shm_flow_set.h> - -#include "utils.h" - -#include <unistd.h> -#include <pthread.h> - -enum proc_state { - PROC_NULL = 0, - PROC_INIT, - PROC_SLEEP, - PROC_WAKE, - PROC_DESTROY -}; - -struct proc_entry { - struct list_head next; - pid_t pid; - char * prog; /* program instantiated */ - struct list_head names; /* names for which process accepts flows */ - struct shm_flow_set * set; - - struct reg_entry * re; /* reg_entry for which a flow arrived */ - - /* The process will block on this */ - enum proc_state state; - pthread_cond_t cond; - pthread_mutex_t lock; -}; - -struct proc_entry * proc_entry_create(pid_t proc, - char * prog); - -void proc_entry_destroy(struct proc_entry * e); - -int proc_entry_sleep(struct proc_entry * e, - struct timespec * timeo); - -void proc_entry_wake(struct proc_entry * e, - struct reg_entry * re); - -void proc_entry_cancel(struct proc_entry * e); - -int proc_entry_add_name(struct proc_entry * e, - char * name); - -void proc_entry_del_name(struct proc_entry * e, - const char * name); - -int proc_table_add(struct list_head * proc_table, - struct proc_entry * e); - -void proc_table_del(struct list_head * proc_table, - pid_t pid); - -struct proc_entry * proc_table_get(struct list_head * proc_table, - pid_t pid); - -#endif /* OUROBOROS_IRMD_PROC_TABLE_H */ diff --git a/src/irmd/prog_table.c b/src/irmd/prog_table.c deleted file mode 100644 index eb2b1966..00000000 --- a/src/irmd/prog_table.c +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2021 - * - * The IPC Resource Manager - Program Table - * - * Dimitri Staessens <dimitri@ouroboros.rocks> - * Sander Vrijders <sander@ouroboros.rocks> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., http://www.fsf.org/about/contact/. - */ - -#include <ouroboros/errno.h> -#include <ouroboros/irm.h> - -#include "prog_table.h" -#include "utils.h" - -#include <assert.h> -#include <stdlib.h> -#include <string.h> - -struct prog_entry * prog_entry_create(char * prog, - uint32_t flags, - char ** argv) -{ - struct prog_entry * e; - - assert(prog); - - e = malloc(sizeof(*e)); - if (e == NULL) - return NULL; - - list_head_init(&e->next); - list_head_init(&e->names); - - e->prog = prog; - e->flags = flags; - - if (flags & BIND_AUTO) { - e->argv = argv; - } else { - e->argv = NULL; - argvfree(argv); - argv = NULL; - } - - return e; -} -void prog_entry_destroy(struct prog_entry * e) -{ - struct list_head * p = NULL; - struct list_head * h = NULL; - - if (e == NULL) - return; - - if (e->prog != NULL) - free(e->prog); - - if (e->argv != NULL) - argvfree(e->argv); - - list_for_each_safe(p, h, &e->names) { - struct str_el * s = list_entry(p, struct str_el, next); - list_del(&s->next); - free(s->str); - free(s); - } - - free(e); -} - -int prog_entry_add_name(struct prog_entry * e, - char * name) -{ - struct str_el * s; - - if (e == NULL || name == NULL) - return -EINVAL; - - s = malloc(sizeof(*s)); - if (s == NULL) - return -ENOMEM; - - s->str = name; - list_add(&s->next, &e->names); - - return 0; -} - -void prog_entry_del_name(struct prog_entry * e, - char * name) -{ - struct list_head * p = NULL; - struct list_head * h = NULL; - - list_for_each_safe(p, h, &e->names) { - struct str_el * s = list_entry(p, struct str_el, next); - if (!strcmp(name, s->str)) { - list_del(&s->next); - if (s->str != NULL) - free(s->str); - free(s); - } - } -} - -int prog_table_add(struct list_head * prog_table, - struct prog_entry * e) -{ - assert(prog_table); - assert(e); - - list_add(&e->next, prog_table); - - return 0; -} - -void prog_table_del(struct list_head * prog_table, - char * prog) -{ - struct list_head * p; - struct list_head * h; - - assert(prog_table); - assert(prog); - - list_for_each_safe(p, h, prog_table) { - struct prog_entry * e = list_entry(p, struct prog_entry, next); - if (!strcmp(prog, e->prog)) { - list_del(&e->next); - prog_entry_destroy(e); - } - } -} - -struct prog_entry * prog_table_get(struct list_head * prog_table, - char * prog) -{ - struct list_head * p; - - assert(prog_table); - assert(prog); - - list_for_each(p, prog_table) { - struct prog_entry * e = list_entry(p, struct prog_entry, next); - if (!strcmp(e->prog, prog)) - return e; - } - - return NULL; -} diff --git a/src/irmd/prog_table.h b/src/irmd/prog_table.h deleted file mode 100644 index eed046c8..00000000 --- a/src/irmd/prog_table.h +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2021 - * - * The IPC Resource Manager - Program Table - * - * Dimitri Staessens <dimitri@ouroboros.rocks> - * Sander Vrijders <sander@ouroboros.rocks> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., http://www.fsf.org/about/contact/. - */ - -#ifndef OUROBOROS_IRMD_PROG_TABLE_H -#define OUROBOROS_IRMD_PROG_TABLE_H - -#include <ouroboros/list.h> - -#include <unistd.h> -#include <stdint.h> - -struct prog_entry { - struct list_head next; - char * prog; /* name of binary */ - uint32_t flags; - char ** argv; - struct list_head names; /* names that all instances will listen for */ -}; - -struct prog_entry * prog_entry_create(char * prog, - uint32_t flags, - char ** argv); - -void prog_entry_destroy(struct prog_entry * e); - -int prog_entry_add_name(struct prog_entry * e, - char * name); - -void prog_entry_del_name(struct prog_entry * e, - char * name); - -int prog_table_add(struct list_head * prog_table, - struct prog_entry * e); - -void prog_table_del(struct list_head * prog_table, - char * prog); - -struct prog_entry * prog_table_get(struct list_head * prog_table, - char * prog); - -#endif /* OUROBOROS_IRMD_PROG_TABLE_H */ diff --git a/src/irmd/reg/flow.c b/src/irmd/reg/flow.c new file mode 100644 index 00000000..52b03e61 --- /dev/null +++ b/src/irmd/reg/flow.c @@ -0,0 +1,215 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * The IPC Resource Manager - Registry - Flows + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#define _POSIX_C_SOURCE 200809L + +#define OUROBOROS_PREFIX "reg/flow" + +#include <ouroboros/logs.h> + +#include "flow.h" + +#include <assert.h> +#include <errno.h> +#include <stdbool.h> +#include <stdlib.h> + +struct reg_flow * reg_flow_create(const struct flow_info * info) +{ + struct reg_flow * flow; + + assert(info != NULL); + assert(info->id > 0); + assert(info->n_pid != 0); + assert(info->n_1_pid == 0); + assert(info->mpl == 0); + assert(info->state == FLOW_INIT); + + flow = malloc(sizeof(*flow)); + if (flow == NULL) { + log_err("Failed to malloc flow."); + goto fail_malloc; + } + + memset(flow, 0, sizeof(*flow)); + + clock_gettime(PTHREAD_COND_CLOCK, &flow->t0); + list_head_init(&flow->next); + + flow->info = *info; + + return flow; + + fail_malloc: + return NULL; +} + +static void destroy_rbuffs(struct reg_flow * flow) +{ + if (flow->n_rb != NULL) + ssm_rbuff_destroy(flow->n_rb); + flow->n_rb = NULL; + + if (flow->n_1_rb != NULL) + ssm_rbuff_destroy(flow->n_1_rb); + flow->n_1_rb = NULL; +} + +void reg_flow_destroy(struct reg_flow * flow) +{ + assert(flow != NULL); + + switch(flow->info.state) { + case FLOW_ACCEPT_PENDING: + clrbuf(flow->data); + /* FALLTHRU */ + default: + destroy_rbuffs(flow); + break; + } + + assert(flow->n_rb == NULL); + assert(flow->n_1_rb == NULL); + assert(flow->data.data == NULL); + assert(flow->data.len == 0); + + assert(list_is_empty(&flow->next)); + + free(flow); +} + +static int create_rbuffs(struct reg_flow * flow, + struct flow_info * info) +{ + assert(flow != NULL); + assert(info != NULL); + + flow->n_rb = ssm_rbuff_create(info->n_pid, info->id); + if (flow->n_rb == NULL) + goto fail_n_rb; + + if (ssm_rbuff_mlock(flow->n_rb) < 0) + log_warn("Failed to mlock n_rb for flow %d.", info->id); + + assert(flow->info.n_1_pid == 0); + assert(flow->n_1_rb == NULL); + + flow->info.n_1_pid = info->n_1_pid; + flow->n_1_rb = ssm_rbuff_create(info->n_1_pid, info->id); + if (flow->n_1_rb == NULL) + goto fail_n_1_rb; + + if (ssm_rbuff_mlock(flow->n_1_rb) < 0) + log_warn("Failed to mlock n_1_rb for flow %d.", info->id); + + return 0; + + fail_n_1_rb: + ssm_rbuff_destroy(flow->n_rb); + fail_n_rb: + return -ENOMEM; +} + +int reg_flow_update(struct reg_flow * flow, + struct flow_info * info) +{ + assert(flow != NULL); + assert(info != NULL); + + assert(flow->info.id == info->id); + + switch(info->state) { + case FLOW_ACCEPT_PENDING: + assert(flow->info.state == FLOW_INIT); + flow->info.n_pid = info->n_pid; + break; + case FLOW_ALLOC_PENDING: + assert(flow->info.state == FLOW_INIT); + assert(info->n_1_pid != 0); + + if (create_rbuffs(flow, info) < 0) + goto fail; + + break; + case FLOW_ALLOCATED: + assert(info->n_1_pid != 0); + assert(flow->info.state > FLOW_INIT); + assert(flow->info.state < FLOW_ALLOCATED); + assert(flow->info.n_pid != 0); + assert(info->mpl != 0); + + flow->info.mpl = info->mpl; + + if (flow->info.state == FLOW_ALLOC_PENDING) + break; + + flow->info.qs = info->qs; + + if (create_rbuffs(flow, info) < 0) + goto fail; + break; + case FLOW_DEALLOCATED: + destroy_rbuffs(flow); + break; + case FLOW_DEALLOC_PENDING: + break; + default: + assert(false); + return -EPERM; + } + + flow->info.state = info->state; + flow->info.uid = info->uid; + + *info = flow->info; + + return 0; + fail: + return -ENOMEM; +} + +void reg_flow_set_data(struct reg_flow * flow, + const buffer_t * buf) +{ + assert(flow != NULL); + assert(buf != NULL); + assert(flow->data.data == NULL); + assert(flow->data.len == 0); + + flow->data = *buf; +} + +void reg_flow_get_data(struct reg_flow * flow, + buffer_t * buf) +{ + assert(flow != NULL); + assert(buf != NULL); + + *buf = flow->data; + + clrbuf(flow->data); +} + +void reg_flow_free_data(struct reg_flow * flow) +{ + freebuf(flow->data); +} diff --git a/src/irmd/reg/flow.h b/src/irmd/reg/flow.h new file mode 100644 index 00000000..b671d486 --- /dev/null +++ b/src/irmd/reg/flow.h @@ -0,0 +1,67 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * The IPC Resource Manager - Registry - Flows + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#ifndef OUROBOROS_IRMD_REG_FLOW_H +#define OUROBOROS_IRMD_REG_FLOW_H + +#include <ouroboros/list.h> +#include <ouroboros/flow.h> +#include <ouroboros/name.h> +#include <ouroboros/pthread.h> +#include <ouroboros/qos.h> +#include <ouroboros/ssm_rbuff.h> +#include <ouroboros/utils.h> + +#include <sys/types.h> +#include <time.h> + +struct reg_flow { + struct list_head next; + + struct flow_info info; + int response; + + buffer_t data; + struct timespec t0; + + char name[NAME_SIZE + 1]; + + struct ssm_rbuff * n_rb; + struct ssm_rbuff * n_1_rb; +}; + +struct reg_flow * reg_flow_create(const struct flow_info * info); + +void reg_flow_destroy(struct reg_flow * flow); + +int reg_flow_update(struct reg_flow * flow, + struct flow_info * info); + +void reg_flow_set_data(struct reg_flow * flow, + const buffer_t * buf); + +void reg_flow_get_data(struct reg_flow * flow, + buffer_t * buf); + +void reg_flow_free_data(struct reg_flow * flow); + +#endif /* OUROBOROS_IRMD_REG_FLOW_H */ diff --git a/src/irmd/reg/ipcp.c b/src/irmd/reg/ipcp.c new file mode 100644 index 00000000..74ec4939 --- /dev/null +++ b/src/irmd/reg/ipcp.c @@ -0,0 +1,91 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * The IPC Resource Manager - Registry - IPCPs + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#define _POSIX_C_SOURCE 200809L + +#define OUROBOROS_PREFIX "reg/ipcp" + +#include <ouroboros/logs.h> +#include <ouroboros/time.h> + +#include "ipcp.h" + +#include <assert.h> +#include <errno.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + +struct reg_ipcp * reg_ipcp_create(const struct ipcp_info * info) +{ + struct reg_ipcp * ipcp; + + assert(info != NULL); + assert(info->state == IPCP_INIT); + + ipcp = malloc(sizeof(*ipcp)); + if (ipcp == NULL) { + log_err("Failed to malloc ipcp."); + goto fail_malloc; + } + + memset(ipcp, 0, sizeof(*ipcp)); + memset(&ipcp->layer, 0, sizeof(ipcp->layer)); + + list_head_init(&ipcp->next); + + ipcp->info = *info; + ipcp->info.state = IPCP_INIT; + + strcpy(ipcp->layer.name, "Not enrolled."); + + return ipcp; + + fail_malloc: + return NULL; +} + +void reg_ipcp_destroy(struct reg_ipcp * ipcp) +{ + assert(ipcp != NULL); + + assert(list_is_empty(&ipcp->next)); + + free(ipcp); +} + +void reg_ipcp_update(struct reg_ipcp * ipcp, + const struct ipcp_info * info) +{ + assert(ipcp != NULL); + + ipcp->info = *info; +} + +void reg_ipcp_set_layer(struct reg_ipcp * ipcp, + const struct layer_info * info) +{ + assert(ipcp != NULL); + assert(ipcp->info.state == IPCP_BOOT); + + ipcp->layer = *info; +} diff --git a/src/irmd/reg/ipcp.h b/src/irmd/reg/ipcp.h new file mode 100644 index 00000000..375973a7 --- /dev/null +++ b/src/irmd/reg/ipcp.h @@ -0,0 +1,47 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * The IPC Resource Manager - Registry - IPCPs + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#ifndef OUROBOROS_IRMD_REG_IPCP_H +#define OUROBOROS_IRMD_REG_IPCP_H + +#include <ouroboros/list.h> +#include <ouroboros/ipcp.h> + +struct reg_ipcp { + struct list_head next; + + struct ipcp_info info; + + struct layer_info layer; +}; + +struct reg_ipcp * reg_ipcp_create(const struct ipcp_info * info); + +void reg_ipcp_destroy(struct reg_ipcp * ipcp); + +void reg_ipcp_update(struct reg_ipcp * ipcp, + const struct ipcp_info * info); + +void reg_ipcp_set_layer(struct reg_ipcp * ipcp, + const struct layer_info * info); + +#endif /* OUROBOROS_IRMD_REG_IPCP_H */ diff --git a/src/irmd/reg/name.c b/src/irmd/reg/name.c new file mode 100644 index 00000000..4e609711 --- /dev/null +++ b/src/irmd/reg/name.c @@ -0,0 +1,381 @@ + +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * The IPC Resource Manager - Registry - Names + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#define _POSIX_C_SOURCE 200809L + +#define OUROBOROS_PREFIX "reg/name" + +#include <ouroboros/logs.h> +#include <ouroboros/utils.h> + +#include "name.h" + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +struct prog_entry { + struct list_head next; + char ** exec; +}; + +struct proc_entry { + struct list_head next; + pid_t pid; +}; + +static void __free_prog_entry(struct prog_entry * entry) +{ + assert(entry != NULL); + assert(entry->exec != NULL); + + argvfree(entry->exec); + free(entry); +} + +struct reg_name * reg_name_create(const struct name_info * info) +{ + struct reg_name * name; + + assert(info != NULL); + + name = malloc(sizeof(*name)); + if (name == NULL) { + log_err("Failed to malloc name."); + goto fail_malloc; + } + + memset(name, 0, sizeof(*name)); + + list_head_init(&name->next); + list_head_init(&name->progs.list); + list_head_init(&name->procs.list); + list_head_init(&name->active.list); + + name->info = *info; + + return name; + + fail_malloc: + return NULL; +} + +void reg_name_destroy(struct reg_name * name) +{ + assert(name != NULL); + + assert(list_is_empty(&name->next)); + + assert(name->progs.len == 0); + assert(name->procs.len == 0); + assert(name->active.len == 0); + + assert(list_is_empty(&name->progs.list)); + assert(list_is_empty(&name->procs.list)); + assert(list_is_empty(&name->active.list)); + + free(name); +} + +static struct proc_entry * __reg_name_get_active(const struct reg_name * name, + pid_t pid) +{ + struct list_head * p; + + assert(name != NULL); + assert(pid > 0); + + list_for_each(p, &name->active.list) { + struct proc_entry * entry; + entry = list_entry(p, struct proc_entry, next); + if (entry->pid == pid) + return entry; + } + + return NULL; +} + +static void __reg_name_del_all_active(struct reg_name * name, + pid_t pid) +{ + struct list_head * p; + struct list_head * h; + + list_for_each_safe(p, h, &name->active.list) { + struct proc_entry * entry; + entry = list_entry(p, struct proc_entry, next); + if (entry->pid == pid) { + list_del(&entry->next); + free(entry); + --name->active.len; + } + } +} + +static struct proc_entry * __reg_name_get_proc(const struct reg_name * name, + pid_t pid) +{ + struct list_head * p; + + assert(name != NULL); + assert(pid > 0); + + list_for_each(p, &name->procs.list) { + struct proc_entry * entry; + entry = list_entry(p, struct proc_entry, next); + if (entry->pid == pid) + return entry; + } + + return NULL; +} + +static struct prog_entry * __reg_name_get_prog(const struct reg_name * name, + const char * prog) +{ + struct list_head * p; + + assert(name != NULL); + assert(prog != NULL); + + list_for_each(p, &name->progs.list) { + struct prog_entry * entry; + entry = list_entry(p, struct prog_entry, next); + if (strcmp(entry->exec[0], prog) == 0) + return entry; + } + + return NULL; +} + +int reg_name_add_active(struct reg_name * name, + pid_t pid) +{ + struct proc_entry * entry; + + assert(name != NULL); + assert(pid > 0); + + assert(__reg_name_get_proc(name, pid) != NULL); + + log_dbg("Process %d accepting flows for %s.", pid, name->info.name); + + if (__reg_name_get_active(name, pid) != NULL) + log_dbg("Process calling accept from multiple threads."); + + entry = malloc(sizeof(*entry)); + if (entry == NULL) { + log_err("Failed to malloc active."); + goto fail_malloc; + } + + entry->pid = pid; + + switch (name->info.pol_lb) { + case LB_RR: /* Round robin policy. */ + list_add_tail(&entry->next, &name->active.list); + break; + case LB_SPILL: /* Keep accepting flows on the current process */ + list_add(&entry->next, &name->active.list); + break; + default: + goto fail_unreachable; + } + + ++name->active.len; + + return 0; + + fail_unreachable: + free(entry); + assert(false); + fail_malloc: + return -1; +} + +void reg_name_del_active(struct reg_name * name, + pid_t pid) +{ + struct proc_entry * entry; + + entry = __reg_name_get_active(name, pid); + if (entry == NULL) + return; + + list_del(&entry->next); + + --name->active.len; + + free(entry); +} + +pid_t reg_name_get_active(struct reg_name * name) +{ + struct proc_entry * e; + + assert(name != NULL); + + if (list_is_empty(&name->active.list)) + return -1; + + e = list_first_entry(&name->active.list, struct proc_entry, next); + + return e->pid; +} + +int reg_name_add_proc(struct reg_name * name, + pid_t pid) +{ + struct proc_entry * entry; + + assert(name != NULL); + assert(pid > 0); + + assert(__reg_name_get_proc(name, pid) == NULL); + + entry = malloc(sizeof(*entry)); + if (entry == NULL) { + log_err("Failed to malloc proc."); + goto fail_malloc; + } + + entry->pid = pid; + + list_add(&entry->next, &name->procs.list); + + ++name->procs.len; + + return 0; + + fail_malloc: + return -1; +} + +void reg_name_del_proc(struct reg_name * name, + pid_t pid) +{ + struct proc_entry * entry; + + assert(name != NULL); + assert(pid > 0); + + entry = __reg_name_get_proc(name, pid); + if (entry == NULL) + return; + + __reg_name_del_all_active(name, pid); + + list_del(&entry->next); + + free(entry); + + --name->procs.len; + + assert(__reg_name_get_proc(name, pid) == NULL); +} + +bool reg_name_has_proc(const struct reg_name * name, + pid_t pid) +{ + return __reg_name_get_proc(name, pid) != NULL; +} + +int reg_name_add_prog(struct reg_name * name, + char ** exec) +{ + struct prog_entry * entry; + + assert(name != NULL); + assert(exec != NULL); + assert(exec[0] != NULL); + + assert(__reg_name_get_prog(name, exec[0]) == NULL); + + entry = malloc(sizeof(*entry)); + if (entry == NULL) { + log_err("Failed to malloc prog."); + goto fail_malloc; + } + + entry->exec = argvdup(exec); + if (entry->exec == NULL) { + log_err("Failed to argvdup prog."); + goto fail_exec; + } + + list_add(&entry->next, &name->progs.list); + + log_dbg("Add prog %s to name %s.", exec[0], name->info.name); + + ++name->progs.len; + + return 0; + + fail_exec: + free(entry); + fail_malloc: + return -1; +} + +void reg_name_del_prog(struct reg_name * name, + const char * prog) +{ + struct prog_entry * entry; + + assert(name != NULL); + assert(prog != NULL); + + entry = __reg_name_get_prog(name, prog); + if (entry == NULL) + return; + + list_del(&entry->next); + + __free_prog_entry(entry); + + --name->progs.len; + + assert(__reg_name_get_prog(name, prog) == NULL); +} + +bool reg_name_has_prog(const struct reg_name * name, + const char * prog) +{ + assert(name != NULL); + assert(prog != NULL); + + return __reg_name_get_prog(name, prog) != NULL; +} + +char ** reg_name_get_exec(const struct reg_name * name) +{ + struct prog_entry * e; + + if (list_is_empty(&name->progs.list)) + return NULL; + + e = list_first_entry(&name->progs.list, struct prog_entry, next); + + return e->exec; +} diff --git a/src/irmd/reg/name.h b/src/irmd/reg/name.h new file mode 100644 index 00000000..30a64e1c --- /dev/null +++ b/src/irmd/reg/name.h @@ -0,0 +1,88 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * The IPC Resource Manager - Registry - Names + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#ifndef OUROBOROS_IRMD_REG_NAME_H +#define OUROBOROS_IRMD_REG_NAME_H + +#include <ouroboros/list.h> +#include <ouroboros/name.h> + +#define BIND_AUTO 0x01 + +struct reg_name { + struct list_head next; + + struct name_info info; + + struct { + void * key; + void * crt; + } cache; + + struct { + struct list_head list; + size_t len; + } progs; /* autostart programs for this name */ + + struct { + struct list_head list; + size_t len; + } procs; /* processes bound to this name */ + + struct { + struct list_head list; + size_t len; + } active; /* processes actively calling accept */ +}; + +struct reg_name * reg_name_create(const struct name_info * info); + +void reg_name_destroy(struct reg_name * name); + +int reg_name_add_proc(struct reg_name * name, + pid_t proc); + +void reg_name_del_proc(struct reg_name * name, + pid_t proc); + +bool reg_name_has_proc(const struct reg_name * name, + pid_t proc); + +int reg_name_add_prog(struct reg_name * name, + char ** exec); + +void reg_name_del_prog(struct reg_name * name, + const char * prog); + +bool reg_name_has_prog(const struct reg_name * name, + const char * prog); + +char ** reg_name_get_exec(const struct reg_name * name); + +int reg_name_add_active(struct reg_name * name, + pid_t proc); + +pid_t reg_name_get_active(struct reg_name * name); + +void reg_name_del_active(struct reg_name * name, + pid_t proc); +#endif /* OUROBOROS_IRMD_REG_NAME_H */ diff --git a/src/irmd/reg/pool.c b/src/irmd/reg/pool.c new file mode 100644 index 00000000..fd983db8 --- /dev/null +++ b/src/irmd/reg/pool.c @@ -0,0 +1,101 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * The IPC Resource Manager - Registry - Per-User Pools + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#define _POSIX_C_SOURCE 200809L + +#define OUROBOROS_PREFIX "reg/pool" + +#include <ouroboros/logs.h> +#include <ouroboros/ssm_pool.h> + +#include "pool.h" + +#include <assert.h> +#include <stdlib.h> + +struct reg_pool * reg_pool_create(uid_t uid, + gid_t gid) +{ + struct reg_pool * pool; + + pool = malloc(sizeof(*pool)); + if (pool == NULL) { + log_err("Failed to malloc pool."); + goto fail_malloc; + } + + pool->ssm = ssm_pool_create(uid, gid); + if (pool->ssm == NULL) { + log_err("Failed to create PUP for uid %d.", uid); + goto fail_ssm; + } + + list_head_init(&pool->next); + pool->uid = uid; + pool->gid = gid; + pool->refcount = 1; + + log_dbg("Created PUP for uid %d gid %d.", uid, gid); + + return pool; + + fail_ssm: + free(pool); + fail_malloc: + return NULL; +} + +void reg_pool_destroy(struct reg_pool * pool) +{ + assert(pool != NULL); + assert(pool->refcount == 0); + + log_dbg("Destroying PUP for uid %d.", pool->uid); + + ssm_pool_destroy(pool->ssm); + + assert(list_is_empty(&pool->next)); + + free(pool); +} + +void reg_pool_ref(struct reg_pool * pool) +{ + assert(pool != NULL); + assert(pool->refcount > 0); + + pool->refcount++; + + log_dbg("PUP uid %d refcount++ -> %zu.", pool->uid, pool->refcount); +} + +int reg_pool_unref(struct reg_pool * pool) +{ + assert(pool != NULL); + assert(pool->refcount > 0); + + pool->refcount--; + + log_dbg("PUP uid %d refcount-- -> %zu.", pool->uid, pool->refcount); + + return pool->refcount == 0 ? 0 : 1; +} diff --git a/src/irmd/reg/pool.h b/src/irmd/reg/pool.h new file mode 100644 index 00000000..576f491c --- /dev/null +++ b/src/irmd/reg/pool.h @@ -0,0 +1,48 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * The IPC Resource Manager - Registry - Per-User Pools + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#ifndef OUROBOROS_IRMD_REG_POOL_H +#define OUROBOROS_IRMD_REG_POOL_H + +#include <ouroboros/list.h> +#include <ouroboros/ssm_pool.h> + +#include <sys/types.h> + +struct reg_pool { + struct list_head next; + uid_t uid; + gid_t gid; + size_t refcount; + struct ssm_pool * ssm; +}; + +struct reg_pool * reg_pool_create(uid_t uid, + gid_t gid); + +void reg_pool_destroy(struct reg_pool * pool); + +void reg_pool_ref(struct reg_pool * pool); + +int reg_pool_unref(struct reg_pool * pool); + +#endif /* OUROBOROS_IRMD_REG_POOL_H */ diff --git a/src/irmd/reg/proc.c b/src/irmd/reg/proc.c new file mode 100644 index 00000000..b97dcf2d --- /dev/null +++ b/src/irmd/reg/proc.c @@ -0,0 +1,193 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * The IPC Resource Manager - Registry - Processes + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This procram is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This procram is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this procram; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#define _POSIX_C_SOURCE 200809L + +#define OUROBOROS_PREFIX "reg/proc" + +#include <ouroboros/logs.h> +#include <ouroboros/utils.h> + +#include "proc.h" + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +struct name_entry { + struct list_head next; + char * name; +}; + +static void __free_name_entry(struct name_entry * entry) +{ + assert(entry != NULL); + assert(entry->name != NULL); + + free(entry->name); + free(entry); +} + +static void __reg_proc_clear_names(struct reg_proc * proc) +{ + struct list_head * p; + struct list_head * h; + + assert(proc != NULL); + + list_for_each_safe(p, h, &proc->names) { + struct name_entry * entry; + entry = list_entry(p, struct name_entry, next); + list_del(&entry->next); + __free_name_entry(entry); + proc->n_names--; + } +} + +struct reg_proc * reg_proc_create(const struct proc_info * info) +{ + struct reg_proc * proc; + + assert(info != NULL); + + proc = malloc(sizeof(*proc)); + if (proc == NULL) { + log_err("Failed to malloc proc."); + goto fail_malloc; + } + + memset(proc, 0, sizeof(*proc)); + + proc->set = ssm_flow_set_create(info->pid); + if (proc->set == NULL) { + log_err("Failed to create flow set for %d.", info->pid); + goto fail_set; + } + + list_head_init(&proc->next); + list_head_init(&proc->names); + + proc->info = *info; + proc->n_names = 0; + + return proc; + + fail_set: + free(proc); + fail_malloc: + return NULL; +} + +void reg_proc_destroy(struct reg_proc * proc) +{ + assert(proc != NULL); + + ssm_flow_set_destroy(proc->set); + + __reg_proc_clear_names(proc); + + assert(list_is_empty(&proc->next)); + + assert(proc->n_names == 0); + + assert(list_is_empty(&proc->names)); + + free(proc); +} + +static struct name_entry * __reg_proc_get_name(const struct reg_proc * proc, + const char * name) +{ + struct list_head * p; + + list_for_each(p, &proc->names) { + struct name_entry * entry; + entry = list_entry(p, struct name_entry, next); + if (strcmp(entry->name, name) == 0) + return entry; + } + + return NULL; +} + +int reg_proc_add_name(struct reg_proc * proc, + const char * name) +{ + struct name_entry * entry; + + assert(__reg_proc_get_name(proc, name) == NULL); + + entry = malloc(sizeof(*entry)); + if (entry == NULL) { + log_err("Failed to malloc name."); + goto fail_malloc; + } + + entry->name = strdup(name); + if (entry == NULL) { + log_err("Failed to strdup name."); + goto fail_name; + } + + list_add(&entry->next, &proc->names); + + proc->n_names++; + + return 0; + + fail_name: + free(entry); + fail_malloc: + return -1; +} + +void reg_proc_del_name(struct reg_proc * proc, + const char * name) +{ + struct name_entry * entry; + + entry = __reg_proc_get_name(proc, name); + if(entry == NULL) + return; + + list_del(&entry->next); + + __free_name_entry(entry); + + proc->n_names--; + + assert(__reg_proc_get_name(proc, name) == NULL); +} + +bool reg_proc_has_name(const struct reg_proc * proc, + const char * name) +{ + return __reg_proc_get_name(proc, name) != NULL; +} + +bool reg_proc_is_privileged(const struct reg_proc * proc) +{ + assert(proc != NULL); + + return is_ouroboros_member_uid(proc->info.uid); +} diff --git a/src/irmd/reg/proc.h b/src/irmd/reg/proc.h new file mode 100644 index 00000000..be4c1161 --- /dev/null +++ b/src/irmd/reg/proc.h @@ -0,0 +1,58 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * The IPC Resource Manager - Registry - Processes + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#ifndef OUROBOROS_IRMD_REG_PROC_H +#define OUROBOROS_IRMD_REG_PROC_H + +#include <ouroboros/list.h> +#include <ouroboros/proc.h> +#include <ouroboros/ssm_flow_set.h> + +struct reg_proc { + struct list_head next; + + struct proc_info info; + + struct list_head names; /* process accepts flows for names */ + size_t n_names; /* number of names */ + + struct ssm_flow_set * set; +}; + +struct reg_proc * reg_proc_create(const struct proc_info * info); + +void reg_proc_destroy(struct reg_proc * proc); + +void reg_proc_clear(struct reg_proc * proc); + +int reg_proc_add_name(struct reg_proc * proc, + const char * name); + +void reg_proc_del_name(struct reg_proc * proc, + const char * name); + +bool reg_proc_has_name(const struct reg_proc * proc, + const char * name); + +bool reg_proc_is_privileged(const struct reg_proc * proc); + +#endif /* OUROBOROS_IRMD_REG_PROC_H */ diff --git a/src/irmd/reg/prog.c b/src/irmd/reg/prog.c new file mode 100644 index 00000000..9b9e7510 --- /dev/null +++ b/src/irmd/reg/prog.c @@ -0,0 +1,174 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * The IPC Resource Manager - Registry - Programs + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#define _POSIX_C_SOURCE 200809L + +#define OUROBOROS_PREFIX "reg/prog" + +#include <ouroboros/logs.h> +#include <ouroboros/utils.h> + +#include "prog.h" + +#include <assert.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +struct name_entry { + struct list_head next; + char * name; +}; + +static void __free_name_entry(struct name_entry * entry) +{ + assert(entry != NULL); + assert(entry->name != NULL); + + free(entry->name); + free(entry); +} + +static void __reg_prog_clear_names(struct reg_prog * prog) +{ + struct list_head * p; + struct list_head * h; + + assert(prog != NULL); + + list_for_each_safe(p, h, &prog->names) { + struct name_entry * entry; + entry = list_entry(p, struct name_entry, next); + list_del(&entry->next); + __free_name_entry(entry); + prog->n_names--; + } +} + +struct reg_prog * reg_prog_create(const struct prog_info * info) +{ + struct reg_prog * p; + + assert(info != NULL); + + p = malloc(sizeof(*p)); + if (p == NULL) { + log_err("Failed to malloc prog."); + goto fail_malloc; + } + + list_head_init(&p->next); + list_head_init(&p->names); + + p->info = *info; + p->n_names = 0; + + return p; + + fail_malloc: + return NULL; +} + +void reg_prog_destroy(struct reg_prog * prog) +{ + assert(prog != NULL); + + __reg_prog_clear_names(prog); + + assert(list_is_empty(&prog->next)); + + assert(prog->n_names == 0); + + assert(list_is_empty(&prog->names)); + + free(prog); +} + +static struct name_entry * __reg_prog_get_name(const struct reg_prog * prog, + const char * name) +{ + struct list_head * p; + + list_for_each(p, &prog->names) { + struct name_entry * entry; + entry = list_entry(p, struct name_entry, next); + if (strcmp(entry->name, name) == 0) + return entry; + } + + return NULL; +} + +int reg_prog_add_name(struct reg_prog * prog, + const char * name) +{ + struct name_entry * entry; + + assert(__reg_prog_get_name(prog, name) == NULL); + + entry = malloc(sizeof(*entry)); + if (entry == NULL) { + log_err("Failed to malloc name."); + goto fail_malloc; + } + + entry->name = strdup(name); + if (entry == NULL) { + log_err("Failed to strdup name."); + goto fail_name; + } + + list_add(&entry->next, &prog->names); + + prog->n_names++; + + return 0; + + fail_name: + free(entry); + fail_malloc: + return -1; +} + +void reg_prog_del_name(struct reg_prog * prog, + const char * name) +{ + struct name_entry * entry; + + entry = __reg_prog_get_name(prog, name); + if (entry == NULL) + return; + + list_del(&entry->next); + + __free_name_entry(entry); + + prog->n_names--; + + assert(__reg_prog_get_name(prog, name) == NULL); +} + +bool reg_prog_has_name(const struct reg_prog * prog, + const char * name) +{ + return __reg_prog_get_name(prog, name) != NULL; +} diff --git a/src/irmd/reg/prog.h b/src/irmd/reg/prog.h new file mode 100644 index 00000000..a98fc6a1 --- /dev/null +++ b/src/irmd/reg/prog.h @@ -0,0 +1,53 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * The IPC Resource Manager - Registry - Programs + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#ifndef OUROBOROS_IRMD_REG_PROG_H +#define OUROBOROS_IRMD_REG_PROG_H + +#include <ouroboros/list.h> +#include <ouroboros/proc.h> + +#include <stdint.h> + +struct reg_prog { + struct list_head next; + + struct prog_info info; + + struct list_head names; /* names to listen for */ + size_t n_names; /* number of names in list */ + }; + +struct reg_prog * reg_prog_create(const struct prog_info * info); + +void reg_prog_destroy(struct reg_prog * prog); + +int reg_prog_add_name(struct reg_prog * prog, + const char * name); + +void reg_prog_del_name(struct reg_prog * prog, + const char * name); + +bool reg_prog_has_name(const struct reg_prog * prog, + const char * name); + +#endif /* OUROBOROS_IRMD_REG_PROG_H */ diff --git a/src/irmd/reg/reg.c b/src/irmd/reg/reg.c new file mode 100644 index 00000000..e89b492b --- /dev/null +++ b/src/irmd/reg/reg.c @@ -0,0 +1,2242 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * +The IPC Resource Manager - Registry + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#define _POSIX_C_SOURCE 200809L + +#define OUROBOROS_PREFIX "reg" + +#include <ouroboros/bitmap.h> +#include <ouroboros/errno.h> +#include <ouroboros/list.h> +#include <ouroboros/logs.h> +#include <ouroboros/protobuf.h> +#include <ouroboros/pthread.h> + +#include "reg.h" +#include "flow.h" +#include "ipcp.h" +#include "name.h" +#include "pool.h" +#include "proc.h" +#include "prog.h" + +#include <assert.h> +#include <signal.h> +#include <stdlib.h> +#include <string.h> + +#define ID_OFFT 1 /* reserve some flow_ids */ + +struct { + struct bmp * flow_ids; /* flow_ids for flows */ + + struct list_head flows; /* flow information */ + size_t n_flows; /* number of flows */ + + struct list_head ipcps; /* list of ipcps in system */ + size_t n_ipcps; /* number of ipcps */ + + struct list_head names; /* registered names known */ + size_t n_names; /* number of names */ + + struct list_head pools; /* per-user pools */ + size_t n_pools; /* number of pools */ + + struct list_head procs; /* processes */ + size_t n_procs; /* number of processes */ + + struct list_head progs; /* programs known */ + size_t n_progs; /* number of programs */ + + struct list_head spawned; /* child processes */ + size_t n_spawned; /* number of child processes */ + + pthread_mutex_t mtx; /* registry lock */ + pthread_cond_t cond; /* condvar for reg changes */ +} reg; + +struct pid_entry { + struct list_head next; + pid_t pid; +}; + +static struct reg_flow * __reg_get_flow(int flow_id) +{ + struct list_head * p; + + assert(flow_id >= ID_OFFT); + + list_for_each(p, ®.flows) { + struct reg_flow * entry; + entry = list_entry(p, struct reg_flow, next); + if (entry->info.id == flow_id) + return entry; + } + + return NULL; +} + +static struct reg_flow * __reg_get_accept_flow(pid_t pid) +{ + struct list_head * p; + + list_for_each(p, ®.flows) { + struct reg_flow * entry; + entry = list_entry(p, struct reg_flow, next); + if (entry->info.state != FLOW_ACCEPT_PENDING) + continue; + if (entry->info.n_pid == pid) + return entry; + } + + return NULL; +} + +static struct list_head * __reg_after_flow(int flow_id) +{ + struct list_head * p; + + assert(flow_id >= ID_OFFT); + + list_for_each(p, ®.flows) { + struct reg_flow * entry; + entry = list_entry(p, struct reg_flow, next); + if (entry->info.id > flow_id) + break; + } + + return p; +} + +static struct reg_ipcp * __reg_get_ipcp(pid_t pid) +{ + struct list_head * p; + + assert(pid > 0); + + list_for_each(p, ®.ipcps) { + struct reg_ipcp * entry; + entry = list_entry(p, struct reg_ipcp, next); + if (entry->info.pid == pid) + return entry; + } + + return NULL; +} + +static struct reg_ipcp * __reg_get_ipcp_by_layer(const char * layer) +{ + struct list_head * p; + + list_for_each(p, ®.ipcps) { + struct reg_ipcp * entry; + entry = list_entry(p, struct reg_ipcp, next); + if (strcmp(entry->layer.name, layer) == 0) + return entry; + } + + return NULL; +} + + +static struct list_head * __reg_after_ipcp(const struct ipcp_info * info) +{ + struct list_head * p; + + assert(info != NULL); + + list_for_each(p, ®.ipcps) { + struct reg_ipcp * entry; + entry = list_entry(p, struct reg_ipcp, next); + if (entry->info.type < info->type) + continue; + + if (entry->info.type > info->type) + break; + + if (entry->info.pid > info->pid) + break; + } + + return p; +} + +static struct reg_name * __reg_get_name(const char * name) +{ + struct list_head * p; + + assert(name != NULL); + + list_for_each(p, ®.names) { + struct reg_name * entry; + entry = list_entry(p, struct reg_name, next); + if (strcmp(entry->info.name, name) == 0) + return entry; + } + + return NULL; +} + +static int __reg_get_pending_flow_id(const char * name) +{ + struct reg_name * entry; + struct reg_flow * flow; + pid_t pid; + + assert(name != NULL); + assert(strlen(name) > 0); + assert(strlen(name) < NAME_SIZE + 1); + + entry =__reg_get_name(name); + if (entry == NULL) + return -ENAME; + + pid = reg_name_get_active(entry); + if (pid < 0) + return -EAGAIN; + + flow = __reg_get_accept_flow(pid); + if (flow == NULL) /* compiler barks, this can't be NULL */ + return -EAGAIN; + + strcpy(flow->name, name); + + return flow->info.id; +} + +static struct list_head * __reg_after_name(const char * name) +{ + struct list_head * p; + + assert(name != NULL); + + list_for_each(p, ®.names) { + struct reg_name * entry; + entry = list_entry(p, struct reg_name, next); + if (strcmp(entry->info.name, name) > 0) + break; + } + + return p; +} + +static struct reg_pool * __reg_get_pool(uid_t uid) +{ + struct list_head * p; + + list_for_each(p, ®.pools) { + struct reg_pool * entry; + entry = list_entry(p, struct reg_pool, next); + if (entry->uid == uid) + return entry; + } + + return NULL; +} + +static struct reg_proc * __reg_get_proc(pid_t pid) +{ + struct list_head * p; + + list_for_each(p, ®.procs) { + struct reg_proc * entry; + entry = list_entry(p, struct reg_proc, next); + if (entry->info.pid == pid) + return entry; + } + + return NULL; +} + +static struct list_head * __reg_after_proc(pid_t pid) +{ + struct list_head * p; + + list_for_each(p, ®.procs) { + struct reg_proc * entry; + entry = list_entry(p, struct reg_proc, next); + if (entry->info.pid > pid) + break; + } + + return p; +} + +static void __reg_kill_all_proc(int signal) +{ + struct list_head * p; + + list_for_each(p, ®.procs) { + struct reg_proc * entry; + entry = list_entry(p, struct reg_proc, next); + kill(entry->info.pid, signal); + } +} + +static pid_t __reg_get_dead_proc(void) +{ + struct list_head * p; + + list_for_each(p, ®.procs) { + struct reg_proc * entry; + entry = list_entry(p, struct reg_proc, next); + if (kill(entry->info.pid, 0) < 0) + return entry->info.pid; + } + + return -1; +} + +static void __reg_cancel_flows_for_proc(pid_t pid) +{ + struct list_head * p; + bool changed = false; + + list_for_each(p, ®.flows) { + struct reg_flow * entry; + entry = list_entry(p, struct reg_flow, next); + if (entry->info.n_pid != pid) + continue; + + switch (entry->info.state) { + case FLOW_ALLOC_PENDING: + /* FALLTHRU */ + case FLOW_ACCEPT_PENDING: + entry->info.state = FLOW_DEALLOCATED; + changed = true; + break; + default: + continue; + } + } + + if (changed) + pthread_cond_broadcast(®.cond); +} + +static struct pid_entry * __reg_get_spawned(pid_t pid) +{ + struct list_head * p; + + list_for_each(p, ®.spawned) { + struct pid_entry * entry; + entry = list_entry(p, struct pid_entry, next); + if (entry->pid == pid) + return entry; + } + + return NULL; +} + +static struct list_head * __reg_after_spawned(pid_t pid) +{ + struct list_head * p; + + list_for_each(p, ®.spawned) { + struct pid_entry * entry; + entry = list_entry(p, struct pid_entry, next); + if (entry->pid > pid) + break; + } + + return p; +} + +static void __reg_kill_all_spawned(int signal) +{ + struct list_head * p; + + list_for_each(p, ®.spawned) { + struct pid_entry * entry; + entry = list_entry(p, struct pid_entry, next); + kill(entry->pid, signal); + } +} + +static pid_t __reg_first_spawned(void) +{ + if (list_is_empty(®.spawned)) + return -1; + + return list_first_entry(®.spawned, struct pid_entry, next)->pid; +} + +static struct reg_prog * __reg_get_prog(const char * name) +{ + struct list_head * p; + + list_for_each(p, ®.progs) { + struct reg_prog * entry; + entry = list_entry(p, struct reg_prog, next); + if (strcmp(entry->info.name, name) == 0) + return entry; + } + + return NULL; +} + +static char ** __reg_get_exec(const char * name) +{ + struct list_head * p; + + list_for_each(p, ®.names) { + struct reg_name * entry; + entry = list_entry(p, struct reg_name, next); + if (strcmp(entry->info.name, name) == 0) + return reg_name_get_exec(entry); + } + + return NULL; +} + +static struct list_head * __reg_after_prog(const char * name) +{ + struct list_head * p; + + list_for_each(p, ®.progs) { + struct reg_prog * entry; + entry = list_entry(p, struct reg_prog, next); + if (strcmp(entry->info.name, name) > 0) + break; + } + + return p; +} + +static void __reg_del_name_from_procs(const char * name) +{ + struct list_head * p; + + list_for_each(p, ®.procs) { + struct reg_proc * proc; + proc = list_entry(p, struct reg_proc, next); + reg_proc_del_name(proc, name); + } +} + +static void __reg_del_name_from_progs(const char * name) +{ + struct list_head * p; + + list_for_each(p, ®.progs) { + struct reg_prog * prog; + prog = list_entry(p, struct reg_prog, next); + reg_prog_del_name(prog, name); + } +} + +static void __reg_proc_update_names(struct reg_proc * proc) +{ + struct list_head * p; + struct reg_prog * prog; + + assert(list_is_empty(&proc->names)); + + prog = __reg_get_prog(proc->info.prog); + if (prog == NULL) + return; + + list_for_each(p, ®.names) { + struct reg_name * name; + name = list_entry(p, struct reg_name, next); + assert(!reg_name_has_proc(name, proc->info.pid)); + if (reg_prog_has_name(prog, name->info.name)) { + reg_proc_add_name(proc, name->info.name); + reg_name_add_proc(name, proc->info.pid); + } + } +} + +static void __reg_del_proc_from_names(pid_t pid) +{ + struct list_head * p; + + list_for_each(p, ®.names) { + struct reg_name * name; + name = list_entry(p, struct reg_name, next); + reg_name_del_proc(name, pid); + } +} + +static void __reg_del_prog_from_names(const char * prog) +{ + struct list_head * p; + + list_for_each(p, ®.names) { + struct reg_name * name; + name = list_entry(p, struct reg_name, next); + reg_name_del_prog(name, prog); + } +} + +static int __reg_add_active_proc(pid_t pid) +{ + struct list_head * p; + size_t n_names = 0; + size_t failed = 0; + + assert(pid > 0); + + list_for_each(p, ®.names) { + struct reg_name * name; + name = list_entry(p, struct reg_name, next); + if (reg_name_has_proc(name, pid)) { + if (reg_name_add_active(name, pid) < 0) + failed++; + n_names++; + } + } + + if (n_names > 0 && failed == n_names) + return -1; + + return 0; /* some were marked */ +} + +static void __reg_del_active_proc(pid_t pid) +{ + struct list_head * p; + + assert(pid > 0); + + list_for_each(p, ®.names) { + struct reg_name * name; + name = list_entry(p, struct reg_name, next); + reg_name_del_active(name, pid); + } +} + +int reg_init(void) +{ + pthread_condattr_t cattr; + + if (pthread_mutex_init(®.mtx, NULL) != 0) { + log_err("Failed to initialize mutex."); + goto fail_mtx; + } + + if (pthread_condattr_init(&cattr) != 0) { + log_err("Failed to initialize condattr."); + goto fail_cattr; + } + +#ifndef __APPLE__ + pthread_condattr_setclock(&cattr, PTHREAD_COND_CLOCK); +#endif + if (pthread_cond_init(®.cond, &cattr) != 0) { + log_err("Failed to initialize condvar."); + goto fail_cond; + } + + reg.flow_ids = bmp_create(SYS_MAX_FLOWS -ID_OFFT, ID_OFFT); + if (reg.flow_ids == NULL) { + log_err("Failed to create flow_ids bitmap."); + goto fail_flow_ids; + } + + pthread_condattr_destroy(&cattr); + + list_head_init(®.flows); + list_head_init(®.ipcps); + list_head_init(®.names); + list_head_init(®.pools); + list_head_init(®.procs); + list_head_init(®.progs); + list_head_init(®.spawned); + + return 0; + + fail_flow_ids: + pthread_cond_destroy(®.cond); + fail_cond: + pthread_condattr_destroy(&cattr); + fail_cattr: + pthread_mutex_destroy(®.mtx); + fail_mtx: + return -1; +} + +void reg_clear(void) +{ + struct list_head * p; + struct list_head * h; + + pthread_mutex_lock(®.mtx); + + list_for_each_safe(p, h, ®.spawned) { + struct pid_entry * entry; + entry = list_entry(p, struct pid_entry, next); + list_del(&entry->next); + free(entry); + reg.n_spawned--; + } + + list_for_each_safe(p, h, ®.progs) { + struct reg_prog * entry; + entry = list_entry(p, struct reg_prog, next); + list_del(&entry->next); + __reg_del_prog_from_names(entry->info.path); + reg_prog_destroy(entry); + reg.n_progs--; + } + + list_for_each_safe(p, h, ®.procs) { + struct reg_proc * entry; + entry = list_entry(p, struct reg_proc, next); + list_del(&entry->next); + __reg_del_proc_from_names(entry->info.pid); + reg_proc_destroy(entry); + reg.n_procs--; + } + + list_for_each_safe(p, h, ®.pools) { + struct reg_pool * entry; + entry = list_entry(p, struct reg_pool, next); + list_del(&entry->next); + entry->refcount = 0; /* Force destroy during cleanup */ + reg_pool_destroy(entry); + reg.n_pools--; + } + + list_for_each_safe(p, h, ®.flows) { + struct reg_flow * entry; + entry = list_entry(p, struct reg_flow, next); + list_del(&entry->next); + reg_flow_destroy(entry); + reg.n_flows--; + } + + list_for_each_safe(p, h, ®.names) { + struct reg_name * entry; + entry = list_entry(p, struct reg_name, next); + list_del(&entry->next); + reg_name_destroy(entry); + reg.n_names--; + } + + list_for_each_safe(p, h, ®.ipcps) { + struct reg_ipcp * entry; + entry = list_entry(p, struct reg_ipcp, next); + list_del(&entry->next); + reg_ipcp_destroy(entry); + reg.n_ipcps--; + } + + pthread_mutex_unlock(®.mtx); +} + +void reg_fini(void) +{ + assert(list_is_empty(®.spawned)); + assert(list_is_empty(®.progs)); + assert(list_is_empty(®.procs)); + assert(list_is_empty(®.pools)); + assert(list_is_empty(®.names)); + assert(list_is_empty(®.ipcps)); + assert(list_is_empty(®.flows)); + + assert(reg.n_spawned == 0); + assert(reg.n_progs == 0); + assert(reg.n_procs == 0); + assert(reg.n_pools == 0); + assert(reg.n_names == 0); + assert(reg.n_ipcps == 0); + assert(reg.n_flows == 0); + + bmp_destroy(reg.flow_ids); + + if (pthread_cond_destroy(®.cond) != 0) + log_warn("Failed to destroy condvar."); + + if (pthread_mutex_destroy(®.mtx) != 0) + log_warn("Failed to destroy mutex."); +} + +int reg_create_flow(struct flow_info * info) +{ + struct reg_flow * f; + + assert(info != NULL); + assert(info->id == 0); + assert(info->n_pid != 0); + assert(info->state == FLOW_INIT); + + pthread_mutex_lock(®.mtx); + + info->id = bmp_allocate(reg.flow_ids); + if (!bmp_is_id_valid(reg.flow_ids, info->id)) { + log_err("Failed to allocate flow id."); + goto fail_id; + } + + f = reg_flow_create(info); + if (f == NULL) { + log_err("Failed to create flow %d.", info->id); + goto fail_flow; + } + + list_add(&f->next, __reg_after_flow(info->id)); + + reg.n_flows++; + + pthread_mutex_unlock(®.mtx); + + return 0; + + fail_flow: + bmp_release(reg.flow_ids, info->id); + info->id = 0; + fail_id: + pthread_mutex_unlock(®.mtx); + return -1; +} + +int reg_destroy_flow(int flow_id) +{ + struct reg_flow * f; + + pthread_mutex_lock(®.mtx); + + f = __reg_get_flow(flow_id); + if (f == NULL) { + log_err("Flow %d does not exist.", flow_id); + goto no_flow; + } + + list_del(&f->next); + + reg.n_flows--; + + bmp_release(reg.flow_ids, flow_id); + + pthread_mutex_unlock(®.mtx); + + pthread_cond_broadcast(®.cond); + + reg_flow_destroy(f); + + return 0; + + no_flow: + pthread_mutex_unlock(®.mtx); + return -1; + +} + +bool reg_has_flow(int flow_id) +{ + bool ret; + + pthread_mutex_lock(®.mtx); + + ret = __reg_get_flow(flow_id) != NULL; + + pthread_mutex_unlock(®.mtx); + + return ret; +} + +int reg_create_ipcp(const struct ipcp_info * info) +{ + struct reg_ipcp * ipcp; + struct pid_entry * entry; + + assert(info != NULL); + assert(info->pid != 0); + assert(info->state == IPCP_INIT); + + pthread_mutex_lock(®.mtx); + + if (__reg_get_ipcp(info->pid) != NULL) { + log_err("IPCP %d already exists.", info->pid); + goto fail_ipcp; + } + + ipcp = reg_ipcp_create(info); + if (ipcp == NULL) { + log_err("Failed to create ipcp %s.", info->name); + goto fail_ipcp; + } + + entry = malloc(sizeof(*entry)); + if (entry == NULL) { + log_err("Failed to create spawn entry.\n"); + goto fail_spawn; + } + + entry->pid = info->pid; + + list_add_tail(&ipcp->next, __reg_after_ipcp(info)); + list_add(&entry->next, __reg_after_spawned(info->pid)); + + reg.n_ipcps++; + reg.n_spawned++; + + pthread_mutex_unlock(®.mtx); + + return 0; + + fail_spawn: + reg_ipcp_destroy(ipcp); + fail_ipcp: + pthread_mutex_unlock(®.mtx); + return -1; + +} + +int reg_update_ipcp(struct ipcp_info * info) +{ + struct reg_ipcp * ipcp; + + pthread_mutex_lock(®.mtx); + + ipcp = __reg_get_ipcp(info->pid); + if (ipcp == NULL) { + log_err("IPCP %d does not exist.", info->pid); + goto no_ipcp; + + } + + reg_ipcp_update(ipcp, info); + + pthread_mutex_unlock(®.mtx); + + reg_ipcp_destroy(ipcp); + + return 0; + + no_ipcp: + pthread_mutex_unlock(®.mtx); + return -1; +} + +bool reg_has_ipcp(pid_t pid) +{ + bool ret; + + pthread_mutex_lock(®.mtx); + + ret = __reg_get_ipcp(pid) != NULL; + + pthread_mutex_unlock(®.mtx); + + return ret; +} + +static int __get_ipcp_info(ipcp_list_msg_t ** msg, + struct reg_ipcp * ipcp) +{ + *msg = malloc(sizeof(**msg)); + if (*msg == NULL) + goto fail; + + ipcp_list_msg__init(*msg); + + (*msg)->name = strdup(ipcp->info.name); + if ((*msg)->name == NULL) + goto fail_msg; + + (*msg)->layer = strdup(ipcp->layer.name); + if ((*msg)->layer == NULL) + goto fail_msg; + + (*msg)->pid = ipcp->info.pid; + (*msg)->type = ipcp->info.type; + (*msg)->hash_algo = ipcp->layer.dir_hash_algo; + + return 0; + + fail_msg: + ipcp_list_msg__free_unpacked(*msg, NULL); + *msg = NULL; + fail: + return -1; +} + +int reg_list_ipcps(ipcp_list_msg_t *** ipcps) +{ + struct list_head * p; + int i = 0; + + pthread_mutex_lock(®.mtx); + + if (reg.n_ipcps == 0) + goto finish; + + *ipcps = malloc(reg.n_ipcps * sizeof(**ipcps)); + if (*ipcps == NULL) { + log_err("Failed to malloc ipcps."); + goto fail_malloc; + } + + list_for_each(p, ®.ipcps) { + struct reg_ipcp * entry; + entry = list_entry(p, struct reg_ipcp, next); + if (__get_ipcp_info(&(*ipcps)[i], entry) < 0) + goto fail; + + i++; + } + finish: + pthread_mutex_unlock(®.mtx); + + return i; + + fail: + while (i-- > 0) + ipcp_list_msg__free_unpacked((*ipcps)[i], NULL); + free(*ipcps); + fail_malloc: + pthread_mutex_unlock(®.mtx); + *ipcps = NULL; + return -ENOMEM; +} + +int reg_create_name(const struct name_info * info) +{ + struct reg_name * n; + + assert(info != NULL); + + pthread_mutex_lock(®.mtx); + + if (__reg_get_name(info->name) != NULL) { + log_dbg("Name %s already exists.", info->name); + goto exists; + } + + n = reg_name_create(info); + if (n == NULL) { + log_err("Failed to create name %s.", info->name); + goto fail_name; + } + + list_add(&n->next, __reg_after_name(info->name)); + + reg.n_names++; + + pthread_mutex_unlock(®.mtx); + return 0; + exists: + pthread_mutex_unlock(®.mtx); + return -EEXIST; + + fail_name: + pthread_mutex_unlock(®.mtx); + return -1; + +} + +int reg_destroy_name(const char * name) +{ + struct reg_name * n; + + pthread_mutex_lock(®.mtx); + + n = __reg_get_name(name); + if (n == NULL) { + log_err("Name %s does not exist.", name); + goto no_name; + } + + __reg_del_name_from_procs(name); + __reg_del_name_from_progs(name); + + list_del(&n->next); + + reg.n_names--; + + pthread_mutex_unlock(®.mtx); + + reg_name_destroy(n); + + return 0; + + no_name: + pthread_mutex_unlock(®.mtx); + return -1; +} + +bool reg_has_name(const char * name) +{ + bool ret; + + pthread_mutex_lock(®.mtx); + + ret = __reg_get_name(name) != NULL; + + pthread_mutex_unlock(®.mtx); + + return ret; +} + +int reg_get_name_info(const char * name, + struct name_info * info) +{ + struct reg_name * n; + + assert(name != NULL); + assert(info != NULL); + + pthread_mutex_lock(®.mtx); + + n = __reg_get_name(name); + if (n == NULL) { + log_err("Name %s does not exist.", name); + goto no_name; + } + + *info = n->info; + + pthread_mutex_unlock(®.mtx); + + return 0; + + no_name: + pthread_mutex_unlock(®.mtx); + return -ENOENT; + +} + +int reg_get_name_for_hash(char * buf, + enum hash_algo algo, + const uint8_t * hash) +{ + struct list_head * p; + uint8_t * thash; + size_t len; + char * name = NULL; + + len = hash_len(algo); + + thash = malloc(len); + if (thash == NULL) + return -ENOMEM; + + pthread_mutex_lock(®.mtx); + + list_for_each(p, ®.names) { + struct reg_name * n = list_entry(p, struct reg_name, next); + str_hash(algo, thash, n->info.name); + if (memcmp(thash, hash, len) == 0) { + name = n->info.name; + break; + } + } + + if (name != NULL) + strcpy(buf, name); + + pthread_mutex_unlock(®.mtx); + + free(thash); + + return name == NULL ? -ENOENT : 0; +} + +int reg_get_name_for_flow_id(char * buf, + int flow_id) +{ + struct reg_flow * f; + + pthread_mutex_lock(®.mtx); + + f = __reg_get_flow(flow_id); + if (f != NULL) + strcpy(buf, f->name); + + pthread_mutex_unlock(®.mtx); + + return f == NULL ? -ENOENT : 0; +} + +int reg_list_names(name_info_msg_t *** names) +{ + struct list_head * p; + int i = 0; + + pthread_mutex_lock(®.mtx); + + if (reg.n_names == 0) + goto finish; + + *names = malloc(reg.n_names * sizeof(**names)); + if (*names == NULL) { + log_err("Failed to malloc names."); + goto fail_malloc; + } + + list_for_each(p, ®.names) { + struct reg_name * entry; + entry = list_entry(p, struct reg_name, next); + (*names)[i] = name_info_s_to_msg(&entry->info); + if ((*names)[i] == NULL) { + log_err("Failed to create name list info."); + goto fail; + } + /* wipe security info to avoid huge messages */ + free((*names)[i]->scrt); + (*names)[i]->scrt = NULL; + free((*names)[i]->skey); + (*names)[i]->skey = NULL; + free((*names)[i]->ccrt); + (*names)[i]->ccrt = NULL; + free((*names)[i]->ckey); + (*names)[i]->ckey = NULL; + + i++; + } + finish: + pthread_mutex_unlock(®.mtx); + + return i; + + fail: + while (i-- > 0) + name_info_msg__free_unpacked((*names)[i], NULL); + free(*names); + fail_malloc: + pthread_mutex_unlock(®.mtx); + *names = NULL; + return -ENOMEM; +} + +int reg_prepare_pool(uid_t uid, + gid_t gid) +{ + struct reg_pool * pool; + + if (is_ouroboros_member_uid(uid)) + return 0; + + pthread_mutex_lock(®.mtx); + + pool = __reg_get_pool(uid); + if (pool == NULL) { + pool = reg_pool_create(uid, gid); + if (pool == NULL) { + log_err("Failed to create pool for uid %d.", uid); + pthread_mutex_unlock(®.mtx); + return -1; + } + list_add(&pool->next, ®.pools); + reg.n_pools++; + } + + reg_pool_ref(pool); + + pthread_mutex_unlock(®.mtx); + + return 0; +} + +int reg_create_proc(const struct proc_info * info) +{ + struct reg_proc * proc; + + assert(info != NULL); + + pthread_mutex_lock(®.mtx); + + if (__reg_get_proc(info->pid) != NULL) { + log_err("Process %d already exists.", info->pid); + goto fail; + } + + proc = reg_proc_create(info); + if (proc == NULL) { + log_err("Failed to create process %d.", info->pid); + goto fail; + } + + __reg_proc_update_names(proc); + + list_add(&proc->next, __reg_after_proc(info->pid)); + + reg.n_procs++; + + pthread_cond_broadcast(®.cond); + + pthread_mutex_unlock(®.mtx); + + return 0; + + fail: + pthread_mutex_unlock(®.mtx); + return -1; +} + +int reg_destroy_proc(pid_t pid) +{ + struct reg_proc * proc; + struct reg_pool * pool = NULL; + struct pid_entry * spawn; + struct reg_ipcp * ipcp; + + pthread_mutex_lock(®.mtx); + + proc = __reg_get_proc(pid); + if (proc != NULL) { + if (!is_ouroboros_member_uid(proc->info.uid)) + pool = __reg_get_pool(proc->info.uid); + list_del(&proc->next); + reg.n_procs--; + reg_proc_destroy(proc); + __reg_del_proc_from_names(pid); + __reg_cancel_flows_for_proc(pid); + if (pool != NULL && reg_pool_unref(pool) == 0) { + list_del(&pool->next); + reg.n_pools--; + reg_pool_destroy(pool); + } + } + + spawn = __reg_get_spawned(pid); + if (spawn != NULL) { + list_del(&spawn->next); + reg.n_spawned--; + free(spawn); + } + + ipcp = __reg_get_ipcp(pid); + if (ipcp != NULL) { + list_del(&ipcp->next); + reg.n_ipcps--; + reg_ipcp_destroy(ipcp); + } + + pthread_mutex_unlock(®.mtx); + + return 0; +} + +bool reg_has_proc(pid_t pid) +{ + bool ret; + + pthread_mutex_lock(®.mtx); + + ret = __reg_get_proc(pid) != NULL; + + pthread_mutex_unlock(®.mtx); + + return ret; +} + +bool reg_is_proc_privileged(pid_t pid) +{ + struct reg_proc * proc; + bool ret = false; + + pthread_mutex_lock(®.mtx); + + proc = __reg_get_proc(pid); + if (proc != NULL) + ret = reg_proc_is_privileged(proc); + + pthread_mutex_unlock(®.mtx); + + return ret; +} + +uid_t reg_get_proc_uid(pid_t pid) +{ + struct reg_proc * proc; + uid_t ret = 0; + + pthread_mutex_lock(®.mtx); + + proc = __reg_get_proc(pid); + if (proc != NULL && !is_ouroboros_member_uid(proc->info.uid)) + ret = proc->info.uid; + + pthread_mutex_unlock(®.mtx); + + return ret; +} + +void reg_kill_all_proc(int signal) +{ + pthread_mutex_lock(®.mtx); + + __reg_kill_all_proc(signal); + + pthread_mutex_unlock(®.mtx); +} + +pid_t reg_get_dead_proc(void) +{ + pid_t ret; + + pthread_mutex_lock(®.mtx); + + ret = __reg_get_dead_proc(); + + pthread_mutex_unlock(®.mtx); + + return ret; +} + +int reg_create_spawned(pid_t pid) +{ + struct pid_entry * entry; + + pthread_mutex_lock(®.mtx); + + if (__reg_get_spawned(pid) != NULL) { + log_err("Spawned process %d already exists.", pid); + goto fail_proc; + } + + entry = malloc(sizeof(*entry)); + if (entry == NULL) { + log_err("Failed to create pid_entry %d.", pid); + goto fail_proc; + } + + entry->pid = pid; + + list_add(&entry->next, __reg_after_spawned(pid)); + + reg.n_spawned++; + + pthread_mutex_unlock(®.mtx); + + return 0; + fail_proc: + pthread_mutex_unlock(®.mtx); + return -1; +} + +bool reg_has_spawned(pid_t pid) +{ + bool ret; + + pthread_mutex_lock(®.mtx); + + ret = __reg_get_spawned(pid) != NULL; + + pthread_mutex_unlock(®.mtx); + + return ret; +} + +void reg_kill_all_spawned(int signal) +{ + pthread_mutex_lock(®.mtx); + + __reg_kill_all_spawned(signal); + + pthread_mutex_unlock(®.mtx); +} + +pid_t reg_first_spawned(void) +{ + pid_t pid; + + pthread_mutex_lock(®.mtx); + + pid = __reg_first_spawned(); + + pthread_mutex_unlock(®.mtx); + + return pid; +} + +int reg_bind_proc(const char * name, + pid_t pid) +{ + struct reg_name * n; + struct reg_proc * p; + + assert(name != NULL); + assert(pid > 0); + + pthread_mutex_lock(®.mtx); + + n = __reg_get_name(name); + if (n == NULL) { + log_err("Could not find name %s.", name); + goto fail; + } + + p = __reg_get_proc(pid); + if (p == NULL) { + log_err("Could not find process %d.", pid); + goto fail; + } + + if (reg_name_has_proc(n, pid)) { + log_err("Process %d already bound to name %s.", pid, name); + goto fail; + } + + if (reg_proc_has_name(p, name)) { + log_err("Name %s already bound to process %d.", name, pid); + } + + if (reg_name_add_proc(n, pid) < 0) { + log_err("Failed to add process %d to name %s.", pid, name); + goto fail; + } + + if (reg_proc_add_name(p, name) < 0) { + log_err("Failed to add name %s to process %d.", name, pid); + goto fail_proc; + } + + if (__reg_get_accept_flow(pid) != NULL) { + if (reg_name_add_active(n, pid) < 0) { + log_warn("Failed to update name %s with active %d", + name, pid); + } + } + + pthread_mutex_unlock(®.mtx); + + return 0; + + fail_proc: + reg_name_del_proc(n, pid); + fail: + pthread_mutex_unlock(®.mtx); + return -1; +} + +int reg_unbind_proc(const char * name, + pid_t pid) +{ + struct reg_name * n; + struct reg_proc * p; + + assert(name != NULL); + assert(pid > 0); + + pthread_mutex_lock(®.mtx); + + n = __reg_get_name(name); + if (n == NULL) { + log_err("Could not find name %s.", name); + goto fail; + } + + p = __reg_get_proc(pid); + if (p == NULL) { + log_err("Could not find process %d.", pid); + goto fail; + } + + if (!reg_name_has_proc(n, pid)) { + log_err("Process %d not bound to name %s.", pid, name); + goto fail; + } + + if (!reg_proc_has_name(p, name)) { + log_err("Name %s not bound to process %d.", name, pid); + goto fail; + } + + reg_name_del_proc(n, pid); + + reg_proc_del_name(p, name); + + pthread_mutex_unlock(®.mtx); + + return 0; + + fail: + pthread_mutex_unlock(®.mtx); + return -1; +} + +int reg_create_prog(const struct prog_info * info) +{ + struct reg_prog * prog; + + assert(info != NULL); + + pthread_mutex_lock(®.mtx); + + if (__reg_get_prog(info->name) != NULL) { + log_dbg("Program %s already exists.", info->name); + goto exists; + } + + prog = reg_prog_create(info); + if (prog == NULL) { + log_err("Failed to create program %s.", info->name); + goto fail_prog; + } + + list_add(&prog->next, __reg_after_prog(info->name)); + + reg.n_progs++; + exists: + pthread_mutex_unlock(®.mtx); + + return 0; + + fail_prog: + pthread_mutex_unlock(®.mtx); + return -1; + +} + +int reg_destroy_prog(const char * name) +{ + struct reg_prog * prog; + + pthread_mutex_lock(®.mtx); + + prog = __reg_get_prog(name); + if (prog == NULL) { + log_err("Program %s does not exist.", name); + goto no_prog; + } + + log_err("Removing %s from names.", prog->info.path); + + __reg_del_prog_from_names(prog->info.path); + + list_del(&prog->next); + + reg.n_progs--; + + pthread_mutex_unlock(®.mtx); + + reg_prog_destroy(prog); + + return 0; + + no_prog: + pthread_mutex_unlock(®.mtx); + return -1; +} + +bool reg_has_prog(const char * name) +{ + bool ret; + + assert(name != NULL); + + pthread_mutex_lock(®.mtx); + + ret = __reg_get_prog(name) != NULL; + + pthread_mutex_unlock(®.mtx); + + return ret; +} + +int reg_get_exec(const char * name, + char *** prog) +{ + char ** exec; + int ret = 0; + + assert(name != NULL); + assert(prog != NULL); + + pthread_mutex_lock(®.mtx); + + exec = __reg_get_exec(name); + if (exec == NULL) { + ret = -EPERM; + goto finish; + } + + *prog = argvdup(exec); + if (*prog == NULL) { + log_err("Failed to argvdup exec."); + ret = -ENOMEM; + goto finish; + } + + finish: + pthread_mutex_unlock(®.mtx); + + return ret; +} + +int reg_bind_prog(const char * name, + char ** exec, + uint8_t flags) +{ + struct reg_name * n; + struct reg_prog * p; + + assert(name != NULL); + assert(exec != NULL); + assert(exec[0] != NULL); + + pthread_mutex_lock(®.mtx); + + n = __reg_get_name(name); + if (n == NULL) { + log_err("Could not find name %s.", name); + goto fail; + } + + p = __reg_get_prog(path_strip(exec[0])); + if (p == NULL) { + log_err("Could not find program %s.", exec[0]); + goto fail; + } + + if (reg_name_has_prog(n, exec[0])) { + log_err("Program %s already bound to %s.", exec[0], name); + goto fail; + } + + if (reg_prog_has_name(p, name)) { + log_err("Name %s already bound to program %s.", name, exec[0]); + goto fail; + } + + + if (flags & BIND_AUTO && reg_name_add_prog(n, exec) < 0) { + log_err("Failed to set autostart %s for %s.", exec[0], name); + goto fail; + } + + if (reg_prog_add_name(p, name) < 0) { + log_err("Failed to add %s to program %s.", name, exec[0]); + goto fail_prog; + } + + pthread_mutex_unlock(®.mtx); + + return 0; + + fail_prog: + reg_name_del_prog(n, exec[0]); + fail: + pthread_mutex_unlock(®.mtx); + return -1; +} + +int reg_unbind_prog(const char * name, + const char * prog) +{ + struct reg_name * n; + struct reg_prog * p; + + assert(name != NULL); + assert(prog != NULL); + + pthread_mutex_lock(®.mtx); + + n = __reg_get_name(name); + if (n == NULL) { + log_err("Could not find name %s.", name); + goto fail; + } + + p = __reg_get_prog(prog); + if (p == NULL) { + log_err("Could not find program %s.", prog); + goto fail; + } + + if (!reg_prog_has_name(p, name)) { + log_err("Name %s not bound to program %s.", name, prog); + goto fail; + } + + reg_name_del_prog(n, prog); + + reg_prog_del_name(p, name); + + pthread_mutex_unlock(®.mtx); + + return 0; + + fail: + pthread_mutex_unlock(®.mtx); + return -1; +} + +int reg_set_layer_for_ipcp(struct ipcp_info * info, + const struct layer_info * layer) +{ + struct reg_ipcp * ipcp; + + assert(info != NULL); + assert(info->state == IPCP_BOOT); + + pthread_mutex_lock(®.mtx); + + ipcp = __reg_get_ipcp(info->pid); + if (ipcp == NULL) { + log_err("IPCP %d not found.", info->pid); + goto fail_ipcp; + } + + reg_ipcp_set_layer(ipcp, layer); + + ipcp->info.state = info->state; + + pthread_mutex_unlock(®.mtx); + + return 0; + fail_ipcp: + pthread_mutex_unlock(®.mtx); + return -1; +} + +int reg_get_ipcp(struct ipcp_info * info, + struct layer_info * layer) +{ + struct reg_ipcp * ipcp; + + assert(info != NULL); + + pthread_mutex_lock(®.mtx); + + ipcp = __reg_get_ipcp(info->pid); + if (ipcp == NULL) { + log_err("IPCP %d not found.", info->pid); + goto fail_ipcp; + } + + *info = ipcp->info; + if (layer != NULL) + *layer = ipcp->layer; + + pthread_mutex_unlock(®.mtx); + + return 0; + fail_ipcp: + pthread_mutex_unlock(®.mtx); + return -1; +} + +int reg_get_ipcp_by_layer(struct ipcp_info * info, + struct layer_info * layer) +{ + struct reg_ipcp * ipcp; + + assert(info != NULL); + assert(layer != NULL); + + pthread_mutex_lock(®.mtx); + + ipcp = __reg_get_ipcp_by_layer(layer->name); + if (ipcp == NULL) { + log_err("No IPCP for %s not found.", layer->name); + goto fail_ipcp; + } + + *info = ipcp->info; + *layer = ipcp->layer; + + pthread_mutex_unlock(®.mtx); + + return 0; + fail_ipcp: + pthread_mutex_unlock(®.mtx); + return -1; +} + +int reg_prepare_flow_alloc(struct flow_info * info) +{ + struct reg_flow * flow; + int ret; + + assert(info != NULL); + + pthread_mutex_lock(®.mtx); + + flow = __reg_get_flow(info->id); + + assert(flow != NULL); + assert(flow->info.state == FLOW_INIT); + + info->state = FLOW_ALLOC_PENDING; + + ret = reg_flow_update(flow, info); + + pthread_mutex_unlock(®.mtx); + + return ret; +} + +int reg_wait_flow_allocated(struct flow_info * info, + buffer_t * pbuf, + const struct timespec * abstime) +{ + struct reg_flow * flow; + int ret = -1; + bool stop = false; + + assert(info != NULL); + assert(info->id >= ID_OFFT); + + pthread_mutex_lock(®.mtx); + + flow = __reg_get_flow(info->id); + + assert(flow != NULL); + assert(info->id == flow->info.id); + assert(info->n_pid == flow->info.n_pid); + + assert(info->state == FLOW_ALLOC_PENDING); + + pthread_cleanup_push(__cleanup_mutex_unlock, ®.mtx); + + while (!stop) { + switch(flow->info.state) { + case FLOW_ALLOC_PENDING: + ret = -__timedwait(®.cond, ®.mtx, abstime); + break; + case FLOW_ALLOCATED: + ret = 0; + stop = true; + break; + case FLOW_DEALLOCATED: + ret = flow->response; + stop = true; + break; + default: + assert(false); + } + + flow = __reg_get_flow(flow->info.id); + if (flow == NULL) { + info->state = FLOW_DEALLOCATED; + ret = -1; + break; + } + + if (ret == -ETIMEDOUT) { + info->state = FLOW_DEALLOCATED; + reg_flow_update(flow, info); + break; + } + } + + if (flow != NULL) { + reg_flow_get_data(flow, pbuf); + *info = flow->info; + } + + pthread_cleanup_pop(true); /* __cleanup_mutex_unlock */ + + return ret; +} + +int reg_respond_alloc(struct flow_info * info, + buffer_t * pbuf, + int response) +{ + struct reg_flow * flow; + + assert(info != NULL); + assert(info->state == FLOW_ALLOCATED || + info->state == FLOW_DEALLOCATED); + assert(pbuf != NULL); + assert(!(info->state == FLOW_DEALLOCATED && pbuf->data != NULL)); + + pthread_mutex_lock(®.mtx); + + flow = __reg_get_flow(info->id); + if (flow == NULL) { + log_warn("Flow %d already destroyed.", info->id); + goto fail_flow; + } + + if (flow->info.state == FLOW_DEALLOCATED) { + log_warn("Flow %d already deallocated.", info->id); + goto fail_flow; + } + + assert(flow->info.state == FLOW_ALLOC_PENDING); + assert(flow->data.len == 0); + assert(flow->data.data == NULL); + + info->n_pid = flow->info.n_pid; + info->n_1_pid = flow->info.n_pid; + + if (reg_flow_update(flow, info) < 0) { + log_err("Failed to create flow structs."); + goto fail_flow; + } + + flow->response = response; + + if (info->state == FLOW_ALLOCATED) + reg_flow_set_data(flow, pbuf); + + pthread_cond_broadcast(®.cond); + + pthread_mutex_unlock(®.mtx); + + return 0; + + fail_flow: + pthread_mutex_unlock(®.mtx); + return -1; +} + +int reg_prepare_flow_accept(struct flow_info * info) +{ + struct reg_flow * flow; + int ret; + + assert(info != NULL); + + pthread_mutex_lock(®.mtx); + + flow = __reg_get_flow(info->id); + + assert(flow != NULL); + assert(info->n_pid != 0); + + info->state = FLOW_ACCEPT_PENDING; + + ret = reg_flow_update(flow, info); + + pthread_cond_broadcast(®.cond); + + pthread_mutex_unlock(®.mtx); + + return ret; +} + +void __cleanup_wait_accept(void * o) +{ + struct reg_flow * flow; + + flow = (struct reg_flow *) o; + + __reg_del_active_proc(flow->info.n_pid); +} + +int reg_wait_flow_accepted(struct flow_info * info, + buffer_t * pbuf, + const struct timespec * abstime) +{ + struct reg_flow * flow; + int ret = -1; + bool stop = false; + + assert(info != NULL); + assert(info->id >= ID_OFFT); + + pthread_mutex_lock(®.mtx); + + flow = __reg_get_flow(info->id); + + assert(flow != NULL); + assert(info->id == flow->info.id); + assert(info->n_pid == flow->info.n_pid); + + if (__reg_add_active_proc(info->n_pid) < 0) { + log_err("Failed to mark pid %d active.", info->n_pid); + goto fail; + } + + pthread_cond_broadcast(®.cond); + + pthread_cleanup_push(__cleanup_mutex_unlock, ®.mtx); + pthread_cleanup_push(__cleanup_wait_accept, flow); + + while (!stop) { + switch(flow->info.state) { + case FLOW_ACCEPT_PENDING: + ret = -__timedwait(®.cond, ®.mtx, abstime); + break; + case FLOW_ALLOCATED: + ret = 0; + stop = true; + break; + case FLOW_DEALLOCATED: + ret = -1; + stop = true; + break; + default: + assert(false); + } + + flow = __reg_get_flow(flow->info.id); + if (flow == NULL) { + info->state = FLOW_DEALLOCATED; + ret = -1; + break; + } + + if (ret == -ETIMEDOUT) { + info->state = FLOW_DEALLOCATED; + reg_flow_update(flow, info); + break; + } + } + + pthread_cleanup_pop(true); /* __cleanup_wait_accept */ + + if (flow != NULL) { + reg_flow_get_data(flow, pbuf); + *info = flow->info; + } + + pthread_cleanup_pop(true); /* __cleanup_mutex_unlock */ + + return ret; + fail: + pthread_mutex_unlock(®.mtx); + return -1; +} + +int reg_wait_flow_accepting(const char * name, + const struct timespec * abstime) +{ + int ret; + + assert(name != NULL); + assert(abstime != NULL); + + pthread_mutex_lock(®.mtx); + + pthread_cleanup_push(__cleanup_mutex_unlock, ®.mtx); + + while (true) { + ret = __reg_get_pending_flow_id(name); + if (ret != -EAGAIN) + break; + + ret = -__timedwait(®.cond, ®.mtx, abstime); + if (ret == -ETIMEDOUT) + break; + } + + pthread_cleanup_pop(true); + + return ret; +} + +int reg_respond_accept(struct flow_info * info, + buffer_t * pbuf) +{ + struct reg_flow * flow; + + assert(info != NULL); + assert(info->state == FLOW_ALLOCATED); + assert(pbuf != NULL); + + pthread_mutex_lock(®.mtx); + + flow = __reg_get_flow(info->id); + if (flow == NULL) { + log_err("Flow not found for request: %d", info->id); + goto fail_flow; + } + + assert(flow->info.state == FLOW_ACCEPT_PENDING); + + info->n_pid = flow->info.n_pid; + + reg_flow_set_data(flow, pbuf); + clrbuf(pbuf); + + if (reg_flow_update(flow, info) < 0) { + log_err("Failed to create flow structs."); + goto fail_flow; + } + + pthread_cond_broadcast(®.cond); + + pthread_mutex_unlock(®.mtx); + + return 0; + + fail_flow: + pthread_mutex_unlock(®.mtx); + return -1; +} + +void reg_dealloc_flow(struct flow_info * info) +{ + struct reg_flow * flow; + + assert(info != NULL); + assert(info->id != 0); + assert(info->n_pid != 0); + + pthread_mutex_lock(®.mtx); + + flow = __reg_get_flow(info->id); + + assert(flow != NULL); + assert(flow->data.data == NULL); + assert(flow->data.len == 0); + assert(flow->info.state == FLOW_ALLOCATED); + + flow->info.state = FLOW_DEALLOC_PENDING; + info->state = FLOW_DEALLOC_PENDING; + info->n_1_pid = flow->info.n_1_pid; + + memset(flow->name, 0, sizeof(flow->name)); + + reg_flow_update(flow, info); + + pthread_mutex_unlock(®.mtx); +} + +void reg_dealloc_flow_resp(struct flow_info * info) +{ + struct reg_flow * flow; + + assert(info != NULL); + assert(info->id != 0); + assert(info->n_1_pid != 0); + + pthread_mutex_lock(®.mtx); + + flow = __reg_get_flow(info->id); + + assert(flow != NULL); + assert(flow->data.data == NULL); + assert(flow->data.len == 0); + + assert(flow->info.state == FLOW_DEALLOC_PENDING); + flow->info.state = FLOW_DEALLOCATED; + info->state = FLOW_DEALLOCATED; + + reg_flow_update(flow, info); + + pthread_mutex_unlock(®.mtx); +} + +int reg_wait_proc(pid_t pid, + const struct timespec * abstime) +{ + struct reg_proc * proc = NULL; + int ret; + + assert(pid > 0); + assert(abstime != NULL); + + pthread_mutex_lock(®.mtx); + + pthread_cleanup_push(__cleanup_mutex_unlock, ®.mtx); + + while (true) { + proc = __reg_get_proc(pid); + if (proc != NULL) { + ret = 0; + break; + } + + ret = -__timedwait(®.cond, ®.mtx, abstime); + if (ret == -ETIMEDOUT) + break; + } + + pthread_cleanup_pop(true); + + return ret; +} + +int reg_wait_ipcp_boot(struct ipcp_info * info, + const struct timespec * abstime) +{ + struct reg_ipcp * ipcp; + int ret; + bool stop = false; + + assert(info->state == IPCP_INIT); + + pthread_mutex_lock(®.mtx); + + ipcp = __reg_get_ipcp(info->pid); + + if (ipcp->info.state == IPCP_INIT) + reg_ipcp_update(ipcp, info); + + pthread_cleanup_push(__cleanup_mutex_unlock, ®.mtx); + + while (!stop) { + if (ipcp == NULL) + break; + + switch(ipcp->info.state) { + case IPCP_NULL: + ret = -1; + stop = true; + break; + case IPCP_BOOT: + /* FALLTHRU*/ + case IPCP_OPERATIONAL: + ret = 0; + stop = true; + break; + case IPCP_INIT: + ret = -__timedwait(®.cond, ®.mtx, abstime); + break; + default: + assert(false); + break; /* Shut up static analyzer. */ + } + + ipcp = __reg_get_ipcp(info->pid); + + if (ret == -ETIMEDOUT) + break; + } + + if (ipcp != NULL) + *info = ipcp->info; + + pthread_cleanup_pop(true); + + return ipcp == NULL? -EIPCP : ret; +} + +int reg_respond_ipcp(const struct ipcp_info * info) +{ + struct reg_ipcp * ipcp; + + assert(info != NULL); + + pthread_mutex_lock(®.mtx); + + ipcp = __reg_get_ipcp(info->pid); + if (ipcp == NULL) { + log_err("IPCP %d not found for response.", info->pid); + goto fail_ipcp; + } + + assert(strcmp(info->name, ipcp->info.name) == 0); + assert(info->type == ipcp->info.type); + + reg_ipcp_update(ipcp, info); + + pthread_cond_broadcast(®.cond); + + pthread_mutex_unlock(®.mtx); + + return 0; + + fail_ipcp: + pthread_mutex_unlock(®.mtx); + return -EIPCP; +} diff --git a/src/irmd/reg/reg.h b/src/irmd/reg/reg.h new file mode 100644 index 00000000..77264fde --- /dev/null +++ b/src/irmd/reg/reg.h @@ -0,0 +1,165 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * The IPC Resource Manager - Registry + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#ifndef OUROBOROS_IRMD_REG_H +#define OUROBOROS_IRMD_REG_H + +#include <ouroboros/flow.h> +#include <ouroboros/ipcp.h> +#include <ouroboros/name.h> +#include <ouroboros/proc.h> +#include <ouroboros/protobuf.h> +#include <ouroboros/time.h> +#include <ouroboros/utils.h> + +#include "pool.h" + +int reg_init(void); + +void reg_clear(void); + +void reg_fini(void); + +int reg_create_flow(struct flow_info * info); + +int reg_destroy_flow(int flow_id); + +bool reg_has_flow(int flow_id); + +int reg_create_proc(const struct proc_info * info); + +/* Use this for all processes, including ipcps */ +int reg_destroy_proc(pid_t pid); + +bool reg_has_proc(pid_t pid); + +bool reg_is_proc_privileged(pid_t pid); + +int reg_prepare_pool(uid_t uid, + gid_t gid); + +uid_t reg_get_proc_uid(pid_t pid); + +void reg_kill_all_proc(int signal); + +pid_t reg_get_dead_proc(void); + +int reg_create_spawned(pid_t pid); + +bool reg_has_spawned(pid_t pid); + +void reg_kill_all_spawned(int signal); + +int reg_first_spawned(void); + +int reg_bind_proc(const char * name, + pid_t proc); + +int reg_unbind_proc(const char * name, + pid_t proc); + +int reg_create_ipcp(const struct ipcp_info * info); + +bool reg_has_ipcp(pid_t pid); + +int reg_set_layer_for_ipcp(struct ipcp_info * info, + const struct layer_info * layer); + +int reg_get_ipcp(struct ipcp_info * info, + struct layer_info * layer); + +int reg_get_ipcp_by_layer(struct ipcp_info * info, + struct layer_info * layer); + +/* TODO don't rely on protobuf here */ +int reg_list_ipcps(ipcp_list_msg_t *** msg); + +int reg_create_name(const struct name_info * info); + +int reg_destroy_name(const char * name); + +bool reg_has_name(const char * name); + +int reg_get_name_info(const char * name, + struct name_info * info); + +int reg_get_name_for_hash(char * buf, + enum hash_algo algo, + const uint8_t * hash); + +int reg_get_name_for_flow_id(char * buf, + int flow_id); + +/* TODO don't rely on protobuf here */ +int reg_list_names(name_info_msg_t *** names); + +int reg_create_prog(const struct prog_info * info); + +int reg_destroy_prog(const char * name); + +bool reg_has_prog(const char * name); + +int reg_get_exec(const char * name, + char *** exec); + +int reg_bind_prog(const char * name, + char ** exec, + uint8_t flags); + +int reg_unbind_prog(const char * name, + const char * prog); + +int reg_prepare_flow_alloc(struct flow_info * info); + +int reg_wait_flow_allocated(struct flow_info * info, + buffer_t * pbuf, + const struct timespec * abstime); + +int reg_respond_alloc(struct flow_info * info, + buffer_t * pbuf, + int response); + +int reg_prepare_flow_accept(struct flow_info * info); + +int reg_wait_flow_accepted(struct flow_info * info, + buffer_t * pbuf, + const struct timespec * abstime); + +int reg_wait_flow_accepting(const char * name, + const struct timespec * abstime); + +int reg_respond_accept(struct flow_info * info, + buffer_t * pbuf); + +void reg_dealloc_flow(struct flow_info * info); + +void reg_dealloc_flow_resp(struct flow_info * info); + +int reg_wait_proc(pid_t pid, + const struct timespec * abstime); + +int reg_wait_ipcp_boot(struct ipcp_info * ipcp, + const struct timespec * abstime); + +int reg_respond_ipcp(const struct ipcp_info * info); + +#endif /* OUROBOROS_IRMD_REG_H */ diff --git a/src/irmd/reg/tests/CMakeLists.txt b/src/irmd/reg/tests/CMakeLists.txt new file mode 100644 index 00000000..e8521545 --- /dev/null +++ b/src/irmd/reg/tests/CMakeLists.txt @@ -0,0 +1,33 @@ +get_filename_component(PARENT_PATH ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) +get_filename_component(PARENT_DIR ${PARENT_PATH} NAME) + +compute_test_prefix() + +create_test_sourcelist(${PARENT_DIR}_tests test_suite.c + # Add new tests here + flow_test.c + ipcp_test.c + name_test.c + proc_test.c + prog_test.c + reg_test.c +) + +add_executable(${PARENT_DIR}_test ${${PARENT_DIR}_tests}) + +target_include_directories(${PARENT_DIR}_test PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_BINARY_DIR}/include + ${CMAKE_SOURCE_DIR}/src/irmd + ${CMAKE_BINARY_DIR}/src/irmd +) + +disable_test_logging_for_target(${PARENT_DIR}_test) +target_link_libraries(${PARENT_DIR}_test PRIVATE ouroboros-common) +ouroboros_target_debug_definitions(${PARENT_DIR}_test) + +add_dependencies(build_tests ${PARENT_DIR}_test) + +ouroboros_register_tests(TARGET ${PARENT_DIR}_test TESTS ${${PARENT_DIR}_tests}) diff --git a/src/irmd/reg/tests/flow_test.c b/src/irmd/reg/tests/flow_test.c new file mode 100644 index 00000000..2066c811 --- /dev/null +++ b/src/irmd/reg/tests/flow_test.c @@ -0,0 +1,286 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * The IPC Resource Manager - Registry - Flows - Unit Tests + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#include "../flow.c" + +#include <test/test.h> + +#include <string.h> + +#define TEST_DATA "testpiggybackdata" + +static int test_reg_flow_create_destroy(void) +{ + struct reg_flow * f; + + struct flow_info info = { + .id = 1, + .n_pid = 1, + .qs = qos_raw, + .state = FLOW_INIT + }; + + TEST_START(); + + f = reg_flow_create(&info); + if (f == NULL) { + printf("Failed to create flow.\n"); + goto fail; + } + + reg_flow_destroy(f); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_reg_flow_create_no_id(void) { + struct flow_info info = { + .id = 0, + .n_pid = 1, + .qs = qos_raw, + .state = FLOW_INIT + }; + + reg_flow_create(&info); /* assert fail */ + + return TEST_RC_SUCCESS; +} + +static int test_reg_flow_create_no_pid(void) { + struct flow_info info = { + .id = 1, + .n_pid = 0, + .qs = qos_raw, + .state = FLOW_INIT + }; + + reg_flow_create(&info); /* assert fail */ + + return TEST_RC_SUCCESS; +} + +static int test_reg_flow_create_has_n_1_pid(void) { + struct flow_info info = { + .id = 1, + .n_pid = 0, + .n_1_pid = 1, + .qs = qos_raw, + .state = FLOW_INIT + }; + + reg_flow_create(&info); /* assert fail */ + + return TEST_RC_SUCCESS; +} + +static int test_reg_flow_create_wrong_state(void) { + struct flow_info info = { + .id = 1, + .n_pid = 0, + .n_1_pid = 1, + .qs = qos_raw, + .state = FLOW_ALLOC_PENDING + }; + + reg_flow_create(&info); /* assert fail */ + + return TEST_RC_SUCCESS; +} + +static int test_reg_flow_create_has_mpl(void) { + struct flow_info info = { + .id = 1, + .n_pid = 1, + .n_1_pid = 0, + .mpl = 10, + .qs = qos_raw, + .state = FLOW_ALLOC_PENDING + }; + + reg_flow_create(&info); /* assert fail */ + + return TEST_RC_SUCCESS; +} + +static int test_reg_flow_update(void) +{ + struct reg_flow * f; + + struct flow_info info = { + .id = 1, + .n_pid = 1, + .qs = qos_raw, + .state = FLOW_INIT + }; + + struct flow_info upd = { + .id = 1, + .n_pid = 1, + .qs = qos_data, + .state = FLOW_DEALLOCATED + }; + + TEST_START(); + + f = reg_flow_create(&info); + if (f == NULL) { + printf("Failed to create flow.\n"); + goto fail; + } + + reg_flow_update(f, &upd); + + if (memcmp(&f->info, &upd, sizeof(upd)) != 0) { + printf("Flow info not updated.\n"); + goto fail; + } + + reg_flow_destroy(f); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_reg_flow_update_wrong_id(void) +{ + struct reg_flow * f; + + struct flow_info info = { + .id = 1, + .n_pid = 1, + .qs = qos_raw, + .state = FLOW_INIT + }; + + struct flow_info upd = { + .id = 2, + .n_pid = 1, + .qs = qos_data, + .state = FLOW_DEALLOCATED + }; + + TEST_START(); + + f = reg_flow_create(&info); + if (f == NULL) { + printf("Failed to create flow.\n"); + goto fail; + } + + reg_flow_update(f, &upd); /* assert fail */ + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_reg_flow_assert_fails(void) +{ + int ret = 0; + + ret |= test_assert_fail(test_reg_flow_create_no_id); + ret |= test_assert_fail(test_reg_flow_create_no_pid); + ret |= test_assert_fail(test_reg_flow_create_has_n_1_pid); + ret |= test_assert_fail(test_reg_flow_create_wrong_state); + ret |= test_assert_fail(test_reg_flow_create_has_mpl); + ret |= test_assert_fail(test_reg_flow_update_wrong_id); + + return ret; +} + +static int test_flow_data(void) +{ + struct reg_flow * f; + + struct flow_info info = { + .id = 1, + .n_pid = 1, + .qs = qos_raw, + .state = FLOW_INIT + }; + + char * data; + buffer_t buf; + buffer_t rcv = {0, NULL}; + + TEST_START(); + + data = strdup(TEST_DATA); + if (data == NULL) { + printf("Failed to strdup data.\n"); + goto fail; + } + + buf.data = (uint8_t *) data; + buf.len = strlen(data); + + f = reg_flow_create(&info); + if (f == NULL) { + printf("Failed to create flow.\n"); + goto fail; + } + + reg_flow_set_data(f, &buf); + + reg_flow_get_data(f, &rcv); + + freebuf(buf); + clrbuf(rcv); + + reg_flow_destroy(f); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + free(data); + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int flow_test(int argc, + char ** argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + + ret |= test_reg_flow_create_destroy(); + ret |= test_reg_flow_update(); + ret |= test_reg_flow_assert_fails(); + ret |= test_flow_data(); + + return ret; +} diff --git a/src/irmd/reg/tests/ipcp_test.c b/src/irmd/reg/tests/ipcp_test.c new file mode 100644 index 00000000..6ab6443d --- /dev/null +++ b/src/irmd/reg/tests/ipcp_test.c @@ -0,0 +1,84 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * The IPC Resource Manager - Registry - IPCPs - Unit Tests + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#include <test/test.h> + +#include "../ipcp.c" + +#define TEST_PID 65535 + +static int test_reg_ipcp_create(void) +{ + struct reg_ipcp * ipcp; + struct ipcp_info info = { + .pid = TEST_PID, + .state = IPCP_INIT + }; + struct layer_info layer = { + .name = "testlayer", + .dir_hash_algo = DIR_HASH_SHA3_224 + }; + + TEST_START(); + + ipcp = reg_ipcp_create(&info); + if (ipcp == NULL) { + printf("Failed to create ipcp.\n"); + goto fail; + } + + if (strcmp(ipcp->layer.name, "Not enrolled.") != 0) { + printf("Layer name was not set.\n"); + goto fail; + } + + ipcp->info.state = IPCP_BOOT; + + reg_ipcp_set_layer(ipcp, &layer); + + if (strcmp(ipcp->layer.name, layer.name) != 0) { + printf("Layer name was not set.\n"); + goto fail; + } + + reg_ipcp_destroy(ipcp); + + TEST_SUCCESS(); + + return 0; + fail: + TEST_FAIL(); + return -1; +} + +int ipcp_test(int argc, + char ** argv) +{ + int res = 0; + + (void) argc; + (void) argv; + + res |= test_reg_ipcp_create(); + + return res; +} diff --git a/src/irmd/reg/tests/name_test.c b/src/irmd/reg/tests/name_test.c new file mode 100644 index 00000000..5b42875e --- /dev/null +++ b/src/irmd/reg/tests/name_test.c @@ -0,0 +1,301 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * The IPC Resource Manager - Registry - Names - Unit Tests + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + + +#include "../name.c" + +#include <test/test.h> + +#define TEST_PID 65534 +#define TEST_PROG "/usr/bin/testprog" +#define TEST_NAME "testservicename" + +static int test_reg_name_create(void) +{ + struct reg_name * n; + struct name_info info = { + .name = TEST_NAME, + .pol_lb = LB_RR, + }; + + TEST_START(); + + n = reg_name_create(&info); + if (n == NULL) { + printf("Failed to create name %s.\n", info.name); + goto fail; + } + + reg_name_destroy(n); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_reg_name_add_proc(void) +{ + struct reg_name * n; + struct name_info info = { + .name = TEST_NAME, + .pol_lb = LB_RR, + }; + + TEST_START(); + + n = reg_name_create(&info); + if (n == NULL) { + printf("Failed to create name %s.\n", info.name); + goto fail; + } + + if (reg_name_add_proc(n, TEST_PID) < 0) { + printf("Failed to add proc.\n"); + goto fail; + } + + if (n->procs.len != 1) { + printf("Proc not added to list.\n"); + goto fail; + } + + if (!reg_name_has_proc(n, TEST_PID)) { + printf("Proc not found.\n"); + goto fail; + } + + reg_name_del_proc(n, TEST_PID); + + if (n->procs.len != 0) { + printf("Proc not removed from list.\n"); + goto fail; + } + + reg_name_destroy(n); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_reg_name_add_prog(void) +{ + struct reg_name * n; + struct name_info info = { + .name = TEST_NAME, + .pol_lb = LB_RR, + }; + + char * exec[] = { TEST_PROG, "--argswitch", "argvalue", NULL}; + + TEST_START(); + + n = reg_name_create(&info); + if (n == NULL) { + printf("Failed to create name %s.\n", info.name); + goto fail; + } + + if (reg_name_add_prog(n, exec) < 0) { + printf("Failed to add prog.\n"); + goto fail; + } + + if (n->progs.len != 1) { + printf("Prog not added to list.\n"); + goto fail; + } + + if (!reg_name_has_prog(n, TEST_PROG)) { + printf("Prog not found.\n"); + goto fail; + } + + reg_name_del_prog(n, TEST_PROG); + + if (n->progs.len != 0) { + printf("Prog not removed from list.\n"); + goto fail; + } + + reg_name_destroy(n); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_reg_name_add_active(enum pol_balance lb) +{ + struct reg_name * n; + pid_t pid; + struct name_info info = { + .name = TEST_NAME, + .pol_lb = lb, + }; + + TEST_START(); + + n = reg_name_create(&info); + if (n == NULL) { + printf("Failed to create name %s.\n", info.name); + goto fail; + } + + if (reg_name_get_active(n) != -1) { + printf("Got active from empty actives.\n"); + goto fail; + } + + if (reg_name_add_proc(n, TEST_PID) < 0) { + printf("Failed to add proc 0.\n"); + goto fail; + } + + if (reg_name_add_proc(n, TEST_PID + 1) < 0) { + printf("Failed to add proc 1.\n"); + goto fail; + } + + if (reg_name_add_proc(n, TEST_PID + 2) < 0) { + printf("Failed to add proc 2.\n"); + goto fail; + } + + if (reg_name_add_active(n, TEST_PID) < 0) { + printf("Failed to add active.\n"); + goto fail; + } + + if (n->active.len != 1) { + printf("Active list not updated.\n"); + goto fail; + } + + if (reg_name_get_active(n) != TEST_PID) { + printf("Failed to get active.\n"); + goto fail; + } + + if (reg_name_get_active(n) != TEST_PID) { + printf("Failed to get active.\n"); + goto fail; + } + + if (reg_name_add_active(n, TEST_PID + 1) < 0) { + printf("Failed to add active 3.\n"); + goto fail; + } + + if (reg_name_add_active(n, TEST_PID + 1) < 0) { + printf("Failed to add active 3.\n"); + goto fail; + } + + + if (reg_name_add_active(n, TEST_PID + 2) < 0) { + printf("Failed to add active 4.\n"); + goto fail; + } + + if (n->procs.len != 3) { + printf("Procs list not updated.\n"); + goto fail; + } + + if (n->active.len != 4) { + printf("Active list not updated.\n"); + goto fail; + } + + pid = info.pol_lb == LB_RR ? TEST_PID : TEST_PID + 2; + + if (reg_name_get_active(n) != pid) { + printf("Got wrong active pid 1.\n"); + goto fail; + } + + reg_name_del_active(n, pid); + + if (reg_name_add_active(n, pid) < 0) { + printf("Failed to add active 4.\n"); + goto fail; + } + + pid = info.pol_lb == LB_RR ? TEST_PID + 1 : TEST_PID + 2; + + if (reg_name_get_active(n) != pid) { + printf("Got wrong active pid 2 %d.\n", pid); + goto fail; + } + + reg_name_del_proc(n, TEST_PID + 2); + + reg_name_del_proc(n, TEST_PID + 1); + + reg_name_del_proc(n, TEST_PID); + + if (n->procs.len != 0) { + printf("Procs list not cleared.\n"); + goto fail; + } + + if (n->active.len != 0) { + printf("Active list not cleared.\n"); + goto fail; + } + + reg_name_destroy(n); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int name_test(int argc, + char ** argv) +{ + int rc = 0; + + (void) argc; + (void) argv; + + rc |= test_reg_name_create(); + rc |= test_reg_name_add_proc(); + rc |= test_reg_name_add_prog(); + rc |= test_reg_name_add_active(LB_RR); + rc |= test_reg_name_add_active(LB_SPILL); + + return rc; +} diff --git a/src/irmd/reg/tests/proc_test.c b/src/irmd/reg/tests/proc_test.c new file mode 100644 index 00000000..c4e689f0 --- /dev/null +++ b/src/irmd/reg/tests/proc_test.c @@ -0,0 +1,119 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * The IPC Resource Manager - Registry - Processes - Unit Tests + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#include "../proc.c" + +#include <test/test.h> + +#define TEST_PID 65534 +#define TEST_PROG "usr/bin/testprog" + +#define TEST_PROC { \ + .pid = TEST_PID, \ + .prog = TEST_PROG, \ + .uid = getuid(), \ + .gid = getgid() \ +} + +static int test_reg_proc_create_destroy(void) +{ + struct reg_proc * proc; + struct proc_info info = TEST_PROC; + + TEST_START(); + + proc = reg_proc_create(&info); + if (proc == NULL) { + printf("Failed to create proc.\n"); + goto fail; + } + + reg_proc_destroy(proc); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_reg_proc_add_name(void) +{ + struct reg_proc * proc; + struct proc_info info = TEST_PROC; + + char * name = "testname"; + + TEST_START(); + + proc = reg_proc_create(&info); + if (proc == NULL) { + printf("Failed to create proc.\n"); + goto fail; + } + + if (reg_proc_add_name(proc, name) < 0) { + printf("Failed to add name."); + goto fail; + } + + if (proc->n_names != 1) { + printf("n_names not updated.\n"); + goto fail; + } + + if (!reg_proc_has_name(proc, name)) { + printf("Name not found.\n"); + goto fail; + } + + reg_proc_del_name(proc, name); + + if (proc->n_names != 0) { + printf("n_names not updated.\n"); + goto fail; + } + + reg_proc_destroy(proc); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int proc_test(int argc, + char ** argv) +{ + int res = 0; + + (void) argc; + (void) argv; + + res |= test_reg_proc_create_destroy(); + res |= test_reg_proc_add_name(); + + return res; +} diff --git a/src/irmd/reg/tests/prog_test.c b/src/irmd/reg/tests/prog_test.c new file mode 100644 index 00000000..3900e7d7 --- /dev/null +++ b/src/irmd/reg/tests/prog_test.c @@ -0,0 +1,115 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * The IPC Resource Manager - Registry - Programs - Unit Tests + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#include "../prog.c" + +#include <test/test.h> + +#define TEST_PROG "usr/bin/testprog" + +static int test_reg_prog_create(void) +{ + struct reg_prog * prog; + struct prog_info info = { + .name = TEST_PROG + }; + + TEST_START(); + + prog = reg_prog_create(&info); + if (prog == NULL) { + printf("Failed to create prog.\n"); + goto fail; + } + + reg_prog_destroy(prog); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_reg_prog_add_name(void) +{ + struct reg_prog * prog; + struct prog_info info = { + .name = TEST_PROG + }; + + char * name = "testname"; + + TEST_START(); + + prog = reg_prog_create(&info); + if (prog == NULL) { + printf("Failed to create prog.\n"); + goto fail; + } + + if (reg_prog_add_name(prog, name) < 0) { + printf("Failed to add name."); + goto fail; + } + + if (prog->n_names != 1) { + printf("n_names not updated.\n"); + goto fail; + } + + if (!reg_prog_has_name(prog, name)) { + printf("Name not found.\n"); + goto fail; + } + + reg_prog_del_name(prog, name); + + if (prog->n_names != 0) { + printf("n_names not updated.\n"); + goto fail; + } + + reg_prog_destroy(prog); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int prog_test(int argc, + char ** argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + + ret |= test_reg_prog_create(); + ret |= test_reg_prog_add_name(); + + return ret; +} diff --git a/src/irmd/reg/tests/reg_test.c b/src/irmd/reg/tests/reg_test.c new file mode 100644 index 00000000..f7a4de8e --- /dev/null +++ b/src/irmd/reg/tests/reg_test.c @@ -0,0 +1,1731 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * The IPC Resource Manager - Registry - Unit Tests + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + + +#include "../pool.c" +#undef OUROBOROS_PREFIX +#include "../reg.c" + +#include <test/test.h> + +#define TEST_PID 3666 +#define TEST_N_1_PID 3999 +#define TEST_FAKE_ID 9128349 +#define TEST_MPL 5 +#define TEST_PROG "reg_test" /* own binary for binary check */ +#define TEST_IPCP "testipcp" +#define TEST_NAME "testname" +#define TEST_DATA "testpbufdata" +#define TEST_DATA2 "testpbufdata2" +#define TEST_LAYER "testlayer" +#define TEST_PROC_INFO { \ + .pid = TEST_PID, \ + .prog = TEST_PROG, \ + .uid = 0, \ + .gid = 0 \ +} +#define REG_TEST_FAIL() \ + do { TEST_FAIL(); reg_clear(); return TEST_RC_FAIL;} while(0) + +static int test_reg_init(void) +{ + TEST_START(); + + if (reg_init() < 0) { + printf("Failed to init registry.\n"); + goto fail; + } + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_reg_create_flow(void) +{ + struct flow_info info = { + .n_pid = TEST_PID, + .qs = qos_raw, + }; + + TEST_START(); + + if (reg_init() < 0) { + printf("Failed to init registry.\n"); + goto fail; + } + + if (reg_create_flow(&info) < 0) { + printf("Failed to create flow.\n"); + goto fail; + } + + if (info.id == 0) { + printf("Failed to update id.'n"); + goto fail; + } + + if (reg.n_flows != 1) { + printf("n_flows was not updated.\n"); + goto fail; + } + + if (!reg_has_flow(info.id)) { + printf("Failed to find flow.\n"); + goto fail; + } + + if (reg_destroy_flow(info.id) < 0) { + printf("Failed to destroy flow.\n"); + goto fail; + } + + if (reg.n_flows != 0) { + printf("n_flows was not updated.\n"); + goto fail; + } + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_reg_allocate_flow_timeout(void) +{ + struct timespec abstime; + struct timespec timeo = TIMESPEC_INIT_MS(1); + buffer_t rbuf = BUF_INIT; + + struct flow_info info = { + .n_pid = TEST_PID, + .qs = qos_raw + }; + + TEST_START(); + + clock_gettime(PTHREAD_COND_CLOCK, &abstime); + + ts_add(&abstime, &timeo, &abstime); + + if (reg_init() < 0) { + printf("Failed to init registry.\n"); + goto fail; + } + + if (reg_create_flow(&info) < 0) { + printf("Failed to add flow.\n"); + goto fail; + } + + if (reg_prepare_flow_accept(&info) < 0) { + printf("Failed to prepare flow for accept.\n"); + goto fail; + } + + if (reg_wait_flow_accepted(&info, &rbuf, &abstime) != -ETIMEDOUT) { + printf("Wait allocated did not timeout.\n"); + goto fail; + } + + if (info.state != FLOW_DEALLOCATED) { + printf("Flow did not timeout in deallocated state.\n"); + goto fail; + } + + reg_destroy_flow(info.id); + + if (reg.n_flows != 0) { + printf("Flow did not destroy.\n"); + goto fail; + } + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static void * test_flow_respond_alloc(void * o) +{ + struct flow_info * info = (struct flow_info *) o; + buffer_t pbuf = BUF_INIT; + int response; + + response = (info->state == FLOW_ALLOCATED) ? 0 : -1; + + if (info->state == FLOW_ALLOCATED) { + pbuf.data = (uint8_t *) strdup(TEST_DATA2); + if (pbuf.data == NULL) { + printf("Failed to strdup data2.\n"); + goto fail; + } + pbuf.len = strlen((char *) pbuf.data) + 1; + } + + reg_respond_alloc(info, &pbuf, response); + + return (void *) 0; + fail: + return (void *) -1; +} + +static void * test_flow_respond_accept(void * o) +{ + struct flow_info * info = (struct flow_info *) o; + buffer_t pbuf; + + pbuf.data = (uint8_t *) strdup(TEST_DATA2); + if (pbuf.data == NULL) { + printf("Failed to strdup data2.\n"); + goto fail; + } + pbuf.len = strlen((char *) pbuf.data) + 1; + + reg_respond_accept(info, &pbuf); + + return (void *) 0; + fail: + return (void *) -1; +} + +static int test_reg_accept_flow_success(void) +{ + pthread_t thr; + struct timespec abstime; + struct timespec timeo = TIMESPEC_INIT_S(1); + buffer_t rbuf = BUF_INIT; + + struct flow_info info = { + .n_pid = TEST_PID, + .qs = qos_raw + }; + + struct flow_info n_1_info = { + .n_1_pid = TEST_N_1_PID, + .qs = qos_data, + .state = FLOW_ALLOCATED /* RESPONSE SUCCESS */ + }; + + TEST_START(); + + clock_gettime(PTHREAD_COND_CLOCK, &abstime); + + ts_add(&abstime, &timeo, &abstime); + + if (reg_init() < 0) { + printf("Failed to init registry.\n"); + goto fail; + } + + if (reg_create_flow(&info) < 0) { + printf("Failed to add flow.\n"); + goto fail; + } + + if (reg_prepare_flow_accept(&info) < 0) { + printf("Failed to prepare flow for accept.\n"); + goto fail; + } + + n_1_info.id = info.id; + n_1_info.mpl = 1; + + pthread_create(&thr, NULL, test_flow_respond_accept, &n_1_info); + + if (reg_wait_flow_accepted(&info, &rbuf, &abstime) < 0) { + printf("Flow allocation failed.\n"); + pthread_join(thr, NULL); + reg_destroy_flow(info.id); + reg_fini(); + goto fail; + } + + pthread_join(thr, NULL); + + if (info.state != FLOW_ALLOCATED) { + printf("Flow succeeded but not in allocated state.\n"); + goto fail; + } + + if (rbuf.data == NULL) { + printf("rbuf data not returned.\n"); + goto fail; + } + + if (strcmp((char *) rbuf.data, TEST_DATA2) != 0) { + printf("Data2 was not passed correctly.\n"); + goto fail; + } + + freebuf(rbuf); + + reg_dealloc_flow(&info); + + if (info.state != FLOW_DEALLOC_PENDING) { + printf("Flow dealloc requested but not in pending state.\n"); + goto fail; + } + + reg_dealloc_flow_resp(&info); + + if (info.state != FLOW_DEALLOCATED) { + printf("Flow deallocated but not in deallocated state.\n"); + goto fail; + } + + reg_destroy_flow(n_1_info.id); + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_reg_accept_flow_success_no_crypt(void) +{ + pthread_t thr; + struct timespec abstime; + struct timespec timeo = TIMESPEC_INIT_S(1); + buffer_t rbuf = BUF_INIT; + + struct flow_info info = { + .n_pid = TEST_PID, + .qs = qos_raw + }; + + struct flow_info n_1_info = { + .n_1_pid = TEST_N_1_PID, + .qs = qos_data, + .state = FLOW_ALLOCATED /* RESPONSE SUCCESS */ + }; + + TEST_START(); + + clock_gettime(PTHREAD_COND_CLOCK, &abstime); + + ts_add(&abstime, &timeo, &abstime); + + if (reg_init() < 0) { + printf("Failed to init registry.\n"); + goto fail; + } + + if (reg_create_flow(&info) < 0) { + printf("Failed to add flow.\n"); + goto fail; + } + + if (reg_prepare_flow_accept(&info) < 0) { + printf("Failed to prepare flow for accept.\n"); + goto fail; + } + + n_1_info.id = info.id; + n_1_info.mpl = 1; + + pthread_create(&thr, NULL, test_flow_respond_accept, &n_1_info); + + if (reg_wait_flow_accepted(&info, &rbuf, &abstime) < 0 ) { + printf("Flow allocation failed.\n"); + pthread_join(thr, NULL); + reg_destroy_flow(info.id); + reg_fini(); + goto fail; + } + + pthread_join(thr, NULL); + + if (info.state != FLOW_ALLOCATED) { + printf("Flow succeeded but not in allocated state.\n"); + goto fail; + } + + if (rbuf.data == NULL) { + printf("rbuf data was not returned.\n"); + goto fail; + } + + freebuf(rbuf); + + n_1_info.state = FLOW_DEALLOCATED; + + reg_dealloc_flow(&info); + + if (info.state != FLOW_DEALLOC_PENDING) { + printf("Flow dealloc requested but not in pending state.\n"); + goto fail; + } + + reg_dealloc_flow_resp(&info); + + if (info.state != FLOW_DEALLOCATED) { + printf("Flow deallocated but not in deallocated state.\n"); + goto fail; + } + + reg_destroy_flow(n_1_info.id); + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + + +static int test_reg_allocate_flow_fail(void) +{ + buffer_t buf = BUF_INIT; + pthread_t thr; + struct timespec abstime; + struct timespec timeo = TIMESPEC_INIT_S(1); + + struct flow_info info = { + .n_pid = TEST_PID, + .qs = qos_raw + }; + + struct flow_info n_1_info = { + .n_1_pid = TEST_N_1_PID, + .qs = qos_data, + .state = FLOW_DEALLOCATED /* RESPONSE FAIL */ + }; + + TEST_START(); + + clock_gettime(PTHREAD_COND_CLOCK, &abstime); + + ts_add(&abstime, &timeo, &abstime); + + if (reg_init() < 0) { + printf("Failed to init registry.\n"); + goto fail; + } + + if (reg_create_flow(&info) < 0) { + printf("Failed to add flow.\n"); + goto fail; + } + + info.n_1_pid = TEST_N_1_PID; + + if (reg_prepare_flow_alloc(&info) < 0) { + printf("Failed to prepare flow for alloc.\n"); + goto fail; + } + + n_1_info.id = info.id; + + pthread_create(&thr, NULL, test_flow_respond_alloc, &n_1_info); + + if (reg_wait_flow_allocated(&info, &buf, &abstime) == 0 ) { + printf("Flow allocation succeeded.\n"); + pthread_join(thr, NULL); + reg_destroy_flow(info.id); + reg_fini(); + goto fail; + } + + pthread_join(thr, NULL); + + if (info.state != FLOW_DEALLOCATED) { + printf("Flow failed but not in deallocated state.\n"); + goto fail; + } + + reg_destroy_flow(n_1_info.id); + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_reg_flow(void) { + int rc = 0; + + rc |= test_reg_create_flow(); + rc |= test_reg_allocate_flow_timeout(); + rc |= test_reg_accept_flow_success(); + rc |= test_reg_accept_flow_success_no_crypt(); + rc |= test_reg_allocate_flow_fail(); + + return rc; +} + +static int test_reg_create_ipcp(void) +{ + struct ipcp_info info = { + .name = TEST_IPCP, + .pid = TEST_PID, + .state = IPCP_INIT /* set by spawn_ipcp */ + }; + + TEST_START(); + + if (reg_init() < 0) { + printf("Failed to init registry.\n"); + goto fail; + } + + if (reg_create_ipcp(&info) < 0) { + printf("Failed to create ipcp.\n"); + goto fail; + } + + if (reg.n_ipcps != 1) { + printf("n_ipcps was not updated.\n"); + goto fail; + } + + if (!reg_has_ipcp(info.pid)) { + printf("Failed to find ipcp.\n"); + goto fail; + } + + if (reg_destroy_proc(info.pid) < 0) { + printf("Failed to destroy ipcp.\n"); + goto fail; + } + + if (reg.n_ipcps != 0) { + printf("n_ipcps was not updated.\n"); + goto fail; + } + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_reg_list_ipcps(void) +{ + ipcp_list_msg_t ** ipcps; + int i; + ssize_t len; + + TEST_START(); + + if (reg_init() < 0) { + printf("Failed to init registry.\n"); + goto fail; + } + + for (i = 0; i < 10; i++) { + struct ipcp_info info = { + .pid = TEST_PID + i, + .state = IPCP_INIT /* set by spawn_ipcp */ + }; + + sprintf(info.name, "%s%d", TEST_IPCP, i); + + if (reg_create_ipcp(&info) < 0) { + printf("Failed to create ipcp %d.\n", i); + goto fail; + } + } + + len = reg_list_ipcps(&ipcps); + if (len < 0) { + printf("Failed to list ipcps.\n"); + goto fail; + } + + if (len != 10) { + printf("Failed to list all ipcps.\n"); + goto fail; + } + + while (len-- > 0) + ipcp_list_msg__free_unpacked(ipcps[len], NULL); + free(ipcps); + + for (i = 0; i < 10; i++) + reg_destroy_proc(TEST_PID + i); + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_insert_ipcps(void) +{ + ipcp_list_msg_t ** ipcps; + struct ipcp_info info; + size_t i; + size_t len; + + TEST_START(); + + if (reg_init() < 0) { + printf("Failed to init registry.\n"); + goto fail; + } + + for (i = 0; i < 100; i++) { + sprintf(info.name, "%s-%zd", TEST_IPCP, i); + info.pid = TEST_PID + rand() % 10000; + info.type = rand() % IPCP_INVALID; + info.state = IPCP_INIT; /* set by spawn_ipcp */ + + if (reg_create_ipcp(&info) < 0) { + printf("Failed to create ipcp %s.\n", info.name); + goto fail; + } + } + + len = reg_list_ipcps(&ipcps); + if (len != 100) { + printf("Failed to list all ipcps.\n"); + goto fail; + } + + for (i = 1; i < len; i++) { + if (ipcps[i]->type < ipcps[i - 1]->type) { + printf("IPCPS not sorted by type.\n"); + goto fail; + } + + if (ipcps[i]->type != ipcps[i - 1]->type) + continue; + + /* allow occasional duplicate PID in test */ + if (ipcps[i]->pid < ipcps[i - 1]->pid) { + printf("IPCPS not sorted by pid.\n"); + goto fail; + } + } + + while (len-- > 0) + ipcp_list_msg__free_unpacked(ipcps[len], NULL); + free(ipcps); + + reg_clear(); + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; +fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_set_layer(void) +{ + struct reg_ipcp * ipcp; + struct ipcp_info info = { + .name = TEST_IPCP, + .pid = TEST_PID, + .state = IPCP_INIT /* set by spawn_ipcp */ + }; + struct layer_info layer = { + .name = TEST_LAYER, + }; + + struct ipcp_info get_info = { + .pid = TEST_PID + }; + struct layer_info get_layer; + + TEST_START(); + + if (reg_init() < 0) { + printf("Failed to init registry.\n"); + goto fail; + } + + if (reg_create_ipcp(&info) < 0) { + printf("Failed to create ipcp.\n"); + goto fail; + } + + ipcp = __reg_get_ipcp(info.pid); + + ipcp->info.state = IPCP_BOOT; + info.state = IPCP_BOOT; + + reg_set_layer_for_ipcp(&info, &layer); + + reg_get_ipcp(&get_info, &get_layer); + + if (memcmp(&get_info, &info, sizeof(ipcp)) != 0) { + printf("Failed to set ipcp info.\n"); + goto fail; + } + + if (memcmp(&get_layer, &layer, sizeof(layer)) != 0) { + printf("Failed to set layer info.\n"); + goto fail; + } + + if (reg_destroy_proc(info.pid) < 0) { + printf("Failed to destroy ipcp.\n"); + goto fail; + } + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_reg_ipcp(void) +{ + int rc = 0; + + rc |= test_reg_create_ipcp(); + rc |= test_reg_list_ipcps(); + rc |= test_insert_ipcps(); + rc |= test_set_layer(); + + return rc; +} + +static int test_reg_create_name(void) +{ + struct name_info info = { + .name = TEST_NAME, + .pol_lb = LB_RR + }; + + TEST_START(); + + if (reg_init() < 0) { + printf("Failed to init registry.\n"); + goto fail; + } + + if (reg_create_name(&info) < 0) { + printf("Failed to create name.\n"); + goto fail; + } + + if (reg.n_names != 1) { + printf("n_names was not updated.\n"); + goto fail; + } + + if (!reg_has_name(info.name)) { + printf("Failed to find name.\n"); + goto fail; + } + + if (reg_destroy_name(info.name) < 0) { + printf("Failed to destroy name.\n"); + goto fail; + } + + if (reg.n_names != 0) { + printf("n_names was not updated.\n"); + goto fail; + } + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_reg_list_names(void) +{ + name_info_msg_t ** names; + int i; + ssize_t len; + + TEST_START(); + + if (reg_init() < 0) { + printf("Failed to init registry.\n"); + goto fail; + } + + for (i = 0; i < 10; i++) { + struct name_info info = { + .pol_lb = LB_RR + }; + + sprintf(info.name, "%s%d", TEST_NAME, i); + + if (reg_create_name(&info) < 0) { + printf("Failed to create name %d.\n", i); + goto fail; + } + } + + len = reg_list_names(&names); + if (len < 0) { + printf("Failed to list names.\n"); + goto fail; + } + + if (len != 10) { + printf("Failed to list all names.\n"); + goto fail; + } + + for (i = 0; i < len; i++) + name_info_msg__free_unpacked(names[i], NULL); + free(names); + + for (i = 0; i < 10; i++) { + char name[NAME_MAX]; + sprintf(name, "%s%d", TEST_NAME, i); + reg_destroy_name(name); + } + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_reg_name(void) +{ + int rc = 0; + + rc |= test_reg_create_name(); + rc |= test_reg_list_names(); + + return rc; +} + +static int test_reg_create_proc(void) +{ + struct proc_info info = TEST_PROC_INFO; + + TEST_START(); + + if (reg_init() < 0) { + printf("Failed to init registry.\n"); + goto fail; + } + + if (reg_create_proc(&info) < 0) { + printf("Failed to create process.\n"); + goto fail; + } + + if (reg.n_procs != 1) { + printf("n_procs was not updated.\n"); + goto fail; + } + + if (!reg_has_proc(info.pid)) { + printf("Failed to find process.\n"); + goto fail; + } + + if (reg_destroy_proc(info.pid) < 0) { + printf("Failed to destroy process.\n"); + goto fail; + } + + if (reg.n_procs != 0) { + printf("n_procs was not updated.\n"); + goto fail; + } + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_reg_proc(void) +{ + int rc = 0; + + rc |= test_reg_create_proc(); + + return rc; +} + +static int test_reg_spawned(void) +{ + TEST_START(); + + if (reg_init() < 0) { + printf("Failed to init registry.\n"); + goto fail; + } + + if (reg_create_spawned(TEST_PID) < 0) { + printf("Failed to create process.\n"); + goto fail; + } + + if (reg.n_spawned != 1) { + printf("n_spawned was not updated.\n"); + goto fail; + } + + if (!reg_has_spawned(TEST_PID)) { + printf("Failed to find spawned.\n"); + goto fail; + } + + if (reg_destroy_proc(TEST_PID) < 0) { + printf("Failed to destroy spawned.\n"); + goto fail; + } + + if (reg.n_spawned != 0) { + printf("n_spawned was not updated.\n"); + goto fail; + } + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_reg_create_prog(void) +{ + struct prog_info info = { + .name = TEST_PROG + }; + + TEST_START(); + + if (reg_init() < 0) { + printf("Failed to init registry.\n"); + goto fail; + } + + if (reg_create_prog(&info) < 0) { + printf("Failed to create program.\n"); + goto fail; + } + + if (reg.n_progs != 1) { + printf("n_progs was not updated.\n"); + goto fail; + } + + if (!reg_has_prog(info.name)) { + printf("Failed to find program.\n"); + goto fail; + } + + if (reg_destroy_prog(info.name) < 0) { + printf("Failed to destroy program.\n"); + goto fail; + } + + if (reg.n_progs != 0) { + printf("n_progs was not updated.\n"); + goto fail; + } + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_reg_prog(void) +{ + int rc = 0; + + rc |= test_reg_create_prog(); + + return rc; +} + +static int test_bind_proc(void) +{ + struct proc_info pinfo = TEST_PROC_INFO; + + struct name_info ninfo = { + .name = TEST_NAME, + .pol_lb = LB_RR + }; + + TEST_START(); + + if (reg_init()) { + printf("Failed to init registry.\n"); + goto fail; + } + + if (reg_create_name(&ninfo) < 0) { + printf("Failed to create name.\n"); + goto fail; + } + + if (reg_create_proc(&pinfo) < 0) { + printf("Failed to create proc.\n"); + goto fail; + } + + if (reg_bind_proc(TEST_NAME, TEST_PID) < 0) { + printf("Failed to bind proc.\n"); + goto fail; + } + + if (reg_unbind_proc(TEST_NAME, TEST_PID) < 0) { + printf("Failed to unbind proc.\n"); + goto fail; + } + + reg_destroy_proc(TEST_PID); + + if (reg_name_has_proc( __reg_get_name(TEST_NAME), TEST_PID)) { + printf("Proc still in name after destroy.\n"); + goto fail; + } + + reg_destroy_name(TEST_NAME); + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; +fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_bind_prog(void) +{ + struct prog_info pinfo = { + .name = TEST_PROG + }; + + struct name_info ninfo = { + .name = TEST_NAME, + .pol_lb = LB_RR + }; + + char * exec[] = { TEST_PROG, "--argswitch", "argvalue", NULL}; + + TEST_START(); + + if (reg_init()) { + printf("Failed to init registry.\n"); + goto fail; + } + + if (reg_create_name(&ninfo) < 0) { + printf("Failed to create name.\n"); + goto fail; + } + + if (reg_create_prog(&pinfo) < 0) { + printf("Failed to create prog.\n"); + goto fail; + } + + if (reg_bind_prog(TEST_NAME, exec, BIND_AUTO) < 0) { + printf("Failed to bind prog.\n"); + goto fail; + } + + if (!reg_name_has_prog( __reg_get_name(TEST_NAME), TEST_PROG)) { + printf("Prog not found in name.\n"); + goto fail; + } + + if (!reg_prog_has_name( __reg_get_prog(TEST_PROG), TEST_NAME)) { + printf("Name not found in prog.\n"); + goto fail; + } + + if (reg_unbind_prog(TEST_NAME, TEST_PROG) < 0) { + printf("Failed to unbind prog.\n"); + goto fail; + } + + if (reg_name_has_prog( __reg_get_name(TEST_NAME), TEST_PROG)) { + printf("Prog still in name after unbind.\n"); + goto fail; + } + + if (reg_prog_has_name( __reg_get_prog(TEST_PROG), TEST_NAME)) { + printf("Name still in prog after unbind.\n"); + goto fail; + } + + if (reg_bind_prog(TEST_NAME, exec, 0) < 0) { + printf("Failed to bind prog.\n"); + goto fail; + } + + if (reg_name_has_prog( __reg_get_name(TEST_NAME), TEST_PROG)) { + printf("Non-auto prog found in name.\n"); + goto fail; + } + + if (reg_unbind_prog(TEST_NAME, TEST_PROG) < 0) { + printf("Failed to unbind prog.\n"); + goto fail; + } + + reg_destroy_prog(TEST_PROG); + + reg_destroy_name(TEST_NAME); + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; +fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_inherit_prog(void) +{ + struct name_info nameinfo = { + .name = TEST_NAME, + .pol_lb = LB_RR + }; + + struct prog_info proginfo = { + .name = TEST_PROG + }; + + struct proc_info procinfo = TEST_PROC_INFO; + + char * exec[] = { TEST_PROG, NULL}; + + TEST_START(); + + if (reg_init()) { + printf("Failed to init registry.\n"); + goto fail; + } + + if (reg_create_name(&nameinfo) < 0) { + printf("Failed to create name.\n"); + goto fail; + } + + if (reg_create_prog(&proginfo) < 0) { + printf("Failed to create prog.\n"); + goto fail; + } + + if (reg_bind_prog(TEST_NAME, exec, 0) < 0) { + printf("Failed to bind prog.\n"); + goto fail; + } + + if (reg_create_proc(&procinfo) < 0) { + printf("Failed to create proc.\n"); + goto fail; + } + + if (!reg_name_has_proc(__reg_get_name(TEST_NAME), TEST_PID)) { + printf("Failed to update name from prog.\n"); + goto fail; + } + + if (!reg_proc_has_name(__reg_get_proc(TEST_PID), TEST_NAME)) { + printf("Failed to update proc from prog.\n"); + goto fail; + } + + reg_destroy_proc(TEST_PID); + + reg_destroy_prog(TEST_PROG); + + reg_destroy_name(TEST_NAME); + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; +fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_wait_accepting_timeout(void) +{ + struct timespec abstime; + struct timespec timeo = TIMESPEC_INIT_MS(1); + int flow_id; + struct name_info ninfo = { + .name = TEST_NAME, + .pol_lb = LB_RR + }; + + TEST_START(); + + if (reg_init()) { + printf("Failed to init registry.\n"); + goto fail; + } + + if (reg_create_name(&ninfo) < 0) { + printf("Failed to create name.\n"); + goto fail; + } + + clock_gettime(PTHREAD_COND_CLOCK, &abstime); + ts_add(&abstime, &timeo, &abstime); + + flow_id = reg_wait_flow_accepting(ninfo.name, &abstime); + if (flow_id != -ETIMEDOUT) { + printf("Wait accept did not time out: %d.\n", flow_id); + goto fail; + } + + reg_destroy_name(TEST_NAME); + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_wait_accepting_fail_name(void) +{ + struct timespec abstime; + struct timespec timeo = TIMESPEC_INIT_S(1); + int flow_id; + + TEST_START(); + + if (reg_init()) { + printf("Failed to init registry.\n"); + goto fail; + } + + clock_gettime(PTHREAD_COND_CLOCK, &abstime); + ts_add(&abstime, &timeo, &abstime); + + flow_id = reg_wait_flow_accepting(TEST_NAME, &abstime); + if (flow_id != -ENAME) { + printf("Wait accept did not fail: %d.\n", flow_id); + goto fail; + } + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static void * test_call_flow_accept(void * o) +{ + struct timespec abstime; + struct timespec timeo = TIMESPEC_INIT_MS(10); + buffer_t pbuf = BUF_INIT; + + struct proc_info pinfo = TEST_PROC_INFO; + + struct flow_info info = { + .n_pid = pinfo.pid, + .qs = qos_raw, + }; + + if (reg_create_proc(&pinfo) < 0) { + printf("Failed to create proc.\n"); + goto fail; + } + + if (reg_bind_proc((char *) o, TEST_PID) < 0) { + printf("Failed to bind proc.\n"); + goto fail; + } + + if (reg_create_flow(&info) < 0) { + printf("Failed to create flow.\n"); + goto fail; + } + + info.state = FLOW_ACCEPT_PENDING; + + reg_prepare_flow_accept(&info); + + clock_gettime(PTHREAD_COND_CLOCK, &abstime); + ts_add(&abstime, &timeo, &abstime); + + if (reg_wait_flow_accepted(&info, &pbuf, &abstime) != -ETIMEDOUT) { + printf("Wait allocated did not timeout.\n"); + goto fail; + } + + if (reg_unbind_proc((char *) o, pinfo.pid) < 0) { + printf("Failed to unbind proc.\n"); + goto fail; + } + + reg_destroy_flow(info.id); + reg_destroy_proc(pinfo.pid); + + return (void *) 0; + fail: + return (void *) -1; +} + +static int test_wait_accepting_success(void) +{ + struct timespec abstime; + struct timespec timeo = TIMESPEC_INIT_S(10); + pthread_t thr; + int flow_id; + struct name_info ninfo = { + .name = TEST_NAME, + .pol_lb = LB_RR + }; + + + TEST_START(); + + if (reg_init()) { + printf("Failed to init registry.\n"); + goto fail; + } + + if (reg_create_name(&ninfo) < 0) { + printf("Failed to create name.\n"); + goto fail; + } + + pthread_create(&thr, NULL, test_call_flow_accept, ninfo.name); + + clock_gettime(PTHREAD_COND_CLOCK, &abstime); + ts_add(&abstime, &timeo, &abstime); + + flow_id = reg_wait_flow_accepting(ninfo.name, &abstime); + if (flow_id < 0) { + printf("Wait accept did not return a flow id: %d.\n", flow_id); + pthread_join(thr, NULL); + reg_destroy_name(TEST_NAME); + reg_fini(); + goto fail; + } + + pthread_join(thr, NULL); + + reg_destroy_name(TEST_NAME); + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_wait_accepting(void) +{ + int rc = 0; + + rc |= test_wait_accepting_timeout(); + rc |= test_wait_accepting_fail_name(); + rc |= test_wait_accepting_success(); + + return rc; +} + +static int test_wait_ipcp_boot_timeout(void) +{ + struct timespec abstime; + struct timespec timeo = TIMESPEC_INIT_MS(1); + struct ipcp_info info = { + .name = TEST_IPCP, + .pid = TEST_PID, + .state = IPCP_INIT /* set by spawn_ipcp */ + }; + + TEST_START(); + + if (reg_init() < 0) { + printf("Failed to init registry.\n"); + goto fail; + } + + if (reg_create_ipcp(&info) < 0) { + printf("Failed to create ipcp.\n"); + goto fail; + } + + clock_gettime(PTHREAD_COND_CLOCK, &abstime); + ts_add(&abstime, &timeo, &abstime); + + if (reg_wait_ipcp_boot(&info, &abstime) != -ETIMEDOUT) { + printf("Wait boot did not timeout.\n"); + goto fail; + } + + if (reg_destroy_proc(info.pid) < 0) { + printf("Failed to destroy ipcp.\n"); + goto fail; + } + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static void * test_ipcp_respond(void * o) +{ + (void) o; + + reg_respond_ipcp((struct ipcp_info *) o); + + return (void *) 0; +} + +static int test_wait_ipcp_boot_fail(void) +{ + struct timespec abstime; + struct timespec timeo = TIMESPEC_INIT_S(10); + pthread_t thr; + struct ipcp_info info = { + .name = TEST_IPCP, + .pid = TEST_PID, + .state = IPCP_INIT /* set by spawn_ipcp */ + }; + struct ipcp_info resp_info = { + .name = TEST_IPCP, + .pid = TEST_PID, + .state = IPCP_NULL + }; + + TEST_START(); + + if (reg_init() < 0) { + printf("Failed to init registry.\n"); + goto fail; + } + + if (reg_create_ipcp(&info) < 0) { + printf("Failed to create ipcp.\n"); + goto fail; + } + + pthread_create(&thr, NULL, test_ipcp_respond, &resp_info); + + clock_gettime(PTHREAD_COND_CLOCK, &abstime); + ts_add(&abstime, &timeo, &abstime); + + info.state = IPCP_INIT; + + if (reg_wait_ipcp_boot(&info, &abstime) == 0) { + printf("IPCP boot reported success.\n"); + pthread_join(thr, NULL); + reg_destroy_proc(info.pid); + reg_fini(); + goto fail; + } + + pthread_join(thr, NULL); + + if (reg_destroy_proc(info.pid) < 0) { + printf("Failed to destroy ipcp.\n"); + goto fail; + } + + if (reg.n_ipcps != 0) { + printf("n_ipcps was not updated.\n"); + goto fail; + } + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_wait_ipcp_boot_success(void) +{ + pthread_t thr; + struct timespec abstime; + struct timespec timeo = TIMESPEC_INIT_S(10); + struct ipcp_info info = { + .name = TEST_IPCP, + .pid = TEST_PID, + .state = IPCP_INIT /* set by spawn_ipcp */ + }; + struct ipcp_info resp_info = { + .name = TEST_IPCP, + .pid = TEST_PID, + .state = IPCP_OPERATIONAL + }; + + TEST_START(); + + if (reg_init() < 0) { + printf("Failed to init registry.\n"); + goto fail; + } + + if (reg_create_ipcp(&info) < 0) { + printf("Failed to create ipcp.\n"); + goto fail; + } + + pthread_create(&thr, NULL, test_ipcp_respond, &resp_info); + + clock_gettime(PTHREAD_COND_CLOCK, &abstime); + ts_add(&abstime, &timeo, &abstime); + + info.state = IPCP_INIT; + + if (reg_wait_ipcp_boot(&info, &abstime) < 0) { + printf("IPCP boot failed.\n"); + pthread_join(thr, NULL); + reg_destroy_proc(info.pid); + reg_fini(); + goto fail; + } + + pthread_join(thr, NULL); + + if (info.state != IPCP_OPERATIONAL) { + printf("IPCP boot succeeded in non-operational state.\n"); + reg_destroy_proc(info.pid); + reg_fini(); + goto fail; + } + + if (reg_destroy_proc(info.pid) < 0) { + printf("Failed to destroy ipcp.\n"); + goto fail; + } + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_wait_ipcp_boot(void) +{ + int rc = 0; + + rc |= test_wait_ipcp_boot_timeout(); + rc |= test_wait_ipcp_boot_fail(); + rc |= test_wait_ipcp_boot_success(); + + return rc; +} + +static int test_wait_proc_timeout(void) +{ + struct timespec abstime; + struct timespec timeo = TIMESPEC_INIT_MS(1); + + TEST_START(); + + if (reg_init() < 0) { + printf("Failed to init registry.\n"); + goto fail; + } + + + clock_gettime(PTHREAD_COND_CLOCK, &abstime); + ts_add(&abstime, &timeo, &abstime); + + if (reg_wait_proc(TEST_PID, &abstime) != -ETIMEDOUT) { + printf("Wait proc did not timeout.\n"); + goto fail; + } + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static void * test_proc(void * o) +{ + (void) o; + + reg_create_proc((struct proc_info *) o); + + return (void *) 0; +} + +static int test_wait_proc_success(void) +{ + struct timespec abstime; + struct timespec timeo = TIMESPEC_INIT_S(10); + pthread_t thr; + struct proc_info info = TEST_PROC_INFO; + + TEST_START(); + + if (reg_init() < 0) { + printf("Failed to init registry.\n"); + goto fail; + } + + pthread_create(&thr, NULL, test_proc, &info); + + clock_gettime(PTHREAD_COND_CLOCK, &abstime); + ts_add(&abstime, &timeo, &abstime); + + if (reg_wait_proc(info.pid, &abstime) < 0) { + printf("Waiting for proc failed.\n"); + pthread_join(thr, NULL); + reg_destroy_proc(info.pid); + reg_fini(); + goto fail; + } + + pthread_join(thr, NULL); + + reg_destroy_proc(info.pid); + + reg_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + REG_TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_wait_proc(void) +{ + int rc = 0; + + rc |= test_wait_proc_timeout(); + rc |= test_wait_proc_success(); + + return rc; +} + +int reg_test(int argc, + char ** argv) +{ + int rc = 0; + + (void) argc; + (void) argv; + + rc |= test_reg_init(); + rc |= test_reg_flow(); + rc |= test_reg_ipcp(); + rc |= test_reg_name(); + rc |= test_reg_proc(); + rc |= test_reg_prog(); + rc |= test_reg_spawned(); + rc |= test_bind_proc(); + rc |= test_bind_prog(); + rc |= test_inherit_prog(); + rc |= test_wait_accepting(); + rc |= test_wait_ipcp_boot(); + rc |= test_wait_proc(); + + return rc; +} diff --git a/src/irmd/registry.c b/src/irmd/registry.c deleted file mode 100644 index 69e3ae97..00000000 --- a/src/irmd/registry.c +++ /dev/null @@ -1,615 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2021 - * - * The IPC Resource Manager - Registry - * - * Dimitri Staessens <dimitri@ouroboros.rocks> - * Sander Vrijders <sander@ouroboros.rocks> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., http://www.fsf.org/about/contact/. - */ - -#if defined(__linux__) || defined(__CYGWIN__) -#define _DEFAULT_SOURCE -#else -#define _POSIX_C_SOURCE 200809L -#endif - -#include "config.h" - -#define OUROBOROS_PREFIX "registry" - -#include <ouroboros/errno.h> -#include <ouroboros/logs.h> -#include <ouroboros/time_utils.h> -#include <ouroboros/pthread.h> - -#include "registry.h" -#include "utils.h" - -#include <stdlib.h> -#include <stdbool.h> -#include <string.h> -#include <signal.h> -#include <unistd.h> -#include <limits.h> -#include <assert.h> - -static struct reg_entry * reg_entry_create(void) -{ - struct reg_entry * e; - - e = malloc(sizeof(*e)); - if (e == NULL) - return NULL; - - e->name = NULL; - e->state = REG_NAME_NULL; - - return e; -} - -static int reg_entry_init(struct reg_entry * e, - char * name) -{ - pthread_condattr_t cattr; - - assert(e); - assert(name); - - list_head_init(&e->next); - list_head_init(&e->reg_progs); - list_head_init(&e->reg_pids); - - e->name = name; - e->pol_lb = 0; - - if (pthread_condattr_init(&cattr)) - goto fail_cattr; - -#ifndef __APPLE__ - pthread_condattr_setclock(&cattr, PTHREAD_COND_CLOCK); -#endif - if (pthread_cond_init(&e->state_cond, &cattr)) - goto fail_cond; - - if (pthread_mutex_init(&e->state_lock, NULL)) - goto fail_mutex; - - pthread_condattr_destroy(&cattr); - - e->state = REG_NAME_IDLE; - - return 0; - - fail_mutex: - pthread_cond_destroy(&e->state_cond); - fail_cond: - pthread_condattr_destroy(&cattr); - fail_cattr: - return -1; -} - -static void cancel_reg_entry_destroy(void * o) -{ - struct reg_entry * e; - struct list_head * p; - struct list_head * h; - - e = (struct reg_entry *) o; - - pthread_mutex_unlock(&e->state_lock); - - pthread_cond_destroy(&e->state_cond); - pthread_mutex_destroy(&e->state_lock); - - if (e->name != NULL) - free(e->name); - - list_for_each_safe(p, h, &e->reg_pids) { - struct pid_el * pe = list_entry(p, struct pid_el, next); - list_del(&pe->next); - free(pe); - } - - list_for_each_safe(p, h, &e->reg_progs) { - struct str_el * a = list_entry(p, struct str_el, next); - list_del(&a->next); - free(a->str); - free(a); - } - - free(e); -} - -static void reg_entry_destroy(struct reg_entry * e) -{ - if (e == NULL) - return; - - pthread_mutex_lock(&e->state_lock); - - if (e->state == REG_NAME_DESTROY) { - pthread_mutex_unlock(&e->state_lock); - return; - } - - if (e->state != REG_NAME_FLOW_ACCEPT) - e->state = REG_NAME_NULL; - else - e->state = REG_NAME_DESTROY; - - pthread_cond_broadcast(&e->state_cond); - - pthread_cleanup_push(cancel_reg_entry_destroy, e); - - while (e->state != REG_NAME_NULL) - pthread_cond_wait(&e->state_cond, &e->state_lock); - - pthread_cleanup_pop(true); -} - -static bool reg_entry_has_prog(struct reg_entry * e, - const char * prog) -{ - struct list_head * p; - - list_for_each(p, &e->reg_progs) { - struct str_el * e = list_entry(p, struct str_el, next); - if (!strcmp(e->str, prog)) - return true; - } - - return false; -} - -int reg_entry_add_prog(struct reg_entry * e, - struct prog_entry * a) -{ - struct str_el * n; - - if (reg_entry_has_prog(e, a->prog)) { - log_warn("Program %s already accepting flows for %s.", - a->prog, e->name); - return 0; - } - - if (!(a->flags & BIND_AUTO)) { - log_dbg("Program %s cannot be auto-instantiated.", a->prog); - return 0; - } - - n = malloc(sizeof(*n)); - if (n == NULL) - return -ENOMEM; - - n->str = strdup(a->prog); - if (n->str == NULL) { - free(n); - return -ENOMEM; - } - - list_add(&n->next, &e->reg_progs); - - pthread_mutex_lock(&e->state_lock); - - if (e->state == REG_NAME_IDLE) - e->state = REG_NAME_AUTO_ACCEPT; - - pthread_mutex_unlock(&e->state_lock); - - return 0; -} - -void reg_entry_del_prog(struct reg_entry * e, - const char * prog) -{ - struct list_head * p; - struct list_head * h; - - list_for_each_safe(p, h, &e->reg_progs) { - struct str_el * e = list_entry(p, struct str_el, next); - if (!strcmp(prog, e->str)) { - list_del(&e->next); - free(e->str); - free(e); - } - } - - pthread_mutex_lock(&e->state_lock); - - if (e->state == REG_NAME_AUTO_ACCEPT && list_is_empty(&e->reg_progs)) { - e->state = REG_NAME_IDLE; - pthread_cond_broadcast(&e->state_cond); - } - - pthread_mutex_unlock(&e->state_lock); -} - -char * reg_entry_get_prog(struct reg_entry * e) -{ - if (!list_is_empty(&e->reg_pids) || list_is_empty(&e->reg_progs)) - return NULL; - - return list_first_entry(&e->reg_progs, struct str_el, next)->str; -} - -static bool reg_entry_has_pid(struct reg_entry * e, - pid_t pid) -{ - struct list_head * p; - - list_for_each(p, &e->reg_progs) { - struct pid_el * e = list_entry(p, struct pid_el, next); - if (e->pid == pid) - return true; - } - - return false; -} - -int reg_entry_add_pid(struct reg_entry * e, - pid_t pid) -{ - struct pid_el * i; - - assert(e); - - if (reg_entry_has_pid(e, pid)) { - log_dbg("Process already registered with this name."); - return -EPERM; - } - - pthread_mutex_lock(&e->state_lock); - - if (e->state == REG_NAME_NULL) { - pthread_mutex_unlock(&e->state_lock); - log_dbg("Tried to add instance in NULL state."); - return -EPERM; - } - - i = malloc(sizeof(*i)); - if (i == NULL) { - pthread_mutex_unlock(&e->state_lock); - return -ENOMEM; - } - - i->pid = pid; - - /* load balancing policy assigns queue order for this process. */ - switch(e->pol_lb) { - case LB_RR: /* Round robin policy. */ - list_add_tail(&i->next, &e->reg_pids); - break; - case LB_SPILL: /* Keep accepting flows on the current process */ - list_add(&i->next, &e->reg_pids); - break; - default: - assert(false); - }; - - if (e->state == REG_NAME_IDLE || - e->state == REG_NAME_AUTO_ACCEPT || - e->state == REG_NAME_AUTO_EXEC) { - e->state = REG_NAME_FLOW_ACCEPT; - pthread_cond_broadcast(&e->state_cond); - } - - pthread_mutex_unlock(&e->state_lock); - - return 0; -} - -void reg_entry_set_policy(struct reg_entry * e, - enum pol_balance p) -{ - e->pol_lb = p; -} - - -static void reg_entry_check_state(struct reg_entry * e) -{ - assert(e); - - if (e->state == REG_NAME_DESTROY) { - e->state = REG_NAME_NULL; - pthread_cond_broadcast(&e->state_cond); - return; - } - - if (list_is_empty(&e->reg_pids)) { - if (!list_is_empty(&e->reg_progs)) - e->state = REG_NAME_AUTO_ACCEPT; - else - e->state = REG_NAME_IDLE; - } else { - e->state = REG_NAME_FLOW_ACCEPT; - } - - pthread_cond_broadcast(&e->state_cond); -} - -void reg_entry_del_pid_el(struct reg_entry * e, - struct pid_el * p) -{ - assert(e); - assert(p); - - list_del(&p->next); - free(p); - - reg_entry_check_state(e); -} - -void reg_entry_del_pid(struct reg_entry * e, - pid_t pid) -{ - struct list_head * p; - struct list_head * h; - - assert(e); - - if (e == NULL) - return; - - list_for_each_safe(p, h, &e->reg_pids) { - struct pid_el * a = list_entry(p, struct pid_el, next); - if (a->pid == pid) { - list_del(&a->next); - free(a); - } - } - - reg_entry_check_state(e); -} - -pid_t reg_entry_get_pid(struct reg_entry * e) -{ - if (e == NULL) - return -1; - - if (list_is_empty(&e->reg_pids)) - return -1; - - return list_first_entry(&e->reg_pids, struct pid_el, next)->pid; -} - -enum reg_name_state reg_entry_get_state(struct reg_entry * e) -{ - enum reg_name_state state; - - assert(e); - - pthread_mutex_lock(&e->state_lock); - - state = e->state; - - pthread_mutex_unlock(&e->state_lock); - - return state; -} - -int reg_entry_set_state(struct reg_entry * e, - enum reg_name_state state) -{ - assert(state != REG_NAME_DESTROY); - - pthread_mutex_lock(&e->state_lock); - - e->state = state; - pthread_cond_broadcast(&e->state_cond); - - pthread_mutex_unlock(&e->state_lock); - - return 0; -} - -int reg_entry_leave_state(struct reg_entry * e, - enum reg_name_state state, - struct timespec * timeout) -{ - struct timespec abstime; - int ret = 0; - - assert(e); - assert(state != REG_NAME_DESTROY); - - if (timeout != NULL) { - clock_gettime(PTHREAD_COND_CLOCK, &abstime); - ts_add(&abstime, timeout, &abstime); - } - - pthread_mutex_lock(&e->state_lock); - - pthread_cleanup_push(__cleanup_mutex_unlock, &e->state_lock); - - while (e->state == state && ret != -ETIMEDOUT) - if (timeout) - ret = -pthread_cond_timedwait(&e->state_cond, - &e->state_lock, - &abstime); - else - ret = -pthread_cond_wait(&e->state_cond, - &e->state_lock); - - if (e->state == REG_NAME_DESTROY) { - ret = -1; - e->state = REG_NAME_NULL; - pthread_cond_broadcast(&e->state_cond); - } - - pthread_cleanup_pop(true); - - return ret; -} - -int reg_entry_wait_state(struct reg_entry * e, - enum reg_name_state state, - struct timespec * timeout) -{ - struct timespec abstime; - int ret = 0; - - assert(e); - assert(state != REG_NAME_DESTROY); - - if (timeout != NULL) { - clock_gettime(PTHREAD_COND_CLOCK, &abstime); - ts_add(&abstime, timeout, &abstime); - } - - pthread_mutex_lock(&e->state_lock); - - while (e->state != state && - e->state != REG_NAME_DESTROY && - ret != -ETIMEDOUT) - if (timeout) - ret = -pthread_cond_timedwait(&e->state_cond, - &e->state_lock, - &abstime); - else - ret = -pthread_cond_wait(&e->state_cond, - &e->state_lock); - - if (e->state == REG_NAME_DESTROY) { - ret = -1; - e->state = REG_NAME_NULL; - pthread_cond_broadcast(&e->state_cond); - } - - pthread_mutex_unlock(&e->state_lock); - - return ret; -} - -struct reg_entry * registry_get_entry(struct list_head * registry, - const char * name) -{ - struct list_head * p = NULL; - - assert(registry); - - list_for_each(p, registry) { - struct reg_entry * e = list_entry(p, struct reg_entry, next); - if (!strcmp(name, e->name)) - return e; - } - - return NULL; -} - -struct reg_entry * registry_get_entry_by_hash(struct list_head * registry, - enum hash_algo algo, - const uint8_t * hash, - size_t len) -{ - struct list_head * p = NULL; - uint8_t * thash; - - thash = malloc(len); - if (thash == NULL) - return NULL; - - assert(registry); - - list_for_each(p, registry) { - struct reg_entry * e = list_entry(p, struct reg_entry, next); - str_hash(algo, thash, e->name); - if (memcmp(thash, hash, len) == 0) { - free(thash); - return e; - } - } - - free(thash); - - return NULL; -} - -struct reg_entry * registry_add_name(struct list_head * registry, - const char * name) -{ - struct reg_entry * e = NULL; - - assert(registry); - assert(name); - - if (registry_has_name(registry, name)) { - log_dbg("Name %s already registered.", name); - return NULL; - } - - e = reg_entry_create(); - if (e == NULL) { - log_dbg("Could not create registry entry."); - return NULL; - } - - if (reg_entry_init(e, strdup(name))) { - reg_entry_destroy(e); - log_dbg("Could not initialize registry entry."); - return NULL; - } - - list_add(&e->next, registry); - - return e; -} - -void registry_del_name(struct list_head * registry, - const char * name) -{ - struct reg_entry * e = registry_get_entry(registry, name); - if (e == NULL) - return; - - list_del(&e->next); - reg_entry_destroy(e); - - return; -} - -void registry_del_process(struct list_head * registry, - pid_t pid) -{ - struct list_head * p; - - assert(registry); - assert(pid > 0); - - list_for_each(p, registry) { - struct reg_entry * e = list_entry(p, struct reg_entry, next); - pthread_mutex_lock(&e->state_lock); - assert(e); - reg_entry_del_pid(e, pid); - pthread_mutex_unlock(&e->state_lock); - } - - return; -} - -void registry_destroy(struct list_head * registry) -{ - struct list_head * p = NULL; - struct list_head * h = NULL; - - assert(registry); - - list_for_each_safe(p, h, registry) { - struct reg_entry * e = list_entry(p, struct reg_entry, next); - list_del(&e->next); - reg_entry_set_state(e, REG_NAME_NULL); - reg_entry_destroy(e); - } -} diff --git a/src/irmd/registry.h b/src/irmd/registry.h deleted file mode 100644 index af34cffc..00000000 --- a/src/irmd/registry.h +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2021 - * - * The IPC Resource Manager - Registry - * - * Dimitri Staessens <dimitri@ouroboros.rocks> - * Sander Vrijders <sander@ouroboros.rocks> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., http://www.fsf.org/about/contact/. - */ - -#ifndef OUROBOROS_IRMD_REGISTRY_H -#define OUROBOROS_IRMD_REGISTRY_H - -#include <ouroboros/hash.h> -#include <ouroboros/ipcp.h> -#include <ouroboros/list.h> -#include <ouroboros/irm.h> - -#include "proc_table.h" -#include "prog_table.h" - -#include <stdint.h> -#include <stdbool.h> -#include <pthread.h> -#include <string.h> -#include <sys/types.h> - -#define registry_has_name(r, name) \ - (registry_get_entry(r, name) != NULL) - -enum reg_name_state { - REG_NAME_NULL = 0, - REG_NAME_IDLE, - REG_NAME_AUTO_ACCEPT, - REG_NAME_AUTO_EXEC, - REG_NAME_FLOW_ACCEPT, - REG_NAME_FLOW_ARRIVED, - REG_NAME_DESTROY -}; - -/* An entry in the registry */ -struct reg_entry { - struct list_head next; - char * name; - - /* Policies for this name. */ - enum pol_balance pol_lb; /* Load balance incoming flows. */ - /* Programs that can be instantiated by the irmd. */ - struct list_head reg_progs; - /* Processes that are listening for this name. */ - struct list_head reg_pids; - - enum reg_name_state state; - pthread_cond_t state_cond; - pthread_mutex_t state_lock; -}; - -int reg_entry_add_prog(struct reg_entry * e, - struct prog_entry * a); - -void reg_entry_del_prog(struct reg_entry * e, - const char * prog); - -char * reg_entry_get_prog(struct reg_entry * e); - -int reg_entry_add_pid(struct reg_entry * e, - pid_t pid); - -void reg_entry_del_pid(struct reg_entry * e, - pid_t pid); - -void reg_entry_del_pid_el(struct reg_entry * e, - struct pid_el * a); - -pid_t reg_entry_get_pid(struct reg_entry * e); - -void reg_entry_set_policy(struct reg_entry * e, - enum pol_balance p); - -enum reg_name_state reg_entry_get_state(struct reg_entry * e); - -int reg_entry_set_state(struct reg_entry * e, - enum reg_name_state state); - -int reg_entry_leave_state(struct reg_entry * e, - enum reg_name_state state, - struct timespec * timeout); - -int reg_entry_wait_state(struct reg_entry * e, - enum reg_name_state state, - struct timespec * timeout); - -struct reg_entry * registry_add_name(struct list_head * registry, - const char * name); - -void registry_del_name(struct list_head * registry, - const char * name); - -void registry_del_process(struct list_head * registry, - pid_t pid); - -void registry_sanitize_pids(struct list_head * registry); - -struct reg_entry * registry_get_entry(struct list_head * registry, - const char * name); - -struct reg_entry * registry_get_entry_by_hash(struct list_head * registry, - enum hash_algo algo, - const uint8_t * hash, - size_t len); - -void registry_destroy(struct list_head * registry); - -#endif /* OUROBOROS_IRMD_REGISTRY_H */ diff --git a/src/irmd/tests/CMakeLists.txt b/src/irmd/tests/CMakeLists.txt deleted file mode 100644 index 68bd762d..00000000 --- a/src/irmd/tests/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -get_filename_component(tmp ".." ABSOLUTE) -get_filename_component(src_folder "${tmp}" NAME) - -create_test_sourcelist(${src_folder}_tests test_suite.c - # Add new tests here -) - -add_executable(${src_folder}_test EXCLUDE_FROM_ALL ${${src_folder}_tests}) -target_link_libraries(${src_folder}_test ouroboros) - -add_dependencies(check ${src_folder}_test) - -set(tests_to_run ${${src_folder}_tests}) -remove(tests_to_run test_suite.c) - -foreach(test ${tests_to_run}) - get_filename_component(test_name ${test} NAME_WE) - add_test(${test_name} ${C_TEST_PATH}/${src_folder}_test ${test_name}) -endforeach(test) diff --git a/src/irmd/utils.c b/src/irmd/utils.c deleted file mode 100644 index 976dcfa2..00000000 --- a/src/irmd/utils.c +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2021 - * - * The IPC Resource Manager - Utilities - * - * Dimitri Staessens <dimitri@ouroboros.rocks> - * Sander Vrijders <sander@ouroboros.rocks> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., http://www.fsf.org/about/contact/. - */ - -#define _POSIX_C_SOURCE 200809L - -#include <stdlib.h> -#include <string.h> - -void argvfree(char ** argv) -{ - char ** argv_dup = argv; - if (argv == NULL) - return; - - while (*argv_dup != NULL) - free(*(argv_dup++)); - - free(argv); -} - -char ** argvdup(char ** argv) -{ - int argc = 0; - char ** argv_dup = argv; - int i; - - if (argv == NULL) - return NULL; - - while (*(argv_dup++) != NULL) - argc++; - - if (argc != 0) { - argv_dup = malloc((argc + 1) * sizeof(*argv_dup)); - for (i = 0; i < argc; ++i) { - argv_dup[i] = strdup(argv[i]); - if (argv_dup[i] == NULL) { - argvfree(argv_dup); - return NULL; - } - } - } - argv_dup[argc] = NULL; - return argv_dup; -} diff --git a/src/lib/.gitignore b/src/lib/.gitignore index 8704469b..3ecaf66d 100644 --- a/src/lib/.gitignore +++ b/src/lib/.gitignore @@ -1 +1 @@ -*.pb-c.[ch]
\ No newline at end of file +*.pb-c.[ch] diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index 022c5cca..c4306b00 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -1,301 +1,160 @@ -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) - -include_directories(${CMAKE_SOURCE_DIR}/include) -include_directories(${CMAKE_BINARY_DIR}/include) - -protobuf_generate_c(IRM_PROTO_SRCS IRM_PROTO_HDRS irmd_messages.proto) -protobuf_generate_c(IPCP_PROTO_SRCS IPCP_PROTO_HDRS ipcpd_messages.proto) -protobuf_generate_c(QOSSPEC_PROTO_SRCS QOSSPEC_PROTO_HDRS - qosspec.proto) -protobuf_generate_c(LAYER_CONFIG_PROTO_SRCS LAYER_CONFIG_PROTO_HDRS - ipcp_config.proto) -protobuf_generate_c(CACEP_PROTO_SRCS CACEP_PROTO_HDRS cacep.proto) - -if (NOT APPLE) - find_library(LIBRT_LIBRARIES rt) - if (NOT LIBRT_LIBRARIES) - message(FATAL_ERROR "Could not find librt") - endif () -else () - set(LIBRT_LIBRARIES "") -endif () - -find_library(LIBPTHREAD_LIBRARIES pthread) -if (NOT LIBPTHREAD_LIBRARIES) - message(FATAL_ERROR "Could not find libpthread") -endif () - -include(CheckSymbolExists) -list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_POSIX_C_SOURCE=200809L) -list(APPEND CMAKE_REQUIRED_DEFINITIONS -D__XSI_VISIBLE=500) -list(APPEND CMAKE_REQUIRED_LIBRARIES pthread) -check_symbol_exists(pthread_mutexattr_setrobust pthread.h HAVE_ROBUST_MUTEX) - -if (HAVE_ROBUST_MUTEX) - set(DISABLE_ROBUST_MUTEXES FALSE CACHE BOOL "Disable robust mutex support") - if (NOT DISABLE_ROBUST_MUTEXES) - message(STATUS "Robust mutex support enabled") - set(HAVE_ROBUST_MUTEX TRUE) - else () - message(STATUS "Robust mutex support disabled by user") - unset(HAVE_ROBUST_MUTEX) - endif () -endif () - -find_library(FUSE_LIBRARIES fuse QUIET) -if (FUSE_LIBRARIES) - #FIXME: Check for version >= 2.6 - set(DISABLE_FUSE FALSE CACHE BOOL "Disable FUSE support") - if (NOT DISABLE_FUSE) - message(STATUS "FUSE support enabled") - set(FUSE_PREFIX "/tmp/ouroboros" CACHE STRING - "Mountpoint for RIB filesystem") - set(HAVE_FUSE TRUE CACHE INTERNAL "") - else () - message(STATUS "FUSE support disabled by user") - unset(HAVE_FUSE CACHE) - endif () -else () - message(STATUS "Install FUSE version > 2.6 to enable RIB access") -endif () - -if (NOT HAVE_FUSE) - set(FUSE_LIBRARIES "") - set(FUSE_INCLUDE_DIR "") -endif () - -mark_as_advanced(FUSE_LIBRARIES) - -find_library(LIBGCRYPT_LIBRARIES gcrypt QUIET) -if (LIBGCRYPT_LIBRARIES) - find_path(LIBGCRYPT_INCLUDE_DIR gcrypt.h - HINTS /usr/include /usr/local/include) - if (LIBGCRYPT_INCLUDE_DIR) - file(STRINGS ${LIBGCRYPT_INCLUDE_DIR}/gcrypt.h GCSTR - REGEX "^#define GCRYPT_VERSION ") - string(REGEX REPLACE "^#define GCRYPT_VERSION \"(.*)\".*$" "\\1" - GCVER "${GCSTR}") - if (NOT GCVER VERSION_LESS "1.7.0") - set(DISABLE_LIBGCRYPT FALSE CACHE BOOL "Disable libgcrypt support") - if (NOT DISABLE_LIBGCRYPT) - message(STATUS "libgcrypt support enabled") - set(HAVE_LIBGCRYPT TRUE CACHE INTERNAL "") - else () - message(STATUS "libgcrypt support disabled by user") - unset(HAVE_LIBGCRYPT CACHE) - endif() - else () - message(STATUS "Install version >= \"1.7.0\" to enable libgcrypt support " - "(found version \"${GCVER}\")") - endif() - endif () -endif () - -if (NOT HAVE_LIBGCRYPT) - set(LIBGCRYPT_LIBRARIES "") - set(LIBGCRYPT_INCLUDE_DIR "") -endif () - -find_package(OpenSSL QUIET) -if (OPENSSL_FOUND) - set(HAVE_OPENSSL_RNG TRUE) - if (OPENSSL_VERSION VERSION_LESS "1.1.0") - message(STATUS "Install version >= \"1.1.0\" to enable OpenSSL support " - "(found version \"${OPENSSL_VERSION}\")") - else () - set(DISABLE_OPENSSL FALSE CACHE BOOL "Disable OpenSSL support") - if (NOT DISABLE_OPENSSL) - message(STATUS "OpenSSL support enabled") - set(HAVE_OPENSSL TRUE) - else() - message(STATUS "OpenSSL support disabled") - unset(HAVE_OPENSSL) - endif() - endif () -endif () - -if (NOT HAVE_OPENSSL_RNG) - set(OPENSSL_INCLUDE_DIR "") - set(OPENSSL_LIBRARIES "") -endif () - -if (APPLE OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") - set(SYS_RND_HDR "") -else () - find_path(SYS_RND_HDR sys/random.h PATH /usr/include/ /usr/local/include/) - if (SYS_RND_HDR) - message(STATUS "Found sys/random.h in ${SYS_RND_HDR}") - set(HAVE_SYS_RANDOM TRUE) - else () - set(SYS_RND_HDR "") - unset(HAVE_SYS_RANDOM) - endif () -endif() - -if (NOT ((CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") OR APPLE OR - HAVE_SYS_RANDOM OR HAVE_OPENSSL_RNG OR HAVE_LIBGCRYPT)) - message(FATAL_ERROR "No secure random generator found, " - "please install libgcrypt (> 1.7.0) or OpenSSL") -endif () - -mark_as_advanced(LIBRT_LIBRARIES LIBPTHREAD_LIBRARIES - LIBGCRYPT_LIBRARIES OPENSSL_LIBRARIES SYS_RND_INCLUDE_DIR - LIBGCRYPT_INCLUDE_DIR SYS_RND_HDR) - -set(SHM_BUFFER_SIZE 4096 CACHE STRING - "Number of blocks in packet buffer, must be a power of 2") -set(SHM_RBUFF_SIZE 1024 CACHE STRING - "Number of blocks in rbuff buffer, must be a power of 2") -set(SYS_MAX_FLOWS 10240 CACHE STRING - "Maximum number of total flows for this system") -set(PROG_MAX_FLOWS 4096 CACHE STRING - "Maximum number of flows in an application") -set(PROG_RES_FDS 64 CACHE STRING - "Number of reserved flow descriptors per application") -set(PROG_MAX_FQUEUES 32 CACHE STRING - "Maximum number of flow sets per application") -set(DU_BUFF_HEADSPACE 256 CACHE STRING - "Bytes of headspace to reserve for future headers") -set(DU_BUFF_TAILSPACE 32 CACHE STRING - "Bytes of tailspace to reserve for future tails") -if (NOT APPLE) - set(PTHREAD_COND_CLOCK "CLOCK_MONOTONIC" CACHE STRING - "Clock to use for condition variable timing") -else () - set(PTHREAD_COND_CLOCK "CLOCK_REALTIME" CACHE INTERNAL - "Clock to use for condition variable timing") -endif () -set(SOCKET_TIMEOUT 1000 CACHE STRING - "Default timeout for responses from IPCPs (ms)") -set(SHM_PREFIX "ouroboros" CACHE STRING - "String to prepend to POSIX shared memory filenames") -set(SHM_RBUFF_PREFIX "/${SHM_PREFIX}.rbuff." CACHE INTERNAL - "Prefix for rbuff POSIX shared memory filenames") -set(SHM_LOCKFILE_NAME "/${SHM_PREFIX}.lockfile" CACHE INTERNAL - "Filename for the POSIX shared memory lockfile") -set(SHM_FLOW_SET_PREFIX "/${SHM_PREFIX}.set." CACHE INTERNAL - "Prefix for the POSIX shared memory flow set") -set(SHM_RDRB_NAME "/${SHM_PREFIX}.rdrb" CACHE INTERNAL - "Name for the main POSIX shared memory buffer") -set(SHM_RDRB_BLOCK_SIZE "sysconf(_SC_PAGESIZE)" CACHE STRING - "Packet buffer block size, multiple of pagesize for performance") -set(SHM_RDRB_MULTI_BLOCK TRUE CACHE BOOL - "Packet buffer multiblock packet support") -set(SHM_RBUFF_LOCKLESS FALSE CACHE BOOL - "Enable shared memory lockless rbuff support") -set(QOS_DISABLE_CRC TRUE CACHE BOOL - "Ignores ber setting on all QoS cubes") -set(DELTA_T_MPL 60 CACHE STRING - "Maximum packet lifetime (s)") -set(DELTA_T_ACK 10 CACHE STRING - "Maximum time to acknowledge a packet (s)") -set(DELTA_T_RTX 120 CACHE STRING - "Maximum time to retransmit a packet (s)") -set(DELTA_T_ACK_DELAY 10 CACHE STRING - "Maximum time to wait before acknowledging a packet (ms)") -set(FRCT_REORDER_QUEUE_SIZE 256 CACHE STRING - "Size of the reordering queue, must be a power of 2") -set(FRCT_START_WINDOW 64 CACHE STRING - "Start window, must be a power of 2") -set(FRCT_RTO_MIN 250 CACHE STRING - "Minimum Retransmission Timeout (RTO) for FRCT (us)") -set(FRCT_TICK_TIME 5000 CACHE STRING - "Tick time for FRCT activity (retransmission, acknowledgments) (us)") -set(RXM_BUFFER_ON_HEAP FALSE CACHE BOOL - "Store packets for retransmission on the heap instead of in packet buffer") -set(RXM_BLOCKING TRUE CACHE BOOL - "Use blocking writes for retransmission") -set(RXM_MIN_RESOLUTION 20 CACHE STRING - "Minimum retransmission delay (ns), as a power to 2") -set(RXM_WHEEL_MULTIPLIER 4 CACHE STRING - "Factor for retransmission wheel levels as a power to 2") -set(RXM_WHEEL_LEVELS 3 CACHE STRING - "Number of levels in the retransmission wheel") -set(RXM_WHEEL_SLOTS_PER_LEVEL 256 CACHE STRING - "Number of slots per level in the retransmission wheel, must be a power of 2") -set(ACK_WHEEL_SLOTS 128 CACHE STRING - "Number of slots in the acknowledgment wheel, must be a power of 2") -set(ACK_WHEEL_RESOLUTION 20 CACHE STRING - "Minimum acknowledgment delay (ns), as a power to 2") - -if (HAVE_FUSE) - set(PROC_FLOW_STATS TRUE CACHE BOOL - "Enable flow statistics tracking for application flows") - if (PROC_FLOW_STATS) - message(STATUS "Application flow statistics enabled") - else () - message(STATUS "Application flow statistics disabled") - endif () -endif () - -set(SOURCE_FILES_DEV - # Add source files here - cacep.c - dev.c - ) - -set(SOURCE_FILES_IRM - irm.c -) +# Ouroboros libraries build configuration +# Configuration options are in cmake/config/lib.cmake + +protobuf_generate_c(MODEL_PROTO_SRCS MODEL_PROTO_HDRS + "${CMAKE_CURRENT_SOURCE_DIR}/pb/model.proto") +protobuf_generate_c(IPCP_CONFIG_PROTO_SRCS IPCP_CONFIG_PROTO_HDRS + "${CMAKE_CURRENT_SOURCE_DIR}/pb/ipcp_config.proto") +protobuf_generate_c(ENROLL_PROTO_SRCS ENROLL_PROTO_HDRS + "${CMAKE_CURRENT_SOURCE_DIR}/pb/enroll.proto") +protobuf_generate_c(CEP_PROTO_SRCS CEP_PROTO_HDRS + "${CMAKE_CURRENT_SOURCE_DIR}/pb/cep.proto") +protobuf_generate_c(IRM_PROTO_SRCS IRM_PROTO_HDRS + "${CMAKE_CURRENT_SOURCE_DIR}/pb/irm.proto") +protobuf_generate_c(IPCP_PROTO_SRCS IPCP_PROTO_HDRS + "${CMAKE_CURRENT_SOURCE_DIR}/pb/ipcp.proto") set(SOURCE_FILES_COMMON bitmap.c btree.c crc32.c + crypt.c hash.c list.c lockfile.c logs.c md5.c notifier.c + protobuf.c qoscube.c random.c rib.c + serdes-irm.c + serdes-oep.c sha3.c - shm_flow_set.c - shm_rbuff.c - shm_rdrbuff.c + ssm/flow_set.c + ssm/rbuff.c + ssm/pool.c sockets.c tpm.c utils.c ) -configure_file("${CMAKE_CURRENT_SOURCE_DIR}/config.h.in" - "${CMAKE_CURRENT_BINARY_DIR}/config.h" @ONLY) +if(HAVE_OPENSSL) + list(APPEND SOURCE_FILES_COMMON crypt/openssl.c) +endif() + +add_library(ouroboros-common SHARED + ${SOURCE_FILES_COMMON} + ${IRM_PROTO_SRCS} + ${IPCP_PROTO_SRCS} + ${IPCP_CONFIG_PROTO_SRCS} + ${MODEL_PROTO_SRCS} + ${ENROLL_PROTO_SRCS}) + +set_target_properties(ouroboros-common PROPERTIES + VERSION ${PACKAGE_VERSION} + SOVERSION ${PACKAGE_VERSION_MAJOR}.${PACKAGE_VERSION_MINOR}) + +ouroboros_target_debug_definitions(ouroboros-common) + +target_include_directories(ouroboros-common + PUBLIC + $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include> + $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/include> + $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}> + $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}> + $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}> + $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> + PRIVATE + ${SYS_RND_HDR} + ${APPLE_INCLUDE_DIRS}) + +target_link_libraries(ouroboros-common + PRIVATE + ${LIBRT_LIBRARIES} + Threads::Threads + PUBLIC + ProtobufC::ProtobufC) + +if(HAVE_OPENSSL) + target_link_libraries(ouroboros-common PUBLIC OpenSSL::Crypto) +endif() + +if(HAVE_LIBGCRYPT) + target_link_libraries(ouroboros-common PUBLIC Gcrypt::Gcrypt) +endif() + +if(HAVE_FUSE) + target_link_libraries(ouroboros-common PRIVATE Fuse::Fuse) +endif() + +install(TARGETS ouroboros-common + EXPORT OuroborosTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) -add_library(ouroboros-common SHARED ${SOURCE_FILES_COMMON} ${IRM_PROTO_SRCS} - ${IPCP_PROTO_SRCS} ${LAYER_CONFIG_PROTO_SRCS} ${QOSSPEC_PROTO_SRCS}) +set(SOURCE_FILES_DEV + cep.c + dev.c +) + +add_library(ouroboros-dev SHARED + ${SOURCE_FILES_DEV} + ${CEP_PROTO_SRCS}) + +set_target_properties(ouroboros-dev PROPERTIES + VERSION ${PACKAGE_VERSION} + SOVERSION ${PACKAGE_VERSION_MAJOR}.${PACKAGE_VERSION_MINOR}) + +ouroboros_target_debug_definitions(ouroboros-dev) -add_library(ouroboros-dev SHARED ${SOURCE_FILES_DEV} ${CACEP_PROTO_SRCS}) +target_include_directories(ouroboros-dev + PUBLIC + $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include> + $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_BINARY_DIR} + ${SYS_RND_HDR}) -add_library(ouroboros-irm SHARED ${SOURCE_FILES_IRM}) +target_link_libraries(ouroboros-dev PUBLIC ouroboros-common) -include(AddCompileFlags) -if (CMAKE_BUILD_TYPE MATCHES "Debug*") - add_compile_flags(ouroboros-common -DCONFIG_OUROBOROS_DEBUG) - add_compile_flags(ouroboros-dev -DCONFIG_OUROBOROS_DEBUG) - add_compile_flags(ouroboros-irm -DCONFIG_OUROBOROS_DEBUG) -endif () +install(TARGETS ouroboros-dev + EXPORT OuroborosTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) -target_link_libraries(ouroboros-common ${LIBRT_LIBRARIES} - ${LIBPTHREAD_LIBRARIES} ${PROTOBUF_C_LIBRARY} ${OPENSSL_LIBRARIES} - ${LIBGCRYPT_LIBRARIES} ${FUSE_LIBRARIES}) +add_library(ouroboros-irm SHARED irm.c) -target_link_libraries(ouroboros-dev ouroboros-common) -target_link_libraries(ouroboros-irm ouroboros-common) +set_target_properties(ouroboros-irm PROPERTIES + VERSION ${PACKAGE_VERSION} + SOVERSION ${PACKAGE_VERSION_MAJOR}.${PACKAGE_VERSION_MINOR}) -install(TARGETS ouroboros-common LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) -install(TARGETS ouroboros-dev LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) -install(TARGETS ouroboros-irm LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) +ouroboros_target_debug_definitions(ouroboros-irm) -target_include_directories(ouroboros-common PUBLIC ${CMAKE_CURRENT_BINARY_DIR} - ${SYS_RND_HDR} ${LIBGCRYPT_INCLUDE_DIR} ${OPENSSL_INCLUDE_DIR}) +target_include_directories(ouroboros-irm + PUBLIC + $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include> + $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> + PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CMAKE_BINARY_DIR} + ${SYS_RND_HDR}) -target_include_directories(ouroboros-dev PUBLIC ${CMAKE_CURRENT_BINARY_DIR} - ${SYS_RND_HDR} ${LIBGCRYPT_INCLUDE_DIR} ${OPENSSL_INCLUDE_DIR}) +target_link_libraries(ouroboros-irm PUBLIC ouroboros-common) -target_include_directories(ouroboros-irm PUBLIC ${CMAKE_CURRENT_BINARY_DIR} - ${SYS_RND_HDR} ${LIBGCRYPT_INCLUDE_DIR} ${OPENSSL_INCLUDE_DIR}) +install(TARGETS ouroboros-irm + EXPORT OuroborosTargets + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) -add_subdirectory(tests) +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/config.h.in" + "${CMAKE_CURRENT_BINARY_DIR}/config.h" @ONLY) + +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/ssm/ssm.h.in" + "${CMAKE_CURRENT_BINARY_DIR}/ssm.h" @ONLY) + +if(BUILD_TESTS) + add_subdirectory(tests) + add_subdirectory(ssm/tests) +endif() diff --git a/src/lib/bitmap.c b/src/lib/bitmap.c index 0c551960..b0840c44 100644 --- a/src/lib/bitmap.c +++ b/src/lib/bitmap.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Bitmap implementation * diff --git a/src/lib/btree.c b/src/lib/btree.c index 64b8689e..1af94b73 100644 --- a/src/lib/btree.c +++ b/src/lib/btree.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * B-trees * diff --git a/src/lib/cacep.c b/src/lib/cep.c index e8d21d8e..ba238023 100644 --- a/src/lib/cacep.c +++ b/src/lib/cep.c @@ -1,7 +1,7 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * - * The Common Application Connection Establishment Protocol + * The Ouroboros Connection Establishment Protocol * * Dimitri Staessens <dimitri@ouroboros.rocks> * Sander Vrijders <sander@ouroboros.rocks> @@ -22,35 +22,35 @@ #define _POSIX_C_SOURCE 199309L -#include <ouroboros/cacep.h> +#include <ouroboros/cep.h> #include <ouroboros/dev.h> #include <ouroboros/errno.h> #include <stdlib.h> #include <string.h> -#include "cacep.pb-c.h" -typedef CacepMsg cacep_msg_t; +#include "cep.pb-c.h" +typedef CepMsg cep_msg_t; #define BUF_SIZE 128 static int read_msg(int fd, struct conn_info * info) { - uint8_t buf[BUF_SIZE]; - cacep_msg_t * msg; - ssize_t len; + uint8_t buf[BUF_SIZE]; + cep_msg_t * msg; + ssize_t len; len = flow_read(fd, buf, BUF_SIZE); if (len < 0) - return -1; + return (int) len; - msg = cacep_msg__unpack(NULL, len, buf); + msg = cep_msg__unpack(NULL, len, buf); if (msg == NULL) return -1; - if (strlen(msg->comp_name) > CACEP_BUF_STRLEN) { - cacep_msg__free_unpacked(msg, NULL); + if (strlen(msg->comp_name) > OCEP_BUF_STRLEN) { + cep_msg__free_unpacked(msg, NULL); return -1; } @@ -61,7 +61,7 @@ static int read_msg(int fd, info->pref_syntax = msg->pref_syntax; info->addr = msg->address; - cacep_msg__free_unpacked(msg, NULL); + cep_msg__free_unpacked(msg, NULL); return 0; } @@ -69,9 +69,9 @@ static int read_msg(int fd, static int send_msg(int fd, const struct conn_info * info) { - cacep_msg_t msg = CACEP_MSG__INIT; - uint8_t * data = NULL; - size_t len = 0; + cep_msg_t msg = CEP_MSG__INIT; + uint8_t * data = NULL; + size_t len = 0; msg.comp_name = (char *) info->comp_name; msg.protocol = (char *) info->protocol; @@ -81,7 +81,7 @@ static int send_msg(int fd, if (msg.pref_syntax < 0) return -1; - len = cacep_msg__get_packed_size(&msg); + len = cep_msg__get_packed_size(&msg); if (len == 0) return -1; @@ -89,7 +89,7 @@ static int send_msg(int fd, if (data == NULL) return -ENOMEM; - cacep_msg__pack(&msg, data); + cep_msg__pack(&msg, data); if (flow_write(fd, data, len) < 0) { free(data); @@ -101,26 +101,20 @@ static int send_msg(int fd, return 0; } -int cacep_snd(int fd, - const struct conn_info * in) +int cep_snd(int fd, + const struct conn_info * in) { if (in == NULL) return -EINVAL; - if (send_msg(fd, in)) - return -1; - - return 0; + return send_msg(fd, in); } -int cacep_rcv(int fd, - struct conn_info * out) +int cep_rcv(int fd, + struct conn_info * out) { if (out == NULL) return -EINVAL; - if (read_msg(fd, out)) - return -1; - - return 0; + return read_msg(fd, out); } diff --git a/src/lib/config.h.in b/src/lib/config.h.in index 5c5b6caf..6065ac41 100644 --- a/src/lib/config.h.in +++ b/src/lib/config.h.in @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Ouroboros library configuration * @@ -21,27 +21,26 @@ */ #cmakedefine HAVE_SYS_RANDOM +#cmakedefine HAVE_EXPLICIT_BZERO #cmakedefine HAVE_LIBGCRYPT #cmakedefine HAVE_OPENSSL - #ifdef HAVE_OPENSSL +#cmakedefine HAVE_OPENSSL_PQC #define HAVE_ENCRYPTION +#define SECMEM_GUARD @SECMEM_GUARD@ #endif +#define PROC_SECMEM_MAX @PROC_SECMEM_MAX@ + +#define SYS_MAX_FLOWS @SYS_MAX_FLOWS@ -#define SYS_MAX_FLOWS @SYS_MAX_FLOWS@ +#cmakedefine QOS_DISABLE_CRC +#cmakedefine HAVE_OPENSSL_RNG -#cmakedefine SHM_RBUFF_LOCKLESS -#cmakedefine SHM_RDRB_MULTI_BLOCK -#cmakedefine QOS_DISABLE_CRC -#cmakedefine HAVE_OPENSSL_RNG +#define SHM_LOCKFILE_NAME "@SHM_LOCKFILE_NAME@" +#define FLOW_ALLOC_TIMEOUT @FLOW_ALLOC_TIMEOUT@ -#define SHM_RBUFF_PREFIX "@SHM_RBUFF_PREFIX@" -#define SHM_LOCKFILE_NAME "@SHM_LOCKFILE_NAME@" -#define SHM_FLOW_SET_PREFIX "@SHM_FLOW_SET_PREFIX@" -#define SHM_RDRB_NAME "@SHM_RDRB_NAME@" -#define SHM_RDRB_BLOCK_SIZE @SHM_RDRB_BLOCK_SIZE@ -#define SHM_BUFFER_SIZE @SHM_BUFFER_SIZE@ -#define SHM_RBUFF_SIZE @SHM_RBUFF_SIZE@ +#define TPM_DEBUG_REPORT_INTERVAL @TPM_DEBUG_REPORT_INTERVAL@ +#define TPM_DEBUG_ABORT_TIMEOUT @TPM_DEBUG_ABORT_TIMEOUT@ #if defined(__linux__) || (defined(__MACH__) && !defined(__APPLE__)) /* Avoid a bug in robust mutex implementation of glibc 2.25 */ @@ -65,19 +64,16 @@ #define PROG_RES_FDS @PROG_RES_FDS@ #define PROG_MAX_FQUEUES @PROG_MAX_FQUEUES@ -#define DU_BUFF_HEADSPACE @DU_BUFF_HEADSPACE@ -#define DU_BUFF_TAILSPACE @DU_BUFF_TAILSPACE@ - /* Default Delta-t parameters */ -#define DELT_MPL (@DELTA_T_MPL@ * BILLION) /* ns */ -#define DELT_A (@DELTA_T_ACK@ * BILLION) /* ns */ -#define DELT_R (@DELTA_T_RTX@ * BILLION) /* ns */ - -#define DELT_ACK (@DELTA_T_ACK_DELAY@ * MILLION) /* ns */ +#cmakedefine FRCT_LINUX_RTT_ESTIMATOR +#define DELT_A (@DELTA_T_ACK@) /* ns */ +#define DELT_R (@DELTA_T_RTX@) /* ns */ #define RQ_SIZE (@FRCT_REORDER_QUEUE_SIZE@) #define START_WINDOW (@FRCT_START_WINDOW@) #define RTO_MIN (@FRCT_RTO_MIN@ * 1000) +#define RTO_DIV (@FRCT_RTO_INC_FACTOR@) +#define MDEV_MUL (@FRCT_RTO_MDEV_MULTIPLIER@) #define TICTIME (@FRCT_TICK_TIME@ * 1000) /* ns */ @@ -92,3 +88,5 @@ #define ACKQ_SLOTS (@ACK_WHEEL_SLOTS@) #define ACKQ_RES (@ACK_WHEEL_RESOLUTION@) /* 2^N ns */ + +#define KEY_ROTATION_BIT (@KEY_ROTATION_BIT@) /* Bit for key rotation */ diff --git a/src/lib/crc32.c b/src/lib/crc32.c index cd267faf..f369ad20 100644 --- a/src/lib/crc32.c +++ b/src/lib/crc32.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * 32-bit Cyclic Redundancy Check * diff --git a/src/lib/crypt.c b/src/lib/crypt.c index 070f5113..8c29cbb3 100644 --- a/src/lib/crypt.c +++ b/src/lib/crypt.c @@ -1,8 +1,7 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * - * Elliptic curve Diffie-Hellman key exchange and - * AES encryption for flows using OpenSSL + * Cryptographic operations * * Dimitri Staessens <dimitri@ouroboros.rocks> * Sander Vrijders <sander@ouroboros.rocks> @@ -21,428 +20,1072 @@ * Foundation, Inc., http://www.fsf.org/about/contact/. */ -#ifdef HAVE_OPENSSL +#if defined(__linux__) || defined(__CYGWIN__) +#define _DEFAULT_SOURCE +#endif + +#include <config.h> + +#include <ouroboros/errno.h> +#include <ouroboros/random.h> +#include <ouroboros/crypt.h> +#ifdef HAVE_OPENSSL #include <openssl/evp.h> -#include <openssl/ec.h> -#include <openssl/pem.h> +#include "crypt/openssl.h" +#endif -#include <openssl/bio.h> +#include <assert.h> +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> + +struct nid_map { + uint16_t nid; + const char * name; +}; + +static const struct nid_map cipher_nid_map[] = { + {NID_aes_128_gcm, "aes-128-gcm"}, + {NID_aes_192_gcm, "aes-192-gcm"}, + {NID_aes_256_gcm, "aes-256-gcm"}, + {NID_chacha20_poly1305, "chacha20-poly1305"}, + {NID_aes_128_ctr, "aes-128-ctr"}, + {NID_aes_192_ctr, "aes-192-ctr"}, + {NID_aes_256_ctr, "aes-256-ctr"}, + {NID_undef, NULL} +}; + +const uint16_t crypt_supported_nids[] = { +#ifdef HAVE_OPENSSL + NID_aes_128_gcm, + NID_aes_192_gcm, + NID_aes_256_gcm, + NID_chacha20_poly1305, + NID_aes_128_ctr, + NID_aes_192_ctr, + NID_aes_256_ctr, +#endif + NID_undef +}; + +static const struct nid_map kex_nid_map[] = { + {NID_X9_62_prime256v1, "prime256v1"}, + {NID_secp384r1, "secp384r1"}, + {NID_secp521r1, "secp521r1"}, + {NID_X25519, "X25519"}, + {NID_X448, "X448"}, + {NID_ffdhe2048, "ffdhe2048"}, + {NID_ffdhe3072, "ffdhe3072"}, + {NID_ffdhe4096, "ffdhe4096"}, + {NID_MLKEM512, "ML-KEM-512"}, + {NID_MLKEM768, "ML-KEM-768"}, + {NID_MLKEM1024, "ML-KEM-1024"}, + {NID_X25519MLKEM768, "X25519MLKEM768"}, + {NID_X448MLKEM1024, "X448MLKEM1024"}, + {NID_undef, NULL} +}; + +const uint16_t kex_supported_nids[] = { +#ifdef HAVE_OPENSSL + NID_X9_62_prime256v1, + NID_secp384r1, + NID_secp521r1, + NID_X25519, + NID_X448, + NID_ffdhe2048, + NID_ffdhe3072, + NID_ffdhe4096, +#ifdef HAVE_OPENSSL_PQC + NID_MLKEM512, + NID_MLKEM768, + NID_MLKEM1024, + NID_X25519MLKEM768, + NID_X448MLKEM1024, +#endif +#endif + NID_undef +}; + +static const struct nid_map md_nid_map[] = { + {NID_sha256, "sha256"}, + {NID_sha384, "sha384"}, + {NID_sha512, "sha512"}, + {NID_sha3_256, "sha3-256"}, + {NID_sha3_384, "sha3-384"}, + {NID_sha3_512, "sha3-512"}, + {NID_blake2b512, "blake2b512"}, + {NID_blake2s256, "blake2s256"}, + {NID_undef, NULL} +}; + +const uint16_t md_supported_nids[] = { +#ifdef HAVE_OPENSSL + NID_sha256, + NID_sha384, + NID_sha512, + NID_sha3_256, + NID_sha3_384, + NID_sha3_512, + NID_blake2b512, + NID_blake2s256, +#endif + NID_undef +}; -#define IVSZ 16 -/* SYMMKEYSZ defined in dev.c */ +struct crypt_ctx { + void * ctx; /* Encryption context */ +}; -/* - * Derive the common secret from - * your public key pair (kp) - * the remote public key (pub). - * Store it in a preallocated buffer (s). - */ -static int __openssl_ecdh_derive_secret(EVP_PKEY * kp, - EVP_PKEY * pub, - uint8_t * s) +struct auth_ctx { + void * store; +}; + +static int parse_kex_value(const char * value, + struct sec_config * cfg) { - EVP_PKEY_CTX * ctx; - int ret; - uint8_t * secret; - size_t secret_len; + SET_KEX_ALGO(cfg, value); + if (cfg->x.nid == NID_undef) + return -ENOTSUP; - ctx = EVP_PKEY_CTX_new(kp, NULL); - if (ctx == NULL) - goto fail_new; + return 0; +} - ret = EVP_PKEY_derive_init(ctx); - if (ret != 1) - goto fail_ctx; +/* not in header, but non-static for unit testing */ +int parse_sec_config(struct sec_config * cfg, + FILE * fp) +{ + char line[256]; + char * equals; + char * key; + char * value; + + assert(cfg != NULL); + assert(fp != NULL); + + /* Set defaults */ + SET_KEX_ALGO_NID(cfg, NID_X9_62_prime256v1); + cfg->x.mode = KEM_MODE_SERVER_ENCAP; + SET_KEX_KDF_NID(cfg, NID_sha256); + SET_KEX_CIPHER_NID(cfg, NID_aes_256_gcm); + SET_KEX_DIGEST_NID(cfg, NID_sha256); + + while (fgets(line, sizeof(line), fp) != NULL) { + char * trimmed; + + /* Skip comments and empty lines */ + if (line[0] == '#' || line[0] == '\n') + continue; + + /* Check for 'none' keyword */ + trimmed = trim_whitespace(line); + if (strcmp(trimmed, "none") == 0) { + memset(cfg, 0, sizeof(*cfg)); + return 0; + } + + /* Find the = separator */ + equals = strchr(line, '='); + if (equals == NULL) + continue; + + /* Split into key and value */ + *equals = '\0'; + key = trim_whitespace(line); + value = trim_whitespace(equals + 1); + + /* Parse key exchange field */ + if (strcmp(key, "kex") == 0) { + if (parse_kex_value(value, cfg) < 0) + return -EINVAL; + } else if (strcmp(key, "cipher") == 0) { + SET_KEX_CIPHER(cfg, value); + if (cfg->c.nid == NID_undef) + return -EINVAL; + } else if (strcmp(key, "kdf") == 0) { + SET_KEX_KDF(cfg, value); + if (cfg->k.nid == NID_undef) + return -EINVAL; + } else if (strcmp(key, "digest") == 0) { + SET_KEX_DIGEST(cfg, value); + if (cfg->d.nid == NID_undef) + return -EINVAL; + } else if (strcmp(key, "kem_mode") == 0) { + if (strcmp(value, "server") == 0) { + cfg->x.mode = KEM_MODE_SERVER_ENCAP; + } else if (strcmp(value, "client") == 0) { + cfg->x.mode = KEM_MODE_CLIENT_ENCAP; + } else { + return -EINVAL; + } + } + } - ret = EVP_PKEY_derive_set_peer(ctx, pub); - if (ret != 1) - goto fail_ctx; + return 0; +} - ret = EVP_PKEY_derive(ctx, NULL, &secret_len); - if (ret != 1) - goto fail_ctx; +/* Parse key exchange config from file */ +int load_sec_config_file(struct sec_config * cfg, + const char * path) +{ + FILE * fp; + int ret; - if (secret_len < SYMMKEYSZ) - goto fail_ctx; + assert(cfg != NULL); + assert(path != NULL); - secret = OPENSSL_malloc(secret_len); - if (secret == NULL) - goto fail_ctx; + fp = fopen(path, "r"); + if (fp == NULL) { + /* File doesn't exist - disable encryption */ + CLEAR_KEX_ALGO(cfg); + return 0; + } + + ret = parse_sec_config(cfg, fp); + + fclose(fp); + + return ret; +} + +int kex_pkp_create(struct sec_config * cfg, + void ** pkp, + uint8_t * pk) +{ +#ifdef HAVE_OPENSSL + assert(cfg != NULL); + assert(pkp != NULL); + + *pkp = NULL; - ret = EVP_PKEY_derive(ctx, secret, &secret_len); - if (ret != 1) - goto fail_derive; + if (cfg->x.str == NULL || kex_validate_nid(cfg->x.nid) < 0) + return -ENOTSUP; - /* Hash the secret for use as AES key. */ - mem_hash(HASH_SHA3_256, s, secret, secret_len); + return openssl_pkp_create(cfg->x.str, (EVP_PKEY **) pkp, pk); +#else + (void) cfg; + (void) pkp; + (void) pk; - OPENSSL_free(secret); - EVP_PKEY_CTX_free(ctx); + *pkp = NULL; return 0; +#endif +} + +void kex_pkp_destroy(void * pkp) +{ + if (pkp == NULL) + return; +#ifdef HAVE_OPENSSL + openssl_pkp_destroy((EVP_PKEY *) pkp); +#else + (void) pkp; + + return; +#endif +} + +int kex_dhe_derive(struct sec_config * cfg, + void * pkp, + buffer_t pk, + uint8_t * s) +{ + assert(cfg != NULL); + + if (kex_validate_nid(cfg->x.nid) < 0) + return -ENOTSUP; + +#ifdef HAVE_OPENSSL + return openssl_dhe_derive((EVP_PKEY *) pkp, pk, cfg->k.nid, s); +#else + (void) pkp; + (void) pk; + + memset(s, 0, SYMMKEYSZ); - fail_derive: - OPENSSL_free(secret); - fail_ctx: - EVP_PKEY_CTX_free(ctx); - fail_new: return -ECRYPT; +#endif } -static int __openssl_ecdh_gen_key(void ** kp) +ssize_t kex_kem_encap(buffer_t pk, + uint8_t * ct, + int kdf, + uint8_t * s) { - EVP_PKEY_CTX * ctx = NULL; - EVP_PKEY_CTX * kctx = NULL; - EVP_PKEY * params = NULL; - int ret; +#ifdef HAVE_OPENSSL + return openssl_kem_encap(pk, ct, kdf, s); +#else + (void) pk; + (void) ct; + (void) kdf; - ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); - if (ctx == NULL) - goto fail_new_id; + memset(s, 0, SYMMKEYSZ); - ret = EVP_PKEY_paramgen_init(ctx); - if (ret != 1) - goto fail_paramgen; + return -ECRYPT; +#endif +} - ret = EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, NID_X9_62_prime256v1); - if (ret != 1) - goto fail_paramgen; +ssize_t kex_kem_encap_raw(buffer_t pk, + uint8_t * ct, + int kdf, + uint8_t * s) +{ +#ifdef HAVE_OPENSSL + return openssl_kem_encap_raw(pk, ct, kdf, s); +#else + (void) pk; + (void) ct; + (void) kdf; - ret = EVP_PKEY_paramgen(ctx, ¶ms); - if (ret != 1) - goto fail_paramgen; + memset(s, 0, SYMMKEYSZ); - kctx = EVP_PKEY_CTX_new(params, NULL); - if (kctx == NULL) - goto fail_keygen_init; + return -ECRYPT; +#endif +} - ret = EVP_PKEY_keygen_init(kctx); - if (ret != 1) - goto fail_keygen; +int kex_kem_decap(void * pkp, + buffer_t ct, + int kdf, + uint8_t * s) +{ +#ifdef HAVE_OPENSSL + return openssl_kem_decap((EVP_PKEY *) pkp, ct, kdf, s); +#else + (void) pkp; + (void) ct; + (void) kdf; - ret = EVP_PKEY_keygen(kctx, (EVP_PKEY **) kp); - if (ret != 1) - goto fail_keygen; + memset(s, 0, SYMMKEYSZ); - EVP_PKEY_free(params); - EVP_PKEY_CTX_free(kctx); - EVP_PKEY_CTX_free(ctx); + return -ECRYPT; +#endif +} - return 0; +int kex_get_algo_from_pk_der(buffer_t pk, + char * algo) +{ +#ifdef HAVE_OPENSSL + return openssl_get_algo_from_pk_der(pk, algo); +#else + (void) pk; + algo[0] = '\0'; - fail_keygen: - EVP_PKEY_CTX_free(kctx); - fail_keygen_init: - EVP_PKEY_free(params); - fail_paramgen: - EVP_PKEY_CTX_free(ctx); - fail_new_id: return -ECRYPT; +#endif } -static ssize_t openssl_ecdh_pkp_create(void ** pkp, - uint8_t * pk) +int kex_get_algo_from_pk_raw(buffer_t pk, + char * algo) { - uint8_t * pos; - ssize_t len; +#ifdef HAVE_OPENSSL + return openssl_get_algo_from_pk_raw(pk, algo); +#else + (void) pk; + algo[0] = '\0'; - assert(pkp != NULL); - assert(*pkp == NULL); - assert(pk != NULL); + return -ECRYPT; +#endif +} - if (__openssl_ecdh_gen_key(pkp) < 0) - return -ECRYPT; +int kex_validate_algo(const char * algo) +{ + if (algo == NULL) + return -EINVAL; + + /* Use NID validation instead of string array */ + return kex_validate_nid(kex_str_to_nid(algo)); +} - assert(*pkp != NULL); +int crypt_validate_nid(int nid) +{ + const struct nid_map * p; + + if (nid == NID_undef) + return -EINVAL; - pos = pk; /* i2d_PUBKEY increments the pointer, don't use buf! */ - len = i2d_PUBKEY(*pkp, &pos); - if (len < 0) { - EVP_PKEY_free(*pkp); - return -ECRYPT; + for (p = cipher_nid_map; p->name != NULL; p++) { + if (p->nid == nid) + return 0; } - return len; + return -ENOTSUP; } -static void openssl_ecdh_pkp_destroy(void * pkp) + +const char * crypt_nid_to_str(uint16_t nid) { - EVP_PKEY_free((EVP_PKEY *) pkp); + const struct nid_map * p; + + for (p = cipher_nid_map; p->name != NULL; p++) { + if (p->nid == nid) + return p->name; + } + + return NULL; } -static int openssl_ecdh_derive(void * pkp, - uint8_t * pk, - size_t len, - uint8_t * s) +uint16_t crypt_str_to_nid(const char * cipher) { - uint8_t * pos; - EVP_PKEY * pub; + const struct nid_map * p; - pos = pk; /* d2i_PUBKEY increments the pointer, don't use key ptr! */ - pub = d2i_PUBKEY(NULL, (const uint8_t **) &pos, (long) len); - if (pub == NULL) - return -ECRYPT; + if (cipher == NULL) + return NID_undef; - if (__openssl_ecdh_derive_secret(pkp, pub, s) < 0) { - EVP_PKEY_free(pub); - return -ECRYPT; + /* fast, check if cipher pointer is in the map */ + for (p = cipher_nid_map; p->name != NULL; p++) { + if (cipher == p->name) + return p->nid; } - EVP_PKEY_free(pub); + for (p = cipher_nid_map; p->name != NULL; p++) { + if (strcmp(p->name, cipher) == 0) + return p->nid; + } - return 0; + return NID_undef; } -/* - * AES encryption calls. If FRCT is disabled, we should generate a - * 128-bit random IV and append it to the packet. If the flow is - * reliable, we could initialize the context once, and consider the - * stream a single encrypted message to avoid initializing the - * encryption context for each packet. - */ +const char * kex_nid_to_str(uint16_t nid) +{ + const struct nid_map * p; + + for (p = kex_nid_map; p->name != NULL; p++) { + if (p->nid == nid) + return p->name; + } + + return NULL; +} -static int openssl_encrypt(struct flow * f, - struct shm_du_buff * sdb) +uint16_t kex_str_to_nid(const char * algo) { - uint8_t * out; - uint8_t * in; - uint8_t * head; - uint8_t iv[IVSZ]; - int in_sz; - int out_sz; - int tmp_sz; - int ret; + const struct nid_map * p; - in = shm_du_buff_head(sdb); - in_sz = shm_du_buff_tail(sdb) - in; + if (algo == NULL) + return NID_undef; - if (random_buffer(iv, IVSZ) < 0) - goto fail_iv; + /* Fast path: check if algo pointer is in the map */ + for (p = kex_nid_map; p->name != NULL; p++) { + if (algo == p->name) + return p->nid; + } - out = malloc(in_sz + EVP_MAX_BLOCK_LENGTH); - if (out == NULL) - goto fail_iv; + /* Slow path: string comparison */ + for (p = kex_nid_map; p->name != NULL; p++) { + if (strcmp(p->name, algo) == 0) + return p->nid; + } - EVP_CIPHER_CTX_reset(f->ctx); + return NID_undef; +} - ret = EVP_EncryptInit_ex(f->ctx, - EVP_aes_256_cbc(), - NULL, - f->key, - iv); - if (ret != 1) - goto fail_encrypt_init; +int kex_validate_nid(int nid) +{ + const struct nid_map * p; - ret = EVP_EncryptUpdate(f->ctx, out, &tmp_sz, in, in_sz); - if (ret != 1) - goto fail_encrypt; + if (nid == NID_undef) + return -EINVAL; - out_sz = tmp_sz; - ret = EVP_EncryptFinal_ex(f->ctx, out + tmp_sz, &tmp_sz); - if (ret != 1) - goto fail_encrypt; + for (p = kex_nid_map; p->name != NULL; p++) { + if (p->nid == nid) + return 0; + } - out_sz += tmp_sz; + return -ENOTSUP; +} - EVP_CIPHER_CTX_cleanup(f->ctx); +const char * md_nid_to_str(uint16_t nid) +{ + const struct nid_map * p; - assert(out_sz >= in_sz); + for (p = md_nid_map; p->name != NULL; p++) { + if (p->nid == nid) + return p->name; + } - head = shm_du_buff_head_alloc(sdb, IVSZ); - if (head == NULL) - goto fail_encrypt; + return NULL; +} - if (shm_du_buff_tail_alloc(sdb, out_sz - in_sz) == NULL) - goto fail_tail_alloc; +uint16_t md_str_to_nid(const char * kdf) +{ + const struct nid_map * p; - memcpy(head, iv, IVSZ); - memcpy(in, out, out_sz); + if (kdf == NULL) + return NID_undef; - free(out); + /* Fast path: check if kdf pointer is in the map */ + for (p = md_nid_map; p->name != NULL; p++) { + if (kdf == p->name) + return p->nid; + } - return 0; + /* Slow path: string comparison */ + for (p = md_nid_map; p->name != NULL; p++) { + if (strcmp(p->name, kdf) == 0) + return p->nid; + } + + return NID_undef; +} + +int md_validate_nid(int nid) +{ + const struct nid_map * p; + + if (nid == NID_undef) + return -EINVAL; + + for (p = md_nid_map; p->name != NULL; p++) { + if (p->nid == nid) + return 0; + } + + return -ENOTSUP; +} + +/* Hash length now returned by md_digest() */ + +int crypt_encrypt(struct crypt_ctx * ctx, + buffer_t in, + buffer_t * out) +{ + assert(ctx != NULL); + assert(ctx->ctx != NULL); + +#ifdef HAVE_OPENSSL + return openssl_encrypt(ctx->ctx, in, out); +#else + (void) ctx; + (void) in; + (void) out; - fail_tail_alloc: - shm_du_buff_head_release(sdb, IVSZ); - fail_encrypt: - EVP_CIPHER_CTX_cleanup(f->ctx); - fail_encrypt_init: - free(out); - fail_iv: return -ECRYPT; +#endif } -static int openssl_decrypt(struct flow * f, - struct shm_du_buff * sdb) +int crypt_decrypt(struct crypt_ctx * ctx, + buffer_t in, + buffer_t * out) { - uint8_t * in; - uint8_t * out; - uint8_t iv[IVSZ]; - int ret; - int out_sz; - int in_sz; - int tmp_sz; + assert(ctx != NULL); + assert(ctx->ctx != NULL); - in = shm_du_buff_head_release(sdb, IVSZ); +#ifdef HAVE_OPENSSL + return openssl_decrypt(ctx->ctx, in, out); +#else + (void) ctx; + (void) in; + (void) out; - memcpy(iv, in, IVSZ); + return -ECRYPT; +#endif +} - in = shm_du_buff_head(sdb); +struct crypt_ctx * crypt_create_ctx(struct crypt_sk * sk) +{ + struct crypt_ctx * crypt; - in_sz = shm_du_buff_tail(sdb) - shm_du_buff_head(sdb); + if (crypt_validate_nid(sk->nid) != 0) + return NULL; - out = malloc(in_sz); - if (out == NULL) - goto fail_malloc; + crypt = malloc(sizeof(*crypt)); + if (crypt == NULL) + goto fail_crypt; - EVP_CIPHER_CTX_reset(f->ctx); + memset(crypt, 0, sizeof(*crypt)); - ret = EVP_DecryptInit_ex(f->ctx, - EVP_aes_256_cbc(), - NULL, - f->key, - iv); - if (ret != 1) - goto fail_decrypt_init; +#ifdef HAVE_OPENSSL + crypt->ctx = openssl_crypt_create_ctx(sk); + if (crypt->ctx == NULL) + goto fail_ctx; +#endif + return crypt; +#ifdef HAVE_OPENSSL + fail_ctx: + free(crypt); +#endif + fail_crypt: + return NULL; +} - ret = EVP_DecryptUpdate(f->ctx, out, &tmp_sz, in, in_sz); - if (ret != 1) - goto fail_decrypt; +void crypt_destroy_ctx(struct crypt_ctx * crypt) +{ + if (crypt == NULL) + return; - out_sz = tmp_sz; +#ifdef HAVE_OPENSSL + assert(crypt->ctx != NULL); + openssl_crypt_destroy_ctx(crypt->ctx); +#else + assert(crypt->ctx == NULL); +#endif + free(crypt); +} - ret = EVP_DecryptFinal_ex(f->ctx, out + tmp_sz, &tmp_sz); - if (ret != 1) - goto fail_decrypt; +int crypt_get_ivsz(struct crypt_ctx * ctx) +{ + if (ctx == NULL) + return -EINVAL; - out_sz += tmp_sz; +#ifdef HAVE_OPENSSL + assert(ctx->ctx != NULL); + return openssl_crypt_get_ivsz(ctx->ctx); +#else + assert(ctx->ctx == NULL); + return -ENOTSUP; +#endif +} - assert(out_sz <= in_sz); +int crypt_get_tagsz(struct crypt_ctx * ctx) +{ + if (ctx == NULL) + return -EINVAL; - shm_du_buff_tail_release(sdb, in_sz - out_sz); +#ifdef HAVE_OPENSSL + assert(ctx->ctx != NULL); + return openssl_crypt_get_tagsz(ctx->ctx); +#else + assert(ctx->ctx == NULL); + return -ENOTSUP; +#endif +} - memcpy(in, out, out_sz); +int crypt_load_privkey_file(const char * path, + void ** key) +{ + *key = NULL; - free(out); +#ifdef HAVE_OPENSSL + return openssl_load_privkey_file(path, key); +#else + (void) path; return 0; +#endif +} - fail_decrypt: - EVP_CIPHER_CTX_cleanup(f->ctx); - fail_decrypt_init: - free(out); - fail_malloc: - return -ECRYPT; +int crypt_load_privkey_str(const char * str, + void ** key) +{ + *key = NULL; +#ifdef HAVE_OPENSSL + return openssl_load_privkey_str(str, key); +#else + (void) str; + + return 0; +#endif } -static int openssl_crypt_init(void ** ctx) +int crypt_load_pubkey_str(const char * str, + void ** key) { - *ctx = EVP_CIPHER_CTX_new(); - if (*ctx == NULL) - return -ECRYPT; + *key = NULL; + +#ifdef HAVE_OPENSSL + return openssl_load_pubkey_str(str, key); +#else + (void) str; return 0; +#endif } -static void openssl_crypt_fini(void * ctx) +int crypt_load_pubkey_file(const char * path, + void ** key) { - EVP_CIPHER_CTX_free(ctx); + *key = NULL; + +#ifdef HAVE_OPENSSL + return openssl_load_pubkey_file(path, key); +#else + (void) path; + + return 0; +#endif } -#endif /* HAVE_OPENSSL */ +int crypt_load_pubkey_file_to_der(const char * path, + buffer_t * buf) +{ + assert(buf != NULL); + +#ifdef HAVE_OPENSSL + return openssl_load_pubkey_file_to_der(path, buf); +#else + (void) path; + + buf->data = NULL; + buf->len = 0; + return 0; +#endif +} -static int crypt_dh_pkp_create(void ** pkp, - uint8_t * pk) +int crypt_load_pubkey_raw_file(const char * path, + buffer_t * buf) { + assert(buf != NULL); + #ifdef HAVE_OPENSSL - assert(pkp != NULL); - *pkp = NULL; - return openssl_ecdh_pkp_create(pkp, pk); + return openssl_load_pubkey_raw_file(path, buf); #else - (void) pkp; - (void) pk; + (void) path; - memset(pk, 0, MSGBUFSZ); - *pkp = NULL; + buf->data = NULL; + buf->len = 0; + return 0; +#endif +} + +int crypt_load_privkey_raw_file(const char * path, + void ** key) +{ + *key = NULL; + +#ifdef HAVE_OPENSSL + return openssl_load_privkey_raw_file(path, key); +#else + (void) path; return 0; #endif } -static void crypt_dh_pkp_destroy(void * pkp) +int crypt_cmp_key(const void * key1, + const void * key2) { #ifdef HAVE_OPENSSL - openssl_ecdh_pkp_destroy(pkp); + return openssl_cmp_key((const EVP_PKEY *) key1, + (const EVP_PKEY *) key2); #else - (void) pkp; - return; + (void) key1; + (void) key2; + + return 0; +#endif +} + +void crypt_free_key(void * key) +{ + if (key == NULL) + return; + +#ifdef HAVE_OPENSSL + openssl_free_key((EVP_PKEY *) key); #endif } -static int crypt_dh_derive(void * pkp, - uint8_t * pk, - size_t len, - uint8_t * s) +int crypt_load_crt_file(const char * path, + void ** crt) { + assert(crt != NULL); + + *crt = NULL; + #ifdef HAVE_OPENSSL - return openssl_ecdh_derive(pkp, pk, len, s); + return openssl_load_crt_file(path, crt); #else - (void) pkp; - (void) pk; - (void) len; + (void) path; - memset(s, 0, SYMMKEYSZ); + return 0; +#endif +} - return -ECRYPT; +int crypt_load_crt_str(const char * str, + void ** crt) +{ + assert(crt != NULL); + + *crt = NULL; + +#ifdef HAVE_OPENSSL + return openssl_load_crt_str(str, crt); +#else + (void) str; + + return 0; #endif } -static int crypt_encrypt(struct flow * f, - struct shm_du_buff * sdb) +int crypt_load_crt_der(const buffer_t buf, + void ** crt) { + assert(crt != NULL); #ifdef HAVE_OPENSSL - return openssl_encrypt(f, sdb); + return openssl_load_crt_der(buf, crt); #else - (void) f; - (void) sdb; + *crt = NULL; + + (void) buf; return 0; #endif } -static int crypt_decrypt(struct flow * f, - struct shm_du_buff * sdb) +int crypt_get_pubkey_crt(void * crt, + void ** pk) { + assert(crt != NULL); + assert(pk != NULL); + #ifdef HAVE_OPENSSL - return openssl_decrypt(f, sdb); + return openssl_get_pubkey_crt(crt, pk); #else - (void) f; - (void) sdb; + (void) crt; - return -ECRYPT; + clrbuf(*pk); + + return 0; +#endif +} + +void crypt_free_crt(void * crt) +{ + if (crt == NULL) + return; +#ifdef HAVE_OPENSSL + openssl_free_crt(crt); +#endif +} + +int crypt_crt_str(const void * crt, + char * buf) +{ +#ifdef HAVE_OPENSSL + return openssl_crt_str(crt, buf); +#else + (void) crt; + (void) buf; + + return 0; +#endif +} + +int crypt_crt_der(const void * crt, + buffer_t * buf) +{ + assert(crt != NULL); + assert(buf != NULL); + +#ifdef HAVE_OPENSSL + return openssl_crt_der(crt, buf); +#else + (void) crt; + + clrbuf(*buf); + + return 0; +#endif +} + +int crypt_check_crt_name(void * crt, + const char * name) +{ +#ifdef HAVE_OPENSSL + return openssl_check_crt_name(crt, name); +#else + (void) crt; + (void) name; + + return 0; #endif } -static int crypt_init(void ** ctx) +int crypt_get_crt_name(void * crt, + char * name) { #ifdef HAVE_OPENSSL - return openssl_crypt_init(ctx); + return openssl_get_crt_name(crt, name); #else + (void) crt; + (void) name; + + return 0; +#endif +} + +struct auth_ctx * auth_create_ctx(void) +{ + struct auth_ctx * ctx; + + ctx = malloc(sizeof(*ctx)); + if (ctx == NULL) + goto fail_malloc; + + memset(ctx, 0, sizeof(*ctx)); +#ifdef HAVE_OPENSSL + ctx->store = openssl_auth_create_store(); + if (ctx->store == NULL) + goto fail_store; +#endif + return ctx; +#ifdef HAVE_OPENSSL + fail_store: + free(ctx); +#endif + fail_malloc: + return NULL; +} + +void auth_destroy_ctx(struct auth_ctx * ctx) +{ + if (ctx == NULL) + return; +#ifdef HAVE_OPENSSL + openssl_auth_destroy_store(ctx->store); +#endif + free(ctx); +} + +int auth_add_crt_to_store(struct auth_ctx * ctx, + void * crt) +{ assert(ctx != NULL); - *ctx = NULL; + assert(crt != NULL); + +#ifdef HAVE_OPENSSL + return openssl_auth_add_crt_to_store(ctx->store, crt); +#else + (void) ctx; + (void) crt; return 0; #endif } -static void crypt_fini(void * ctx) +int auth_verify_crt(struct auth_ctx * ctx, + void * crt) { #ifdef HAVE_OPENSSL - openssl_crypt_fini(ctx); + return openssl_verify_crt(ctx->store, crt); #else - assert(ctx == NULL); (void) ctx; + (void) crt; + + return 0; +#endif +} + +int auth_sign(void * pkp, + int md_nid, + buffer_t msg, + buffer_t * sig) +{ +#ifdef HAVE_OPENSSL + return openssl_sign((EVP_PKEY *) pkp, md_nid, msg, sig); +#else + (void) pkp; + (void) md_nid; + (void) msg; + (void) sig; + + clrbuf(*sig); + + return 0; +#endif +} + +int auth_verify_sig(void * pk, + int md_nid, + buffer_t msg, + buffer_t sig) +{ +#ifdef HAVE_OPENSSL + return openssl_verify_sig((EVP_PKEY *) pk, md_nid, msg, sig); +#else + (void) pk; + (void) md_nid; + (void) msg; + (void) sig; + + return 0; +#endif +} + +ssize_t md_digest(int md_nid, + buffer_t in, + uint8_t * out) +{ +#ifdef HAVE_OPENSSL + return openssl_md_digest(md_nid, in, out); +#else + (void) md_nid; + (void) in; + (void) out; + + return -1; +#endif +} + +ssize_t md_len(int md_nid) +{ +#ifdef HAVE_OPENSSL + return openssl_md_len(md_nid); +#else + (void) md_nid; + return -1; +#endif +} + +int crypt_secure_malloc_init(size_t max) +{ +#ifdef HAVE_OPENSSL + return openssl_secure_malloc_init(max, SECMEM_GUARD); +#else + (void) max; + return 0; +#endif +} + +void crypt_secure_malloc_fini(void) +{ +#ifdef HAVE_OPENSSL + openssl_secure_malloc_fini(); +#endif +} + +void * crypt_secure_malloc(size_t size) +{ +#ifdef HAVE_OPENSSL + return openssl_secure_malloc(size); +#else + return malloc(size); +#endif +} + +void crypt_secure_free(void * ptr, + size_t size) +{ + if (ptr == NULL) + return; + +#ifdef HAVE_OPENSSL + openssl_secure_free(ptr, size); +#else + memset(ptr, 0, size); + free(ptr); +#endif +} + +void crypt_secure_clear(void * ptr, + size_t size) +{ + volatile uint8_t * p; + + if (ptr == NULL) + return; + +#ifdef HAVE_OPENSSL + (void) p; + openssl_secure_clear(ptr, size); +#elif defined(HAVE_EXPLICIT_BZERO) + (void) p; + explicit_bzero(ptr, size); +#else /* best effort to avoid optimizing out */ + p = ptr; + while (size-- > 0) + *p++ = 0; #endif } diff --git a/src/lib/crypt/openssl.c b/src/lib/crypt/openssl.c new file mode 100644 index 00000000..232aa6c9 --- /dev/null +++ b/src/lib/crypt/openssl.c @@ -0,0 +1,1880 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OpenSSL based cryptographic operations + * Elliptic curve Diffie-Hellman key exchange + * AES encryption + # Authentication + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#define _POSIX_C_SOURCE 200809L + +#include <config.h> + +#include <ouroboros/errno.h> +#include <ouroboros/crypt.h> +#include <ouroboros/hash.h> +#include <ouroboros/random.h> +#include <ouroboros/utils.h> + +#include <openssl/evp.h> +#include <openssl/bio.h> +#include <openssl/ec.h> +#include <openssl/err.h> +#include <openssl/kdf.h> +#include <openssl/pem.h> +#include <openssl/sha.h> +#include <openssl/provider.h> +#include <openssl/x509v3.h> +#include <openssl/x509_vfy.h> + +#include <assert.h> +#include <stdio.h> + +#define IS_EC_GROUP(str) (strcmp(str, "EC") == 0) +#define IS_DH_GROUP(str) (strcmp(str, "DH") == 0) + +#define HKDF_INFO_DHE "o7s-ossl-dhe" +#define HKDF_INFO_ENCAP "o7s-ossl-encap" +#define HKDF_INFO_ROTATION "o7s-key-rotation" +#define HKDF_SALT_LEN 32 /* SHA-256 output size */ + +struct ossl_crypt_ctx { + EVP_CIPHER_CTX * evp_ctx; + const EVP_CIPHER * cipher; + int ivsz; + int tagsz; + + struct { + uint8_t * cur; /* current key */ + uint8_t * prv; /* rotated key */ + } keys; + + struct { + uint32_t cntr; /* counter */ + uint32_t mask; /* phase mask */ + uint32_t age; /* counter within epoch */ + uint8_t phase; /* current key phase */ + uint8_t salt[HKDF_SALT_LEN]; + } rot; /* rotation logic */ +}; + +struct kdf_info { + buffer_t secret; + int nid; + buffer_t salt; + buffer_t info; + buffer_t key; +}; + +/* Key rotation macros */ +#define HAS_PHASE_BIT_TOGGLED(ctx) \ + (((ctx)->rot.cntr & (ctx)->rot.mask) != \ + (((ctx)->rot.cntr - 1) & (ctx)->rot.mask)) + +#define HAS_GRACE_EXPIRED(ctx) \ + ((ctx)->rot.age >= ((ctx)->rot.mask >> 1)) + +#define ROTATION_TOO_RECENT(ctx) \ + ((ctx)->rot.age < ((ctx)->rot.mask - ((ctx)->rot.mask >> 2))) + +/* Convert hash NID to OpenSSL digest name string for HKDF */ +static const char * hash_nid_to_digest_name(int nid) +{ + const EVP_MD * md; + const char * name; + + md = EVP_get_digestbynid(nid); + if (md == NULL) + return "SHA256"; /* fallback to SHA-256 */ + + name = EVP_MD_get0_name(md); + if (name == NULL) + return "SHA256"; /* fallback to SHA-256 */ + + return name; +} + +/* Extract public key bytes from a key pair for salt derivation */ +static int get_pk_bytes_from_key(EVP_PKEY * key, + buffer_t * pk) +{ + const char * name; + int ret; + + assert(key != NULL); + assert(pk != NULL); + + name = EVP_PKEY_get0_type_name(key); + if (name == NULL) + goto fail_name; + + if (IS_HYBRID_KEM(name)) { + pk->len = EVP_PKEY_get1_encoded_public_key(key, &pk->data); + if (pk->len == 0) + goto fail_name; + } else { + /* Pure ML-KEM: use DER encoding to match encap */ + pk->data = NULL; + ret = i2d_PUBKEY(key, &pk->data); + if (ret <= 0) + goto fail_name; + pk->len = (size_t) ret; + } + + return 0; + fail_name: + return -ECRYPT; +} + +/* Derive salt from public key bytes by hashing them */ +static int derive_salt_from_pk_bytes(buffer_t pk, + uint8_t * salt, + size_t salt_len) +{ + uint8_t hash[EVP_MAX_MD_SIZE]; + unsigned hash_len; + + assert(pk.data != NULL); + assert(salt != NULL); + + if (EVP_Digest(pk.data, pk.len, hash, &hash_len, + EVP_sha256(), NULL) != 1) + goto fail_digest; + + memcpy(salt, hash, salt_len < hash_len ? salt_len : hash_len); + + return 0; + fail_digest: + return -ECRYPT; +} + +/* Derive salt from two public key byte buffers (DHE) in canonical order */ +static int derive_salt_from_pk_bytes_dhe(buffer_t local, + buffer_t remote, + uint8_t * salt, + size_t salt_len) +{ + uint8_t * concat; + size_t concat_len; + uint8_t hash[EVP_MAX_MD_SIZE]; + unsigned hash_len; + size_t min_len; + int cmp; + + assert(local.data != NULL); + assert(remote.data != NULL); + assert(salt != NULL); + + concat_len = local.len + remote.len; + concat = OPENSSL_malloc(concat_len); + if (concat == NULL) + goto fail_malloc; + + /* Canonical order: compare and concatenate smaller first */ + min_len = local.len < remote.len ? local.len : remote.len; + cmp = memcmp(local.data, remote.data, min_len); + if (cmp < 0 || (cmp == 0 && local.len < remote.len)) { + memcpy(concat, local.data, local.len); + memcpy(concat + local.len, remote.data, remote.len); + } else { + memcpy(concat, remote.data, remote.len); + memcpy(concat + remote.len, local.data, local.len); + } + + if (EVP_Digest(concat, concat_len, hash, &hash_len, + EVP_sha256(), NULL) != 1) + goto fail_digest; + + OPENSSL_free(concat); + + memcpy(salt, hash, salt_len < hash_len ? salt_len : hash_len); + + return 0; + fail_digest: + OPENSSL_free(concat); + fail_malloc: + return -ECRYPT; +} + +/* Derive key using HKDF */ +#define OPc_u_str OSSL_PARAM_construct_utf8_string +#define OPc_o_str OSSL_PARAM_construct_octet_string +static int derive_key_hkdf(struct kdf_info * ki) +{ + EVP_KDF * kdf; + EVP_KDF_CTX * kctx; + OSSL_PARAM params[5]; + const char * digest; + int idx; + + digest = hash_nid_to_digest_name(ki->nid); + + kdf = EVP_KDF_fetch(NULL, "HKDF", NULL); + if (kdf == NULL) + goto fail_fetch; + + kctx = EVP_KDF_CTX_new(kdf); + if (kctx == NULL) + goto fail_ctx; + + idx = 0; + params[idx++] = OPc_u_str("digest", (char *) digest, 0); + params[idx++] = OPc_o_str("key", ki->secret.data, ki->secret.len); + params[idx++] = OPc_o_str("salt", ki->salt.data, ki->salt.len); + params[idx++] = OPc_o_str("info", ki->info.data, ki->info.len); + + params[idx] = OSSL_PARAM_construct_end(); + + if (EVP_KDF_derive(kctx, ki->key.data, ki->key.len, params) != 1) + goto fail_derive; + + EVP_KDF_CTX_free(kctx); + EVP_KDF_free(kdf); + + return 0; + + fail_derive: + EVP_KDF_CTX_free(kctx); + fail_ctx: + EVP_KDF_free(kdf); + fail_fetch: + return -ECRYPT; +} + +/* Key rotation helper functions implementation */ +static int should_rotate_key_rx(struct ossl_crypt_ctx * ctx, + uint8_t rx_phase) +{ + assert(ctx != NULL); + + /* Phase must have changed */ + if (rx_phase == ctx->rot.phase) + return 0; + + if (ROTATION_TOO_RECENT(ctx)) + return 0; + + return 1; +} + +static int rotate_key(struct ossl_crypt_ctx * ctx) +{ + struct kdf_info ki; + uint8_t * tmp; + + assert(ctx != NULL); + + /* Swap keys - move current to prev */ + tmp = ctx->keys.prv; + ctx->keys.prv = ctx->keys.cur; + + if (tmp != NULL) { + /* Reuse old prev_key memory for new key */ + ctx->keys.cur = tmp; + } else { + /* First rotation - allocate new memory */ + ctx->keys.cur = OPENSSL_secure_malloc(SYMMKEYSZ); + if (ctx->keys.cur == NULL) + return -ECRYPT; + } + + /* Derive new key from previous key using HKDF */ + ki.secret.data = ctx->keys.prv; + ki.secret.len = SYMMKEYSZ; + ki.nid = NID_sha256; + ki.salt.data = ctx->rot.salt; + ki.salt.len = HKDF_SALT_LEN; + ki.info.data = (uint8_t *) HKDF_INFO_ROTATION; + ki.info.len = strlen(HKDF_INFO_ROTATION); + ki.key.data = ctx->keys.cur; + ki.key.len = SYMMKEYSZ; + + if (derive_key_hkdf(&ki) != 0) + return -ECRYPT; + + ctx->rot.age = 0; + ctx->rot.phase = !ctx->rot.phase; + + return 0; +} + +static void cleanup_old_key(struct ossl_crypt_ctx * ctx) +{ + assert(ctx != NULL); + + if (ctx->keys.prv == NULL) + return; + + if (!HAS_GRACE_EXPIRED(ctx)) + return; + + OPENSSL_secure_clear_free(ctx->keys.prv, SYMMKEYSZ); + ctx->keys.prv = NULL; +} + +static int try_decrypt(struct ossl_crypt_ctx * ctx, + uint8_t * key, + uint8_t * iv, + uint8_t * input, + int in_sz, + uint8_t * out, + int * out_sz) +{ + uint8_t * tag; + int tmp_sz; + int ret; + + tag = input + in_sz; + + EVP_CIPHER_CTX_reset(ctx->evp_ctx); + + ret = EVP_DecryptInit_ex(ctx->evp_ctx, ctx->cipher, NULL, key, iv); + if (ret != 1) + return -1; + + if (ctx->tagsz > 0) { + ret = EVP_CIPHER_CTX_ctrl(ctx->evp_ctx, EVP_CTRL_AEAD_SET_TAG, + ctx->tagsz, tag); + if (ret != 1) + return -1; + } + + ret = EVP_DecryptUpdate(ctx->evp_ctx, out, &tmp_sz, input, in_sz); + if (ret != 1) + return -1; + + *out_sz = tmp_sz; + + ret = EVP_DecryptFinal_ex(ctx->evp_ctx, out + tmp_sz, &tmp_sz); + if (ret != 1) + return -1; + + *out_sz += tmp_sz; + + return 0; +} + +/* + * Derive the common secret from + * - your public key pair (pkp) + * - the remote public key bytes (remote_pk). + * Store it in a preallocated buffer (s). + */ +static int __openssl_dhe_derive(EVP_PKEY * pkp, + EVP_PKEY * pub, + buffer_t remote_pk, + int kdf, + uint8_t * s) +{ + EVP_PKEY_CTX * ctx; + struct kdf_info ki; + buffer_t local_pk; + int ret; + uint8_t * secret; + size_t secret_len; + uint8_t salt_buf[HKDF_SALT_LEN]; + + /* Extract local public key bytes */ + local_pk.data = NULL; + ret = i2d_PUBKEY(pkp, &local_pk.data); + if (ret <= 0) + goto fail_local; + local_pk.len = (size_t) ret; + + /* Derive salt from both public keys */ + if (derive_salt_from_pk_bytes_dhe(local_pk, remote_pk, salt_buf, + HKDF_SALT_LEN) < 0) + goto fail_salt; + + ctx = EVP_PKEY_CTX_new(pkp, NULL); + if (ctx == NULL) + goto fail_salt; + + ret = EVP_PKEY_derive_init(ctx); + if (ret != 1) + goto fail_ctx; + + ret = EVP_PKEY_derive_set_peer(ctx, pub); + if (ret != 1) + goto fail_ctx; + + ret = EVP_PKEY_derive(ctx, NULL, &secret_len); + if (ret != 1) + goto fail_ctx; + + if (secret_len < SYMMKEYSZ) + goto fail_ctx; + + secret = OPENSSL_malloc(secret_len); + if (secret == NULL) + goto fail_ctx; + + ret = EVP_PKEY_derive(ctx, secret, &secret_len); + if (ret != 1) + goto fail_derive; + + ki.nid = kdf; + ki.secret.len = secret_len; + ki.secret.data = secret; + ki.info.len = strlen(HKDF_INFO_DHE); + ki.info.data = (uint8_t *) HKDF_INFO_DHE; + ki.key.len = SYMMKEYSZ; + ki.key.data = s; + ki.salt.len = HKDF_SALT_LEN; + ki.salt.data = salt_buf; + + /* Derive symmetric key from shared secret using HKDF */ + ret = derive_key_hkdf(&ki); + + OPENSSL_free(secret); + EVP_PKEY_CTX_free(ctx); + OPENSSL_free(local_pk.data); + + if (ret != 0) + return ret; + + return 0; + fail_derive: + OPENSSL_free(secret); + fail_ctx: + EVP_PKEY_CTX_free(ctx); + fail_salt: + OPENSSL_free(local_pk.data); + fail_local: + return -ECRYPT; +} + +static int __openssl_dhe_gen_key(const char * algo, + EVP_PKEY ** kp) +{ + EVP_PKEY_CTX * ctx = NULL; + EVP_PKEY_CTX * kctx = NULL; + EVP_PKEY * params = NULL; + int nid; + int type; + int ret; + + assert(algo != NULL); + assert(kp != NULL); + + nid = OBJ_txt2nid(algo); + if (nid == NID_undef) + return -ECRYPT; + + /* X25519 and X448: direct keygen context */ + if (nid == EVP_PKEY_X25519 || nid == EVP_PKEY_X448) { + kctx = EVP_PKEY_CTX_new_id(nid, NULL); + if (kctx == NULL) + goto fail_kctx; + + goto keygen; + } + /* EC and FFDHE: parameter generation first */ + type = (strncmp(algo, "ffdhe", 5) == 0) ? EVP_PKEY_DH : EVP_PKEY_EC; + + ctx = EVP_PKEY_CTX_new_id(type, NULL); + if (ctx == NULL) + goto fail_ctx; + + ret = EVP_PKEY_paramgen_init(ctx); + if (ret != 1) + goto fail_paramgen; + + if (type == EVP_PKEY_EC) + ret = EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, nid); + else /* EVP_PKEY_DH */ + ret = EVP_PKEY_CTX_set_dh_nid(ctx, nid); + + if (ret != 1) + goto fail_paramgen; + + ret = EVP_PKEY_paramgen(ctx, ¶ms); + if (ret != 1) + goto fail_paramgen; + + kctx = EVP_PKEY_CTX_new(params, NULL); + if (kctx == NULL) + goto fail_kctx; + + EVP_PKEY_free(params); + EVP_PKEY_CTX_free(ctx); + keygen: + ret = EVP_PKEY_keygen_init(kctx); + if (ret != 1) + goto fail_keygen; + + ret = EVP_PKEY_keygen(kctx, kp); + if (ret != 1) + goto fail_keygen; + + EVP_PKEY_CTX_free(kctx); + + return 0; + + fail_keygen: + EVP_PKEY_CTX_free(kctx); + return -ECRYPT; + fail_kctx: + if (params != NULL) + EVP_PKEY_free(params); + fail_paramgen: + if (ctx != NULL) + EVP_PKEY_CTX_free(ctx); + fail_ctx: + return -ECRYPT; +} + +static int __openssl_kem_gen_key(const char * algo, + EVP_PKEY ** kp) +{ + EVP_PKEY_CTX * kctx; + int ret; + + assert(algo != NULL); + assert(kp != NULL); + + /* PQC KEM (ML-KEM-512, ML-KEM-768, ML-KEM-1024) or hybrid */ + kctx = EVP_PKEY_CTX_new_from_name(NULL, algo, NULL); + if (kctx == NULL) + goto fail_kctx; + + ret = EVP_PKEY_keygen_init(kctx); + if (ret != 1) + goto fail_keygen; + + ret = EVP_PKEY_keygen(kctx, kp); + if (ret != 1) + goto fail_keygen; + + EVP_PKEY_CTX_free(kctx); + + return 0; + + fail_keygen: + EVP_PKEY_CTX_free(kctx); + fail_kctx: + return -ECRYPT; +} + +/* Determine hybrid KEM algorithm from raw key/ciphertext length */ +static const char * __openssl_hybrid_algo_from_len(size_t len) +{ + switch(len) { + case X25519MLKEM768_PKSZ: + return "X25519MLKEM768"; + case X25519MLKEM768_CTSZ: + return "X25519MLKEM768"; + case X448MLKEM1024_PKSZ: + return "X448MLKEM1024"; + default: + break; + } + + return NULL; +} + +static int __openssl_kex_gen_key(const char * algo, + EVP_PKEY ** kp) +{ + assert(algo != NULL); + assert(kp != NULL); + + /* Dispatch based on algorithm name prefix */ + if (IS_KEM_ALGORITHM(algo)) + return __openssl_kem_gen_key(algo, kp); + + return __openssl_dhe_gen_key(algo, kp); +} + +ssize_t openssl_pkp_create(const char * algo, + EVP_PKEY ** pkp, + uint8_t * pk) +{ + uint8_t * pos; + buffer_t raw; + ssize_t len; + + assert(algo != NULL); + assert(pkp != NULL); + assert(*pkp == NULL); + assert(pk != NULL); + + if (__openssl_kex_gen_key(algo, pkp) < 0) + goto fail_key; + + if (IS_HYBRID_KEM(algo)) { /* Raw encode hybrid KEM */ + raw.len = EVP_PKEY_get1_encoded_public_key(*pkp, &raw.data); + if (raw.len == 0) + goto fail_pubkey; + + memcpy(pk, raw.data, raw.len); + OPENSSL_free(raw.data); + + return (ssize_t) raw.len; + } else { /* DER encode standard algorithms */ + pos = pk; /* i2d_PUBKEY increments the pointer, don't use pk! */ + len = i2d_PUBKEY(*pkp, &pos); + if (len < 0) + goto fail_pubkey; + + return len; + } + fail_pubkey: + EVP_PKEY_free(*pkp); + fail_key: + return -ECRYPT; +} + +/* Common KEM encapsulation - pub key and salt already prepared */ +static ssize_t __openssl_kem_encap(EVP_PKEY * pub, + uint8_t * salt, + uint8_t * ct, + int kdf, + uint8_t * s) +{ + EVP_PKEY_CTX * ctx; + struct kdf_info ki; + uint8_t * secret; + size_t secret_len; + size_t ct_len; + int ret; + + ctx = EVP_PKEY_CTX_new(pub, NULL); + if (ctx == NULL) + goto fail_ctx; + + ret = EVP_PKEY_encapsulate_init(ctx, NULL); + if (ret != 1) + goto fail_encap; + + /* Get required lengths */ + ret = EVP_PKEY_encapsulate(ctx, NULL, &ct_len, NULL, &secret_len); + if (ret != 1 || ct_len > MSGBUFSZ) + goto fail_encap; + + /* Allocate buffer for secret */ + secret = OPENSSL_malloc(secret_len); + if (secret == NULL) + goto fail_encap; + + /* Perform encapsulation */ + ret = EVP_PKEY_encapsulate(ctx, ct, &ct_len, secret, &secret_len); + if (ret != 1) + goto fail_secret; + + ki.secret.len = secret_len; + ki.secret.data = secret; + ki.nid = kdf; + ki.info.len = strlen(HKDF_INFO_ENCAP); + ki.info.data = (uint8_t *) HKDF_INFO_ENCAP; + ki.key.len = SYMMKEYSZ; + ki.key.data = s; + ki.salt.len = HKDF_SALT_LEN; + ki.salt.data = salt; + + /* Derive symmetric key from shared secret using HKDF */ + ret = derive_key_hkdf(&ki); + + OPENSSL_free(secret); + EVP_PKEY_CTX_free(ctx); + + if (ret != 0) + return -ECRYPT; + + return (ssize_t) ct_len; + + fail_secret: + OPENSSL_free(secret); + fail_encap: + EVP_PKEY_CTX_free(ctx); + fail_ctx: + return -ECRYPT; +} + +/* ML-KEM encapsulation - DER-encoded public key */ +ssize_t openssl_kem_encap(buffer_t pk, + uint8_t * ct, + int kdf, + uint8_t * s) +{ + EVP_PKEY * pub; + uint8_t * pos; + uint8_t salt[HKDF_SALT_LEN]; + ssize_t ret; + + assert(pk.data != NULL); + assert(ct != NULL); + assert(s != NULL); + + if (derive_salt_from_pk_bytes(pk, salt, HKDF_SALT_LEN) < 0) + goto fail_salt; + + pos = pk.data; + pub = d2i_PUBKEY(NULL, (const uint8_t **) &pos, (long) pk.len); + if (pub == NULL) + goto fail_salt; + + ret = __openssl_kem_encap(pub, salt, ct, kdf, s); + + EVP_PKEY_free(pub); + + return ret; + fail_salt: + return -ECRYPT; +} + +/* Hybrid KEM encapsulation: raw-encoded public key */ +ssize_t openssl_kem_encap_raw(buffer_t pk, + uint8_t * ct, + int kdf, + uint8_t * s) +{ + EVP_PKEY * pub; + const char * algo; + uint8_t salt[HKDF_SALT_LEN]; + ssize_t ret; + + assert(pk.data != NULL); + assert(ct != NULL); + assert(s != NULL); + + if (derive_salt_from_pk_bytes(pk, salt, HKDF_SALT_LEN) < 0) + goto fail_salt; + + algo = __openssl_hybrid_algo_from_len(pk.len); + if (algo == NULL) + goto fail_salt; + + pub = EVP_PKEY_new_raw_public_key_ex(NULL, algo, NULL, + pk.data, pk.len); + if (pub == NULL) + goto fail_salt; + + ret = __openssl_kem_encap(pub, salt, ct, kdf, s); + + EVP_PKEY_free(pub); + + return ret; + fail_salt: + return -ECRYPT; +} + +/* KEM decapsulation - used by party that generated the keypair */ +int openssl_kem_decap(EVP_PKEY * priv, + buffer_t ct, + int kdf, + uint8_t * s) +{ + EVP_PKEY_CTX * ctx; + struct kdf_info ki; + buffer_t pk; + uint8_t * secret; + size_t secret_len; + int ret; + uint8_t salt[HKDF_SALT_LEN]; + + /* Extract public key bytes from private key */ + if (get_pk_bytes_from_key(priv, &pk) < 0) + goto fail_pk; + + if (derive_salt_from_pk_bytes(pk, salt, HKDF_SALT_LEN) < 0) + goto fail_salt; + + ctx = EVP_PKEY_CTX_new(priv, NULL); + if (ctx == NULL) + goto fail_salt; + + ret = EVP_PKEY_decapsulate_init(ctx, NULL); + if (ret != 1) + goto fail_ctx; + + /* Get required secret length */ + ret = EVP_PKEY_decapsulate(ctx, NULL, &secret_len, ct.data, ct.len); + if (ret != 1) + goto fail_ctx; + + /* Allocate buffer for secret */ + secret = OPENSSL_malloc(secret_len); + if (secret == NULL) + goto fail_ctx; + + /* Perform decapsulation */ + ret = EVP_PKEY_decapsulate(ctx, secret, &secret_len, ct.data, ct.len); + if (ret != 1) + goto fail_secret; + + ki.secret.len = secret_len; + ki.secret.data = secret; + ki.nid = kdf; + ki.info.len = strlen(HKDF_INFO_ENCAP); + ki.info.data = (uint8_t *) HKDF_INFO_ENCAP; + ki.key.len = SYMMKEYSZ; + ki.key.data = s; + ki.salt.len = HKDF_SALT_LEN; + ki.salt.data = salt; + + /* Derive symmetric key from shared secret using HKDF */ + ret = derive_key_hkdf(&ki); + + OPENSSL_free(secret); + EVP_PKEY_CTX_free(ctx); + OPENSSL_free(pk.data); + + if (ret != 0) + return ret; + + return 0; + + fail_secret: + OPENSSL_free(secret); + fail_ctx: + EVP_PKEY_CTX_free(ctx); + fail_salt: + OPENSSL_free(pk.data); + fail_pk: + return -ECRYPT; +} + +void openssl_pkp_destroy(EVP_PKEY * pkp) +{ + EVP_PKEY_free(pkp); +} + +int __openssl_get_curve(EVP_PKEY * pub, + char * algo) +{ + int ret; + size_t len = KEX_ALGO_BUFSZ; + + ret = EVP_PKEY_get_utf8_string_param(pub, "group", algo, len, &len); + return ret == 1 ? 0 : -ECRYPT; +} + +int openssl_get_algo_from_pk_der(buffer_t pk, + char * algo) +{ + uint8_t * pos; + EVP_PKEY * pub; + char * type_str; + + assert(pk.data != NULL); + assert(algo != NULL); + + pos = pk.data; + pub = d2i_PUBKEY(NULL, (const uint8_t **) &pos, (long) pk.len); + if (pub == NULL) + goto fail_decode; + + type_str = (char *) EVP_PKEY_get0_type_name(pub); + if (type_str == NULL) + goto fail_pub; + + strcpy(algo, type_str); + + if ((IS_EC_GROUP(algo) || IS_DH_GROUP(algo)) && + __openssl_get_curve(pub, algo) < 0) + goto fail_pub; + + EVP_PKEY_free(pub); + return 0; + + fail_pub: + EVP_PKEY_free(pub); + fail_decode: + return -ECRYPT; +} + +int openssl_get_algo_from_pk_raw(buffer_t pk, + char * algo) +{ + const char * hybrid_algo; + + assert(pk.data != NULL); + assert(algo != NULL); + + hybrid_algo = __openssl_hybrid_algo_from_len(pk.len); + if (hybrid_algo == NULL) + return -ECRYPT; + + strcpy(algo, hybrid_algo); + + return 0; +} + +int openssl_dhe_derive(EVP_PKEY * pkp, + buffer_t pk, + int kdf, + uint8_t * s) +{ + uint8_t * pos; + EVP_PKEY * pub; + + assert(pkp != NULL); + assert(pk.data != NULL); + assert(s != NULL); + + /* X.509 DER decoding for DHE */ + pos = pk.data; /* d2i_PUBKEY increments pos, don't use key ptr! */ + pub = d2i_PUBKEY(NULL, (const uint8_t **) &pos, (long) pk.len); + if (pub == NULL) + goto fail_decode; + + if (__openssl_dhe_derive(pkp, pub, pk, kdf, s) < 0) + goto fail_derive; + + EVP_PKEY_free(pub); + + return 0; + fail_derive: + EVP_PKEY_free(pub); + fail_decode: + return -ECRYPT; +} + +int openssl_encrypt(struct ossl_crypt_ctx * ctx, + buffer_t in, + buffer_t * out) +{ + uint8_t * ptr; + uint8_t * iv; + int in_sz; + int out_sz; + int tmp_sz; + int ret; + + assert(ctx != NULL); + + in_sz = (int) in.len; + + out->data = malloc(in.len + EVP_MAX_BLOCK_LENGTH + \ + ctx->ivsz + ctx->tagsz); + if (out->data == NULL) + goto fail_malloc; + + iv = out->data; + ptr = out->data + ctx->ivsz; + + if (random_buffer(iv, ctx->ivsz) < 0) + goto fail_encrypt; + + /* Set IV bit 7 to current key phase (bit KEY_ROTATION_BIT of counter) */ + if (ctx->rot.cntr & ctx->rot.mask) + iv[0] |= 0x80; + else + iv[0] &= 0x7F; + + EVP_CIPHER_CTX_reset(ctx->evp_ctx); + + ret = EVP_EncryptInit_ex(ctx->evp_ctx, ctx->cipher, NULL, + ctx->keys.cur, iv); + if (ret != 1) + goto fail_encrypt; + + ret = EVP_EncryptUpdate(ctx->evp_ctx, ptr, &tmp_sz, in.data, in_sz); + if (ret != 1) + goto fail_encrypt; + + out_sz = tmp_sz; + ret = EVP_EncryptFinal_ex(ctx->evp_ctx, ptr + tmp_sz, &tmp_sz); + if (ret != 1) + goto fail_encrypt; + + out_sz += tmp_sz; + + /* For AEAD ciphers, get and append the authentication tag */ + if (ctx->tagsz > 0) { + ret = EVP_CIPHER_CTX_ctrl(ctx->evp_ctx, EVP_CTRL_AEAD_GET_TAG, + ctx->tagsz, ptr + out_sz); + if (ret != 1) + goto fail_encrypt; + out_sz += ctx->tagsz; + } + + assert(out_sz >= in_sz); + + out->len = (size_t) out_sz + ctx->ivsz; + + /* Increment packet counter and check for key rotation */ + ctx->rot.cntr++; + ctx->rot.age++; + + if (HAS_PHASE_BIT_TOGGLED(ctx)) { + if (rotate_key(ctx) != 0) + goto fail_encrypt; + } + + cleanup_old_key(ctx); + + return 0; + fail_encrypt: + free(out->data); + fail_malloc: + clrbuf(*out); + return -ECRYPT; +} + +int openssl_decrypt(struct ossl_crypt_ctx * ctx, + buffer_t in, + buffer_t * out) +{ + uint8_t * iv; + uint8_t * input; + uint8_t rx_phase; + int out_sz; + int in_sz; + + assert(ctx != NULL); + + in_sz = (int) in.len - ctx->ivsz; + if (in_sz < ctx->tagsz) + return -ECRYPT; + + in_sz -= ctx->tagsz; + + out->data = malloc(in_sz + EVP_MAX_BLOCK_LENGTH); + if (out->data == NULL) + goto fail_malloc; + + iv = in.data; + input = in.data + ctx->ivsz; + + /* Extract phase from IV bit 7 and check for key rotation */ + rx_phase = (iv[0] & 0x80) ? 1 : 0; + + if (should_rotate_key_rx(ctx, rx_phase)) { + if (rotate_key(ctx) != 0) + goto fail_decrypt; + } + + ctx->rot.cntr++; + ctx->rot.age++; + + if (try_decrypt(ctx, ctx->keys.cur, iv, input, in_sz, out->data, + &out_sz) != 0) { + if (ctx->keys.prv == NULL) + goto fail_decrypt; + if (try_decrypt(ctx, ctx->keys.prv, iv, input, in_sz, + out->data, &out_sz) != 0) + goto fail_decrypt; + } + + assert(out_sz <= in_sz); + + out->len = (size_t) out_sz; + + return 0; + fail_decrypt: + free(out->data); + fail_malloc: + clrbuf(*out); + return -ECRYPT; +} + +struct ossl_crypt_ctx * openssl_crypt_create_ctx(struct crypt_sk * sk) +{ + struct ossl_crypt_ctx * ctx; + + assert(sk != NULL); + assert(sk->key != NULL); + assert(sk->rot_bit > 0 && sk->rot_bit < 32); + + ctx = malloc(sizeof(*ctx)); + if (ctx == NULL) + goto fail_malloc; + + memset(ctx, 0, sizeof(*ctx)); + + ctx->keys.cur = OPENSSL_secure_malloc(SYMMKEYSZ); + if (ctx->keys.cur == NULL) + goto fail_key; + + memcpy(ctx->keys.cur, sk->key, SYMMKEYSZ); + + ctx->keys.prv = NULL; + + /* Derive rotation salt from initial shared secret */ + if (EVP_Digest(sk->key, SYMMKEYSZ, ctx->rot.salt, NULL, + EVP_sha256(), NULL) != 1) + goto fail_cipher; + + ctx->cipher = EVP_get_cipherbynid(sk->nid); + if (ctx->cipher == NULL) + goto fail_cipher; + + ctx->ivsz = EVP_CIPHER_iv_length(ctx->cipher); + + /* Set tag size for AEAD ciphers (GCM, CCM, OCB, ChaCha20-Poly1305) */ + if (EVP_CIPHER_flags(ctx->cipher) & EVP_CIPH_FLAG_AEAD_CIPHER) + ctx->tagsz = 16; /* Standard AEAD tag length (128 bits) */ + + ctx->rot.cntr = 0; + ctx->rot.mask = (1U << sk->rot_bit); + ctx->rot.age = 0; + ctx->rot.phase = 0; + + ctx->evp_ctx = EVP_CIPHER_CTX_new(); + if (ctx->evp_ctx == NULL) + goto fail_cipher; + + return ctx; + + fail_cipher: + OPENSSL_secure_clear_free(ctx->keys.cur, SYMMKEYSZ); + fail_key: + free(ctx); + fail_malloc: + return NULL; +} + +void openssl_crypt_destroy_ctx(struct ossl_crypt_ctx * ctx) +{ + if (ctx == NULL) + return; + + if (ctx->keys.cur != NULL) + OPENSSL_secure_clear_free(ctx->keys.cur, SYMMKEYSZ); + + if (ctx->keys.prv != NULL) + OPENSSL_secure_clear_free(ctx->keys.prv, SYMMKEYSZ); + + EVP_CIPHER_CTX_free(ctx->evp_ctx); + free(ctx); +} + +int openssl_crypt_get_ivsz(struct ossl_crypt_ctx * ctx) +{ + assert(ctx != NULL); + + return ctx->ivsz; +} + +int openssl_crypt_get_tagsz(struct ossl_crypt_ctx * ctx) +{ + assert(ctx != NULL); + + return ctx->tagsz; +} + +/* AUTHENTICATION */ + +int openssl_load_crt_file(const char * path, + void ** crt) +{ + FILE * fp; + X509 * xcrt; + + fp = fopen(path, "r"); + if (fp == NULL) + goto fail_file; + + xcrt = PEM_read_X509(fp, NULL, NULL, NULL); + if (xcrt == NULL) + goto fail_crt; + + fclose(fp); + + *crt = (void *) xcrt; + + return 0; + fail_crt: + fclose(fp); + fail_file: + *crt = NULL; + return -1; +} + +int openssl_load_crt_str(const char * str, + void ** crt) +{ + BIO * bio; + X509 * xcrt; + + bio = BIO_new(BIO_s_mem()); + if (bio == NULL) + goto fail_bio; + + if (BIO_write(bio, str, strlen(str)) < 0) + goto fail_crt; + + xcrt = PEM_read_bio_X509(bio, NULL, NULL, NULL); + if (xcrt == NULL) + goto fail_crt; + + BIO_free(bio); + + *crt = (void *) xcrt; + + return 0; + fail_crt: + BIO_free(bio); + fail_bio: + *crt = NULL; + return -1; +} + +int openssl_load_crt_der(buffer_t buf, + void ** crt) +{ + const uint8_t * p; + X509 * xcrt; + + assert(crt != NULL); + + p = buf.data; + + xcrt = d2i_X509(NULL, &p, buf.len); + if (xcrt == NULL) + goto fail_crt; + + *crt = (void *) xcrt; + + return 0; + fail_crt: + *crt = NULL; + return -1; +} + +int openssl_get_pubkey_crt(void * crt, + void ** key) +{ + EVP_PKEY * pk; + X509 * xcrt; + + assert(crt != NULL); + assert(key != NULL); + + xcrt = (X509 *) crt; + + pk = X509_get_pubkey(xcrt); + if (pk == NULL) + goto fail_key; + + *key = (void *) pk; + + return 0; + fail_key: + return -1; +} + +void openssl_free_crt(void * crt) +{ + X509_free((X509 *) crt); +} + +int openssl_load_privkey_file(const char * path, + void ** key) +{ + FILE * fp; + EVP_PKEY * pkey; + unsigned long err; + char errbuf[256]; + + fp = fopen(path, "r"); + if (fp == NULL) { + fprintf(stderr, "Failed to open %s\n", path); + goto fail_file; + } + + pkey = PEM_read_PrivateKey(fp, NULL, NULL, ""); + if (pkey == NULL) { + err = ERR_get_error(); + ERR_error_string_n(err, errbuf, sizeof(errbuf)); + fprintf(stderr, + "OpenSSL error loading privkey from %s: %s\n", + path, errbuf); + goto fail_key; + } + + fclose(fp); + + *key = (void *) pkey; + + return 0; + fail_key: + fclose(fp); + fail_file: + *key = NULL; + return -1; +} + +int openssl_load_privkey_str(const char * str, + void ** key) +{ + BIO * bio; + EVP_PKEY * pkey; + + bio = BIO_new(BIO_s_mem()); + if (bio == NULL) + goto fail_bio; + + if (BIO_write(bio, str, strlen(str)) < 0) + goto fail_key; + + pkey = PEM_read_bio_PrivateKey(bio, NULL, NULL, NULL); + if (pkey == NULL) + goto fail_key; + + BIO_free(bio); + + *key = (void *) pkey; + + return 0; + fail_key: + BIO_free(bio); + fail_bio: + *key = NULL; + return -1; +} + +int openssl_load_pubkey_file(const char * path, + void ** key) +{ + FILE * fp; + EVP_PKEY * pkey; + + fp = fopen(path, "r"); + if (fp == NULL) + goto fail_file; + + pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL); + if (pkey == NULL) + goto fail_key; + + fclose(fp); + + *key = (void *) pkey; + + return 0; + fail_key: + fclose(fp); + fail_file: + *key = NULL; + return -1; +} + +int openssl_load_pubkey_file_to_der(const char * path, + buffer_t * buf) +{ + FILE * fp; + EVP_PKEY * pkey; + int ret; + + assert(path != NULL); + assert(buf != NULL); + + memset(buf, 0, sizeof(*buf)); + + fp = fopen(path, "r"); + if (fp == NULL) + goto fail_file; + + pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL); + if (pkey == NULL) + goto fail_key; + + /* Extract public key bytes in DER format */ + ret = get_pk_bytes_from_key(pkey, buf); + if (ret < 0) + goto fail_extract; + + EVP_PKEY_free(pkey); + + fclose(fp); + + return 0; + + fail_extract: + EVP_PKEY_free(pkey); + fail_key: + fclose(fp); + fail_file: + clrbuf(*buf); + return -1; +} + +int openssl_load_pubkey_str(const char * str, + void ** key) +{ + BIO * bio; + EVP_PKEY * pkey; + + bio = BIO_new(BIO_s_mem()); + if (bio == NULL) + goto fail_bio; + + if (BIO_write(bio, str, strlen(str)) < 0) + goto fail_key; + + pkey = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL); + if (pkey == NULL) + goto fail_key; + + BIO_free(bio); + + *key = (void *) pkey; + + return 0; + fail_key: + BIO_free(bio); + fail_bio: + *key = NULL; + return -1; +} + +int openssl_load_pubkey_raw_file(const char * path, + buffer_t * buf) +{ + FILE * fp; + uint8_t tmp_buf[MSGBUFSZ]; + size_t bytes_read; + const char * algo; + + assert(path != NULL); + assert(buf != NULL); + + fp = fopen(path, "rb"); + if (fp == NULL) + goto fail_file; + + bytes_read = fread(tmp_buf, 1, MSGBUFSZ, fp); + if (bytes_read == 0) + goto fail_read; + + /* Validate that this is a known hybrid KEM format */ + algo = __openssl_hybrid_algo_from_len(bytes_read); + if (algo == NULL) + goto fail_read; + + buf->data = malloc(bytes_read); + if (buf->data == NULL) + goto fail_malloc; + + memcpy(buf->data, tmp_buf, bytes_read); + buf->len = bytes_read; + + fclose(fp); + return 0; + + fail_malloc: + fail_read: + fclose(fp); + fail_file: + clrbuf(*buf); + return -1; +} + +/* Determine hybrid KEM algorithm from raw private key length */ +static const char * __openssl_hybrid_algo_from_sk_len(size_t len) +{ + switch(len) { + case X25519MLKEM768_SKSZ: + return "X25519MLKEM768"; + case X448MLKEM1024_SKSZ: + return "X448MLKEM1024"; + default: + break; + } + + return NULL; +} + +int openssl_load_privkey_raw_file(const char * path, + void ** key) +{ + FILE * fp; + uint8_t tmp_buf[4096]; + size_t bytes_read; + const char * algo; + EVP_PKEY * pkey; + + assert(path != NULL); + assert(key != NULL); + + fp = fopen(path, "rb"); + if (fp == NULL) + goto fail_file; + + bytes_read = fread(tmp_buf, 1, sizeof(tmp_buf), fp); + if (bytes_read == 0) + goto fail_read; + + /* Determine algorithm from key size */ + algo = __openssl_hybrid_algo_from_sk_len(bytes_read); + if (algo == NULL) + goto fail_read; + + pkey = EVP_PKEY_new_raw_private_key_ex(NULL, algo, NULL, + tmp_buf, bytes_read); + /* Clear sensitive data from stack */ + OPENSSL_cleanse(tmp_buf, bytes_read); + + if (pkey == NULL) + goto fail_read; + + fclose(fp); + + *key = (void *) pkey; + + return 0; + + fail_read: + fclose(fp); + fail_file: + *key = NULL; + return -1; +} + +int openssl_cmp_key(const EVP_PKEY * key1, + const EVP_PKEY * key2) +{ + assert(key1 != NULL); + assert(key2 != NULL); + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + return EVP_PKEY_eq(key1, key2) == 1 ? 0 : -1; +#else + return EVP_PKEY_cmp(key1, key2) == 1 ? 0 : -1; +#endif +} + +void openssl_free_key(EVP_PKEY * key) +{ + EVP_PKEY_free(key); +} + +int openssl_check_crt_name(void * crt, + const char * name) +{ + char * subj; + char * cn; + X509 * xcrt; + + xcrt = (X509 *) crt; + + subj = X509_NAME_oneline(X509_get_subject_name(xcrt), NULL, 0); + if (subj == NULL) + goto fail_subj; + + cn = strstr(subj, "CN="); + if (cn == NULL) + goto fail_cn; + + if (strcmp(cn + 3, name) != 0) + goto fail_cn; + + free(subj); + + return 0; + fail_cn: + free(subj); + fail_subj: + return -1; +} + +int openssl_get_crt_name(void * crt, + char * name) +{ + char * subj; + char * cn; + char * end; + X509 * xcrt; + + xcrt = (X509 *) crt; + + subj = X509_NAME_oneline(X509_get_subject_name(xcrt), NULL, 0); + if (subj == NULL) + goto fail_subj; + + cn = strstr(subj, "CN="); + if (cn == NULL) + goto fail_cn; + + cn += 3; /* Skip "CN=" */ + + /* Find end of CN (comma or slash for next field) */ + end = strpbrk(cn, ",/"); + if (end != NULL) + *end = '\0'; + + strcpy(name, cn); + free(subj); + + return 0; + fail_cn: + free(subj); + fail_subj: + return -1; +} + +int openssl_crt_str(const void * crt, + char * str) +{ + BIO * bio; + X509 * xcrt; + char * p; + ssize_t len; + + xcrt = (X509 *) crt; + + bio = BIO_new(BIO_s_mem()); + if (bio == NULL) + goto fail_bio; + + X509_print(bio, xcrt); + + len = (ssize_t) BIO_get_mem_data(bio, &p); + if (len <= 0 || p == NULL) + goto fail_p; + + memcpy(str, p, len); + str[len] = '\0'; + + BIO_free(bio); + + return 0; + fail_p: + BIO_free(bio); + fail_bio: + return -1; +} + +int openssl_crt_der(const void * crt, + buffer_t * buf) +{ + int len; + + assert(crt != NULL); + assert(buf != NULL); + + len = i2d_X509((X509 *) crt, &buf->data); + if (len < 0) + goto fail_der; + + buf->len = (size_t) len; + + return 0; + + fail_der: + clrbuf(*buf); + return -1; +} + + +void * openssl_auth_create_store(void) +{ + return X509_STORE_new(); +} + +void openssl_auth_destroy_store(void * ctx) +{ + X509_STORE_free((X509_STORE *) ctx); +} + +int openssl_auth_add_crt_to_store(void * store, + void * crt) +{ + int ret; + + ret = X509_STORE_add_cert((X509_STORE *) store, (X509 *) crt); + + return ret == 1 ? 0 : -1; +} + +int openssl_verify_crt(void * store, + void * crt) +{ + X509_STORE_CTX * ctx; + X509_STORE * _store; + X509* _crt; + int ret; + + _store = (X509_STORE *) store; + _crt = (X509 *) crt; + + ctx = X509_STORE_CTX_new(); + if (ctx == NULL) + goto fail_store_ctx; + + ret = X509_STORE_CTX_init(ctx, _store, _crt, NULL); + if (ret != 1) + goto fail_ca; + + ret = X509_verify_cert(ctx); + if (ret != 1) + goto fail_ca; + + X509_STORE_CTX_free(ctx); + + return 0; + fail_ca: + X509_STORE_CTX_free(ctx); + fail_store_ctx: + return -1; +} + +static const EVP_MD * select_md(EVP_PKEY * pkey, + int nid) +{ + if (EVP_PKEY_get_id(pkey) < 0) + return NULL; /* Provider-based (PQC) */ + + if (nid == NID_undef) + return NULL; /* Classical requires explicit nid */ + + return EVP_get_digestbynid(nid); +} + +int openssl_sign(EVP_PKEY * pkp, + int nid, + buffer_t msg, + buffer_t * sig) +{ + EVP_MD_CTX * mdctx; + const EVP_MD * md; + size_t required; + + assert(pkp != NULL); + assert(sig != NULL); + + mdctx = EVP_MD_CTX_new(); + if (!mdctx) + goto fail_ctx; + + md = select_md(pkp, nid); + + if (EVP_DigestSignInit(mdctx, NULL, md, NULL, pkp) != 1) + goto fail_digest; + + /* Get required signature buffer size */ + if (EVP_DigestSign(mdctx, NULL, &required, msg.data, msg.len) != 1) + goto fail_digest; + + sig->data = malloc(required); + if (sig->data == NULL) + goto fail_digest; + + if (EVP_DigestSign(mdctx, sig->data, &required, msg.data, msg.len) != 1) + goto fail_sign; + + sig->len = required; + + EVP_MD_CTX_free(mdctx); + + return 0; + fail_sign: + freebuf(*sig); + fail_digest: + EVP_MD_CTX_free(mdctx); + fail_ctx: + clrbuf(*sig); + return -1; +} + +int openssl_verify_sig(EVP_PKEY * pk, + int nid, + buffer_t msg, + buffer_t sig) +{ + EVP_MD_CTX * mdctx; + const EVP_MD * md; + int ret; + + assert(pk != NULL); + + mdctx = EVP_MD_CTX_new(); + if (!mdctx) + goto fail_ctx; + + md = select_md(pk, nid); + + if (EVP_DigestVerifyInit(mdctx, NULL, md, NULL, pk) != 1) + goto fail_digest; + + ret = EVP_DigestVerify(mdctx, sig.data, sig.len, msg.data, msg.len); + if (ret != 1) + goto fail_digest; + + EVP_MD_CTX_free(mdctx); + + return 0; + fail_digest: + EVP_MD_CTX_free(mdctx); + fail_ctx: + clrbuf(sig); + return -1; +} + +ssize_t openssl_md_digest(int nid, + buffer_t in, + uint8_t * out) +{ + const EVP_MD * md; + unsigned int len; + + assert(in.data != NULL); + assert(out != NULL); + + md = EVP_get_digestbynid(nid); + if (md == NULL) + return -1; + + if (EVP_Digest(in.data, in.len, out, &len, md, NULL) != 1) + return -1; + + return (ssize_t) len; +} + +ssize_t openssl_md_len(int nid) +{ + const EVP_MD * md; + + md = EVP_get_digestbynid(nid); + if (md == NULL) + return -1; + + return (ssize_t) EVP_MD_get_size(md); +} + +int openssl_secure_malloc_init(size_t max, + size_t guard) +{ + return CRYPTO_secure_malloc_init(max, guard) == 1 ? 0 : -1; +} + +void openssl_secure_malloc_fini(void) +{ + CRYPTO_secure_malloc_done(); +} + +void * openssl_secure_malloc(size_t size) +{ + return OPENSSL_secure_malloc(size); +} + +void openssl_secure_free(void * ptr) +{ + OPENSSL_secure_free(ptr); +} + +void openssl_secure_clear(void * ptr, + size_t size) +{ + OPENSSL_cleanse(ptr, size); +} diff --git a/src/lib/crypt/openssl.h b/src/lib/crypt/openssl.h new file mode 100644 index 00000000..a6bb5840 --- /dev/null +++ b/src/lib/crypt/openssl.h @@ -0,0 +1,172 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * OpenSSL based cryptographic operations + * Elliptic curve Diffie-Hellman key exchange + * AES encryption + # Authentication + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#ifndef OUROBOROS_LIB_CRYPT_OPENSSL_H +#define OUROBOROS_LIB_CRYPT_OPENSSL_H + +struct ossl_crypt_ctx; + +ssize_t openssl_pkp_create(const char * algo, + EVP_PKEY ** pkp, + uint8_t * pk); + +void openssl_pkp_destroy(EVP_PKEY * pkp); + +int openssl_dhe_derive(EVP_PKEY * pkp, + buffer_t pk, + int kdf_nid, + uint8_t * s); + +ssize_t openssl_kem_encap(buffer_t pk, + uint8_t * ct, + int kdf_nid, + uint8_t * s); + +/* no X509 DER support yet for DHKEM public keys */ +ssize_t openssl_kem_encap_raw(buffer_t pk, + uint8_t * ct, + int kdf_nid, + uint8_t * s); + +int openssl_kem_decap(EVP_PKEY * priv, + buffer_t ct, + int kdf_nid, + uint8_t * s); + +int openssl_get_algo_from_pk_der(buffer_t pk, + char * algo); + +int openssl_get_algo_from_pk_raw(buffer_t pk, + char * algo); + +int openssl_encrypt(struct ossl_crypt_ctx * ctx, + buffer_t in, + buffer_t * out); + +int openssl_decrypt(struct ossl_crypt_ctx * ctx, + buffer_t in, + buffer_t * out); + +struct ossl_crypt_ctx * openssl_crypt_create_ctx(struct crypt_sk * sk); + +void openssl_crypt_destroy_ctx(struct ossl_crypt_ctx * ctx); + +int openssl_crypt_get_ivsz(struct ossl_crypt_ctx * ctx); + +int openssl_crypt_get_tagsz(struct ossl_crypt_ctx * ctx); + +/* AUTHENTICATION */ + +int openssl_load_crt_file(const char * path, + void ** crt); + +int openssl_load_crt_str(const char * str, + void ** crt); + +int openssl_load_crt_der(buffer_t buf, + void ** crt); + +int openssl_get_pubkey_crt(void * crt, + void ** pk); + +void openssl_free_crt(void * crt); + +int openssl_load_privkey_file(const char * path, + void ** key); + +int openssl_load_privkey_str(const char * str, + void ** key); + +int openssl_load_pubkey_file(const char * path, + void ** key); + +int openssl_load_pubkey_str(const char * str, + void ** key); +int openssl_load_pubkey_file_to_der(const char * path, + buffer_t * buf); +int openssl_load_pubkey_raw_file(const char * path, + buffer_t * buf); + +int openssl_load_privkey_raw_file(const char * path, + void ** key); + +int openssl_cmp_key(const EVP_PKEY * key1, + const EVP_PKEY * key2); + +void openssl_free_key(EVP_PKEY * key); + +int openssl_check_crt_name(void * crt, + const char * name); + +int openssl_get_crt_name(void * crt, + char * name); + +int openssl_crt_str(const void * crt, + char * str); + +int openssl_crt_der(const void * crt, + buffer_t * buf); + +void * openssl_auth_create_store(void); + +void openssl_auth_destroy_store(void * store); + +int openssl_auth_add_crt_to_store(void * store, + void * crt); + +int openssl_verify_crt(void * store, + void * crt); + +int openssl_sign(EVP_PKEY * pkp, + int md_nid, + buffer_t msg, + buffer_t * sig); + +int openssl_verify_sig(EVP_PKEY * pk, + int md_nid, + buffer_t msg, + buffer_t sig); + +ssize_t openssl_md_digest(int md_nid, + buffer_t in, + uint8_t * out); + +ssize_t openssl_md_len(int md_nid); + +/* Secure memory allocation */ +int openssl_secure_malloc_init(size_t max, + size_t guard); + +void openssl_secure_malloc_fini(void); + +void * openssl_secure_malloc(size_t size); + +void openssl_secure_free(void * ptr, + size_t size); + +void openssl_secure_clear(void * ptr, + size_t size); + +#endif /* OUROBOROS_LIB_CRYPT_OPENSSL_H */ diff --git a/src/lib/dev.c b/src/lib/dev.c index c8b7d93b..454dd027 100644 --- a/src/lib/dev.c +++ b/src/lib/dev.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * API for applications * @@ -27,27 +27,38 @@ #endif #include "config.h" +#include "ssm.h" -#include <ouroboros/hash.h> -#include <ouroboros/cacep.h> -#include <ouroboros/errno.h> +#include <ouroboros/bitmap.h> +#include <ouroboros/cep.h> +#include <ouroboros/crypt.h> #include <ouroboros/dev.h> +#include <ouroboros/errno.h> +#include <ouroboros/fccntl.h> +#include <ouroboros/flow.h> +#include <ouroboros/fqueue.h> +#include <ouroboros/hash.h> +#include <ouroboros/ipcp.h> #include <ouroboros/ipcp-dev.h> +#include <ouroboros/list.h> #include <ouroboros/local-dev.h> -#include <ouroboros/sockets.h> -#include <ouroboros/fccntl.h> -#include <ouroboros/bitmap.h> +#include <ouroboros/np1_flow.h> #include <ouroboros/pthread.h> #include <ouroboros/random.h> -#include <ouroboros/shm_flow_set.h> -#include <ouroboros/shm_rdrbuff.h> -#include <ouroboros/shm_rbuff.h> +#include <ouroboros/serdes-irm.h> +#include <ouroboros/ssm_flow_set.h> +#include <ouroboros/ssm_pool.h> +#include <ouroboros/ssm_rbuff.h> +#include <ouroboros/sockets.h> #include <ouroboros/utils.h> -#include <ouroboros/fqueue.h> #ifdef PROC_FLOW_STATS #include <ouroboros/rib.h> #endif +#ifdef HAVE_LIBGCRYPT +#include <gcrypt.h> +#endif +#include <assert.h> #include <stdlib.h> #include <string.h> #include <stdio.h> @@ -65,51 +76,36 @@ #define CRCLEN (sizeof(uint32_t)) #define SECMEMSZ 16384 -#define SYMMKEYSZ 32 #define MSGBUFSZ 2048 -struct flow_set { - size_t idx; -}; - -struct fqueue { - int fqueue[2 * SHM_BUFFER_SIZE]; /* Safe copy from shm. */ - size_t fqsize; - size_t next; -}; - -enum port_state { - PORT_NULL = 0, - PORT_INIT, - PORT_ID_PENDING, - PORT_ID_ASSIGNED, - PORT_DESTROY -}; - -struct port { +/* map flow_ids to flow descriptors; track state of the flow */ +struct fmap { int fd; - - enum port_state state; - pthread_mutex_t state_lock; - pthread_cond_t state_cond; + /* TODO: use actual flow state */ + enum flow_state state; }; #define frcti_to_flow(frcti) \ ((struct flow *)((uint8_t *) frcti - offsetof(struct flow, frcti))) struct flow { - struct shm_rbuff * rx_rb; - struct shm_rbuff * tx_rb; - struct shm_flow_set * set; - int flow_id; - int oflags; - qosspec_t qs; + struct list_head next; + + struct flow_info info; + + struct ssm_rbuff * rx_rb; + struct ssm_rbuff * tx_rb; + struct ssm_flow_set * set; + + uint16_t oflags; ssize_t part_idx; - void * ctx; - uint8_t key[SYMMKEYSZ]; + struct crypt_ctx * crypt; + int headsz; /* IV */ + int tailsz; /* Tag + CRC */ - pid_t pid; + struct timespec snd_act; + struct timespec rcv_act; bool snd_timesout; bool rcv_timesout; @@ -119,232 +115,492 @@ struct flow { struct frcti * frcti; }; -struct { - char * prog; - pid_t pid; +struct flow_set { + size_t idx; + pthread_rwlock_t lock; +}; - struct shm_rdrbuff * rdrb; - struct shm_flow_set * fqset; +struct fqueue { + struct flowevent fqueue[SSM_RBUFF_SIZE]; /* Safe copy from shm. */ + size_t fqsize; + size_t next; +}; - struct timerwheel * tw; +struct { + struct ssm_pool * pool; + struct ssm_flow_set * fqset; struct bmp * fds; struct bmp * fqueues; struct flow * flows; - struct port * ports; + struct fmap * id_to_fd; + struct list_head flow_list; - pthread_rwlock_t lock; -} ai; + pthread_mutex_t mtx; + pthread_cond_t cond; -#include "frct.c" + pthread_t tx; + pthread_t rx; + size_t n_frcti; + fset_t * frct_set; -static void port_destroy(struct port * p) + pthread_rwlock_t lock; +} proc; + +static void flow_destroy(struct fmap * p) { - pthread_mutex_lock(&p->state_lock); + pthread_mutex_lock(&proc.mtx); - if (p->state == PORT_DESTROY) { - pthread_mutex_unlock(&p->state_lock); + if (p->state == FLOW_DESTROY) { + pthread_mutex_unlock(&proc.mtx); return; } - if (p->state == PORT_ID_PENDING) - p->state = PORT_DESTROY; + if (p->state == FLOW_ALLOC_PENDING) + p->state = FLOW_DESTROY; else - p->state = PORT_NULL; + p->state = FLOW_NULL; + + pthread_cond_signal(&proc.cond); - pthread_cond_signal(&p->state_cond); + pthread_cleanup_push(__cleanup_mutex_unlock, &proc.mtx); - while (p->state != PORT_NULL) - pthread_cond_wait(&p->state_cond, &p->state_lock); + while (p->state != FLOW_NULL) + pthread_cond_wait(&proc.cond, &proc.mtx); - p->fd = -1; - p->state = PORT_INIT; + p->fd = -1; + p->state = FLOW_INIT; - pthread_mutex_unlock(&p->state_lock); + pthread_cleanup_pop(true); } -static void port_set_state(struct port * p, - enum port_state state) +static void flow_set_state(struct fmap * p, + enum flow_state state) { - pthread_mutex_lock(&p->state_lock); + pthread_mutex_lock(&proc.mtx); - if (p->state == PORT_DESTROY) { - pthread_mutex_unlock(&p->state_lock); + if (p->state == FLOW_DESTROY) { + pthread_mutex_unlock(&proc.mtx); return; } p->state = state; - pthread_cond_broadcast(&p->state_cond); + pthread_cond_broadcast(&proc.cond); - pthread_mutex_unlock(&p->state_lock); + pthread_mutex_unlock(&proc.mtx); } -static enum port_state port_wait_assign(int flow_id) +static enum flow_state flow_wait_assign(int flow_id) { - enum port_state state; - struct port * p; + enum flow_state state; + struct fmap * p; - p = &ai.ports[flow_id]; + p = &proc.id_to_fd[flow_id]; - pthread_mutex_lock(&p->state_lock); + pthread_mutex_lock(&proc.mtx); - if (p->state == PORT_ID_ASSIGNED) { - pthread_mutex_unlock(&p->state_lock); - return PORT_ID_ASSIGNED; + if (p->state == FLOW_ALLOCATED) { + pthread_mutex_unlock(&proc.mtx); + return FLOW_ALLOCATED; } - if (p->state == PORT_INIT) - p->state = PORT_ID_PENDING; + if (p->state == FLOW_INIT) + p->state = FLOW_ALLOC_PENDING; - while (p->state == PORT_ID_PENDING) - pthread_cond_wait(&p->state_cond, &p->state_lock); + pthread_cleanup_push(__cleanup_mutex_unlock, &proc.mtx); - if (p->state == PORT_DESTROY) { - p->state = PORT_NULL; - pthread_cond_broadcast(&p->state_cond); + while (p->state == FLOW_ALLOC_PENDING) + pthread_cond_wait(&proc.cond, &proc.mtx); + + if (p->state == FLOW_DESTROY) { + p->state = FLOW_NULL; + pthread_cond_broadcast(&proc.cond); } state = p->state; - assert(state != PORT_INIT); + pthread_cleanup_pop(true); - pthread_mutex_unlock(&p->state_lock); + assert(state != FLOW_INIT); return state; } -static int proc_announce(char * prog) +static int proc_announce(const struct proc_info * proc) +{ + uint8_t buf[SOCK_BUF_SIZE]; + buffer_t msg = {SOCK_BUF_SIZE, buf}; + int err; + + if (proc_announce__irm_req_ser(&msg, proc) < 0) + return -ENOMEM; + + err = send_recv_msg(&msg); + if (err < 0) + return err; + + return irm__irm_result_des(&msg); +} + +/* IRMd will clean up the mess if this fails */ +static void proc_exit(void) +{ + uint8_t buf[SOCK_BUF_SIZE]; + buffer_t msg = {SOCK_BUF_SIZE, buf}; + + if (proc_exit__irm_req_ser(&msg) < 0) + return; + + send_recv_msg(&msg); +} + +static int spb_encrypt(struct flow * flow, + struct ssm_pk_buff * spb) +{ + buffer_t in; + buffer_t out; + uint8_t * head; + uint8_t * tail; + + if (flow->crypt == NULL) + return 0; /* No encryption */ + + in.data = ssm_pk_buff_head(spb); + in.len = ssm_pk_buff_len(spb); + + if (crypt_encrypt(flow->crypt, in, &out) < 0) + goto fail_encrypt; + + head = ssm_pk_buff_head_alloc(spb, flow->headsz); + if (head == NULL) + goto fail_alloc; + + tail = ssm_pk_buff_tail_alloc(spb, flow->tailsz); + if (tail == NULL) + goto fail_alloc; + + memcpy(head, out.data, out.len); + + freebuf(out); + + return 0; + fail_alloc: + freebuf(out); + fail_encrypt: + return -ECRYPT; +} + +static int spb_decrypt(struct flow * flow, + struct ssm_pk_buff * spb) +{ + buffer_t in; + buffer_t out; + uint8_t * head; + + if (flow->crypt == NULL) + return 0; /* No decryption */ + + in.data = ssm_pk_buff_head(spb); + in.len = ssm_pk_buff_len(spb); + + if (crypt_decrypt(flow->crypt, in, &out) < 0) + return -ENOMEM; + + + head = ssm_pk_buff_head_release(spb, flow->headsz) + flow->headsz; + ssm_pk_buff_tail_release(spb, flow->tailsz); + + memcpy(head, out.data, out.len); + + freebuf(out); + + return 0; +} + +#include "frct.c" + +void * flow_tx(void * o) { - irm_msg_t msg = IRM_MSG__INIT; - irm_msg_t * recv_msg; - int ret = -1; + struct timespec tic = TIMESPEC_INIT_NS(TICTIME); + + (void) o; - msg.code = IRM_MSG_CODE__IRM_PROC_ANNOUNCE; - msg.has_pid = true; - msg.pid = ai.pid; - msg.prog = prog; + while (true) { + timerwheel_move(); - recv_msg = send_recv_irm_msg(&msg); - if (recv_msg == NULL) { - return -EIRMD; + nanosleep(&tic, NULL); } - if (!recv_msg->has_result || (ret = recv_msg->result)) { - irm_msg__free_unpacked(recv_msg, NULL); - return ret; + return (void *) 0; +} + +static void flow_send_keepalive(struct flow * flow, + struct timespec now) +{ + struct ssm_pk_buff * spb; + ssize_t idx; + uint8_t * ptr; + + idx = ssm_pool_alloc(proc.pool, 0, &ptr, &spb); + if (idx < 0) + return; + + pthread_rwlock_wrlock(&proc.lock); + + flow->snd_act = now; + + if (ssm_rbuff_write(flow->tx_rb, idx)) + ssm_pool_remove(proc.pool, idx); + else + ssm_flow_set_notify(flow->set, flow->info.id, FLOW_PKT); + + pthread_rwlock_unlock(&proc.lock); +} + +/* Needs rdlock on proc. */ +static void _flow_keepalive(struct flow * flow) +{ + struct timespec now; + struct timespec s_act; + struct timespec r_act; + int flow_id; + time_t timeo; + uint32_t acl; + + s_act = flow->snd_act; + r_act = flow->rcv_act; + + flow_id = flow->info.id; + timeo = flow->info.qs.timeout; + + acl = ssm_rbuff_get_acl(flow->rx_rb); + if (timeo == 0 || acl & (ACL_FLOWPEER | ACL_FLOWDOWN)) + return; + + clock_gettime(PTHREAD_COND_CLOCK, &now); + + if (ts_diff_ns(&now, &r_act) > (int64_t) timeo * MILLION) { + ssm_rbuff_set_acl(flow->rx_rb, ACL_FLOWPEER); + ssm_flow_set_notify(proc.fqset, flow_id, FLOW_PEER); + return; } - irm_msg__free_unpacked(recv_msg, NULL); + if (ts_diff_ns(&now, &s_act) > (int64_t) timeo * (MILLION >> 2)) { + pthread_rwlock_unlock(&proc.lock); - return ret; + flow_send_keepalive(flow, now); + + pthread_rwlock_rdlock(&proc.lock); + } } -static void flow_clear(int fd) +static void handle_keepalives(void) { - memset(&ai.flows[fd], 0, sizeof(ai.flows[fd])); + struct list_head * p; + struct list_head * h; + + pthread_rwlock_rdlock(&proc.lock); + + list_for_each_safe(p, h, &proc.flow_list) { + struct flow * flow; + flow = list_entry(p, struct flow, next); + _flow_keepalive(flow); + } - ai.flows[fd].flow_id = -1; - ai.flows[fd].pid = -1; + pthread_rwlock_unlock(&proc.lock); } -#include "crypt.c" +static void __cleanup_fqueue_destroy(void * fq) +{ + fqueue_destroy((fqueue_t *) fq); +} -static void flow_fini(int fd) +void * flow_rx(void * o) +{ + struct timespec tic = TIMESPEC_INIT_NS(TICTIME); + int ret; + struct fqueue * fq; + + (void) o; + + fq = fqueue_create(); + + pthread_cleanup_push(__cleanup_fqueue_destroy, fq); + + /* fevent will filter all FRCT packets for us */ + while ((ret = fevent(proc.frct_set, fq, &tic)) != 0) { + if (ret == -ETIMEDOUT) { + handle_keepalives(); + continue; + } + + while (fqueue_next(fq) >= 0) + ; /* no need to act */ + } + + pthread_cleanup_pop(true); + + return (void *) 0; +} + +static void flow_clear(int fd) +{ + memset(&proc.flows[fd], 0, sizeof(proc.flows[fd])); + + proc.flows[fd].info.id = -1; +} + +static void __flow_fini(int fd) { assert(fd >= 0 && fd < SYS_MAX_FLOWS); - if (ai.flows[fd].flow_id != -1) { - port_destroy(&ai.ports[ai.flows[fd].flow_id]); - bmp_release(ai.fds, fd); + if (proc.flows[fd].frcti != NULL) { + proc.n_frcti--; + if (proc.n_frcti == 0) { + pthread_cancel(proc.tx); + pthread_join(proc.tx, NULL); + } + + ssm_flow_set_del(proc.fqset, 0, proc.flows[fd].info.id); + + frcti_destroy(proc.flows[fd].frcti); } - if (ai.flows[fd].frcti != NULL) - frcti_destroy(ai.flows[fd].frcti); + if (proc.flows[fd].info.id != -1) { + flow_destroy(&proc.id_to_fd[proc.flows[fd].info.id]); + bmp_release(proc.fds, fd); + } - if (ai.flows[fd].rx_rb != NULL) { - shm_rbuff_set_acl(ai.flows[fd].rx_rb, ACL_FLOWDOWN); - shm_rbuff_close(ai.flows[fd].rx_rb); + if (proc.flows[fd].rx_rb != NULL) { + ssm_rbuff_set_acl(proc.flows[fd].rx_rb, ACL_FLOWDOWN); + ssm_rbuff_close(proc.flows[fd].rx_rb); } - if (ai.flows[fd].tx_rb != NULL) { - shm_rbuff_set_acl(ai.flows[fd].tx_rb, ACL_FLOWDOWN); - shm_rbuff_close(ai.flows[fd].tx_rb); + if (proc.flows[fd].tx_rb != NULL) { + ssm_rbuff_set_acl(proc.flows[fd].tx_rb, ACL_FLOWDOWN); + ssm_rbuff_close(proc.flows[fd].tx_rb); } - if (ai.flows[fd].set != NULL) { - shm_flow_set_notify(ai.flows[fd].set, - ai.flows[fd].flow_id, + if (proc.flows[fd].set != NULL) { + ssm_flow_set_notify(proc.flows[fd].set, + proc.flows[fd].info.id, FLOW_DEALLOC); - shm_flow_set_close(ai.flows[fd].set); + ssm_flow_set_close(proc.flows[fd].set); } - if (ai.flows[fd].ctx != NULL) - crypt_fini(ai.flows[fd].ctx); + crypt_destroy_ctx(proc.flows[fd].crypt); + + list_del(&proc.flows[fd].next); flow_clear(fd); } -static int flow_init(int flow_id, - pid_t pid, - qosspec_t qs, - uint8_t * s) +static void flow_fini(int fd) { - int fd; - int err = -ENOMEM; + pthread_rwlock_wrlock(&proc.lock); - pthread_rwlock_wrlock(&ai.lock); + __flow_fini(fd); - fd = bmp_allocate(ai.fds); - if (!bmp_is_id_valid(ai.fds, fd)) { + pthread_rwlock_unlock(&proc.lock); +} + +#define IS_ENCRYPTED(crypt) ((crypt)->nid != NID_undef) +#define IS_ORDERED(flow) (flow.qs.in_order != 0) +static int flow_init(struct flow_info * info, + struct crypt_sk * sk) +{ + struct timespec now; + struct flow * flow; + int fd; + int err = -ENOMEM; + + clock_gettime(PTHREAD_COND_CLOCK, &now); + + pthread_rwlock_wrlock(&proc.lock); + + fd = bmp_allocate(proc.fds); + if (!bmp_is_id_valid(proc.fds, fd)) { err = -EBADF; goto fail_fds; } - ai.flows[fd].rx_rb = shm_rbuff_open(ai.pid, flow_id); - if (ai.flows[fd].rx_rb == NULL) + flow = &proc.flows[fd]; + + flow->info = *info; + + flow->rx_rb = ssm_rbuff_open(info->n_pid, info->id); + if (flow->rx_rb == NULL) goto fail_rx_rb; - ai.flows[fd].tx_rb = shm_rbuff_open(pid, flow_id); - if (ai.flows[fd].tx_rb == NULL) + flow->tx_rb = ssm_rbuff_open(info->n_1_pid, info->id); + if (flow->tx_rb == NULL) goto fail_tx_rb; - ai.flows[fd].set = shm_flow_set_open(pid); - if (ai.flows[fd].set == NULL) + flow->set = ssm_flow_set_open(info->n_1_pid); + if (flow->set == NULL) goto fail_set; - ai.flows[fd].flow_id = flow_id; - ai.flows[fd].oflags = FLOWFDEFAULT; - ai.flows[fd].pid = pid; - ai.flows[fd].part_idx = NO_PART; - ai.flows[fd].qs = qs; + flow->oflags = FLOWFDEFAULT; + flow->part_idx = NO_PART; + flow->snd_act = now; + flow->rcv_act = now; + flow->crypt = NULL; + flow->headsz = 0; + flow->tailsz = 0; + + if (IS_ENCRYPTED(sk)) { + /* Set to lower value in tests, should we make configurable? */ + sk->rot_bit = KEY_ROTATION_BIT; + flow->crypt = crypt_create_ctx(sk); + if (flow->crypt == NULL) + goto fail_crypt; + flow->headsz = crypt_get_ivsz(flow->crypt); + flow->tailsz = crypt_get_tagsz(flow->crypt); + } + + assert(flow->frcti == NULL); + + if (IS_ORDERED(flow->info)) { + flow->frcti = frcti_create(fd, DELT_A, DELT_R, info->mpl); + if (flow->frcti == NULL) + goto fail_frcti; - if (qs.cypher_s > 0) { - assert(s != NULL); - if (crypt_init(&ai.flows[fd].ctx) < 0) - goto fail_ctx; + if (ssm_flow_set_add(proc.fqset, 0, info->id)) + goto fail_flow_set_add; - memcpy(ai.flows[fd].key, s, SYMMKEYSZ); + ++proc.n_frcti; + if (proc.n_frcti == 1 && + pthread_create(&proc.tx, NULL, flow_tx, NULL) < 0) + goto fail_tx_thread; } - ai.ports[flow_id].fd = fd; + list_add_tail(&flow->next, &proc.flow_list); + + proc.id_to_fd[info->id].fd = fd; - port_set_state(&ai.ports[flow_id], PORT_ID_ASSIGNED); + flow_set_state(&proc.id_to_fd[info->id], FLOW_ALLOCATED); - pthread_rwlock_unlock(&ai.lock); + pthread_rwlock_unlock(&proc.lock); return fd; - fail_ctx: - shm_flow_set_close(ai.flows[fd].set); + fail_tx_thread: + ssm_flow_set_del(proc.fqset, 0, info->id); + fail_flow_set_add: + frcti_destroy(flow->frcti); + fail_frcti: + crypt_destroy_ctx(flow->crypt); + fail_crypt: + ssm_flow_set_close(flow->set); fail_set: - shm_rbuff_close(ai.flows[fd].tx_rb); + ssm_rbuff_close(flow->tx_rb); fail_tx_rb: - shm_rbuff_close(ai.flows[fd].rx_rb); + ssm_rbuff_close(flow->rx_rb); fail_rx_rb: - bmp_release(ai.fds, fd); + bmp_release(proc.fds, fd); fail_fds: - pthread_rwlock_unlock(&ai.lock); + pthread_rwlock_unlock(&proc.lock); return err; } @@ -362,169 +618,220 @@ static void init(int argc, char ** argv, char ** envp) { - const char * prog = argv[0]; - int i; + struct proc_info info; + char * prog = argv[0]; + int i; #ifdef PROC_FLOW_STATS - char procstr[32]; + char procstr[32]; #endif (void) argc; (void) envp; - assert(ai.prog == NULL); - if (check_python(argv[0])) prog = argv[1]; - ai.pid = getpid(); + prog = path_strip(prog); + if (prog == NULL) { + fprintf(stderr, "FATAL: Could not determine program name.\n"); + goto fail_prog; + } + + memset(&info, 0, sizeof(info)); + info.pid = getpid(); + strncpy(info.prog, prog, PROG_NAME_SIZE); + + if (proc_announce(&info)) { + fprintf(stderr, "FATAL: Could not announce to IRMd.\n"); + goto fail_prog; + } + #ifdef HAVE_LIBGCRYPT if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) { - if (!gcry_check_version(GCRYPT_VERSION)) - goto fail_fds; + if (!gcry_check_version(GCRYPT_VERSION)) { + fprintf(stderr, "FATAL: Could not get gcry version.\n"); + goto fail_prog; + } gcry_control(GCRYCTL_DISABLE_SECMEM, 0); gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); } #endif - ai.fds = bmp_create(PROG_MAX_FLOWS - PROG_RES_FDS, PROG_RES_FDS); - if (ai.fds == NULL) + proc.fds = bmp_create(PROG_MAX_FLOWS - PROG_RES_FDS, PROG_RES_FDS); + if (proc.fds == NULL) { + fprintf(stderr, "FATAL: Could not create fd bitmap.\n"); goto fail_fds; + } - ai.fqueues = bmp_create(PROG_MAX_FQUEUES, 0); - if (ai.fqueues == NULL) + proc.fqueues = bmp_create(PROG_MAX_FQUEUES, 0); + if (proc.fqueues == NULL) { + fprintf(stderr, "FATAL: Could not create fqueue bitmap.\n"); goto fail_fqueues; + } + + if (is_ouroboros_member_uid(getuid())) + proc.pool = ssm_pool_open(0); + else + proc.pool = ssm_pool_open(getuid()); - ai.rdrb = shm_rdrbuff_open(); - if (ai.rdrb == NULL) + if (proc.pool == NULL) { + fprintf(stderr, "FATAL: Could not open packet buffer.\n"); goto fail_rdrb; + } - ai.flows = malloc(sizeof(*ai.flows) * PROG_MAX_FLOWS); - if (ai.flows == NULL) + proc.flows = malloc(sizeof(*proc.flows) * PROG_MAX_FLOWS); + if (proc.flows == NULL) { + fprintf(stderr, "FATAL: Could not malloc flows.\n"); goto fail_flows; + } for (i = 0; i < PROG_MAX_FLOWS; ++i) flow_clear(i); - ai.ports = malloc(sizeof(*ai.ports) * SYS_MAX_FLOWS); - if (ai.ports == NULL) - goto fail_ports; + proc.id_to_fd = malloc(sizeof(*proc.id_to_fd) * SYS_MAX_FLOWS); + if (proc.id_to_fd == NULL) { + fprintf(stderr, "FATAL: Could not malloc id_to_fd.\n"); + goto fail_id_to_fd; + } - if (prog != NULL) { - ai.prog = strdup(path_strip((char *) prog)); - if (ai.prog == NULL) - goto fail_prog; + for (i = 0; i < SYS_MAX_FLOWS; ++i) + proc.id_to_fd[i].state = FLOW_INIT; - if (proc_announce((char *) ai.prog)) - goto fail_announce; + if (pthread_mutex_init(&proc.mtx, NULL)) { + fprintf(stderr, "FATAL: Could not init mutex.\n"); + goto fail_mtx; } - for (i = 0; i < SYS_MAX_FLOWS; ++i) { - ai.ports[i].state = PORT_INIT; - if (pthread_mutex_init(&ai.ports[i].state_lock, NULL)) { - int j; - for (j = 0; j < i; ++j) - pthread_mutex_destroy(&ai.ports[j].state_lock); - goto fail_announce; - } - if (pthread_cond_init(&ai.ports[i].state_cond, NULL)) { - int j; - for (j = 0; j < i; ++j) - pthread_cond_destroy(&ai.ports[j].state_cond); - goto fail_state_cond; - } + if (pthread_cond_init(&proc.cond, NULL) < 0) { + fprintf(stderr, "FATAL: Could not init condvar.\n"); + goto fail_cond; } - if (pthread_rwlock_init(&ai.lock, NULL)) - goto fail_lock; + if (pthread_rwlock_init(&proc.lock, NULL) < 0) { + fprintf(stderr, "FATAL: Could not initialize flow lock.\n"); + goto fail_flow_lock; + } - ai.fqset = shm_flow_set_open(getpid()); - if (ai.fqset == NULL) + proc.fqset = ssm_flow_set_open(getpid()); + if (proc.fqset == NULL) { + fprintf(stderr, "FATAL: Could not open flow set.\n"); goto fail_fqset; + } + + proc.frct_set = fset_create(); + if (proc.frct_set == NULL || proc.frct_set->idx != 0) { + fprintf(stderr, "FATAL: Could not create FRCT set.\n"); + goto fail_frct_set; + } - if (timerwheel_init() < 0) + if (timerwheel_init() < 0) { + fprintf(stderr, "FATAL: Could not initialize timerwheel.\n"); goto fail_timerwheel; + } + + if (crypt_secure_malloc_init(PROC_SECMEM_MAX) < 0) { + fprintf(stderr, "FATAL: Could not init secure malloc.\n"); + goto fail_timerwheel; + } #if defined PROC_FLOW_STATS if (strstr(argv[0], "ipcpd") == NULL) { sprintf(procstr, "proc.%d", getpid()); - /* Don't bail on fail, it just won't show metrics */ - rib_init(procstr); + if (rib_init(procstr) < 0) { + fprintf(stderr, "FATAL: Could not initialize RIB.\n"); + goto fail_rib_init; + } } #endif + if (pthread_create(&proc.rx, NULL, flow_rx, NULL) < 0) { + fprintf(stderr, "FATAL: Could not start monitor thread.\n"); + goto fail_monitor; + } + + list_head_init(&proc.flow_list); + return; + fail_monitor: +#if defined PROC_FLOW_STATS + rib_fini(); + fail_rib_init: +#endif + timerwheel_fini(); fail_timerwheel: - shm_flow_set_close(ai.fqset); + fset_destroy(proc.frct_set); + fail_frct_set: + ssm_flow_set_close(proc.fqset); fail_fqset: - pthread_rwlock_destroy(&ai.lock); - fail_lock: - for (i = 0; i < SYS_MAX_FLOWS; ++i) - pthread_cond_destroy(&ai.ports[i].state_cond); - fail_state_cond: - for (i = 0; i < SYS_MAX_FLOWS; ++i) - pthread_mutex_destroy(&ai.ports[i].state_lock); - fail_announce: - free(ai.prog); - fail_prog: - free(ai.ports); - fail_ports: - free(ai.flows); + pthread_rwlock_destroy(&proc.lock); + fail_flow_lock: + pthread_cond_destroy(&proc.cond); + fail_cond: + pthread_mutex_destroy(&proc.mtx); + fail_mtx: + free(proc.id_to_fd); + fail_id_to_fd: + free(proc.flows); fail_flows: - shm_rdrbuff_close(ai.rdrb); + ssm_pool_close(proc.pool); fail_rdrb: - bmp_destroy(ai.fqueues); + bmp_destroy(proc.fqueues); fail_fqueues: - bmp_destroy(ai.fds); + bmp_destroy(proc.fds); fail_fds: - fprintf(stderr, "FATAL: ouroboros-dev init failed. " - "Make sure an IRMd is running.\n\n"); - memset(&ai, 0, sizeof(ai)); + memset(&proc, 0, sizeof(proc)); + fail_prog: exit(EXIT_FAILURE); } static void fini(void) { - int i = 0; -#ifdef PROC_FLOW_STATS - rib_fini(); -#endif + int i; - if (ai.fds == NULL) + if (proc.fds == NULL) return; - if (ai.prog != NULL) - free(ai.prog); + pthread_cancel(proc.rx); + pthread_join(proc.rx, NULL); - pthread_rwlock_wrlock(&ai.lock); + pthread_rwlock_wrlock(&proc.lock); for (i = 0; i < PROG_MAX_FLOWS; ++i) { - if (ai.flows[i].flow_id != -1) { + struct flow * flow = &proc.flows[i]; + if (flow->info.id != -1) { ssize_t idx; - shm_rbuff_set_acl(ai.flows[i].rx_rb, ACL_FLOWDOWN); - while ((idx = shm_rbuff_read(ai.flows[i].rx_rb)) >= 0) - shm_rdrbuff_remove(ai.rdrb, idx); - flow_fini(i); + ssm_rbuff_set_acl(flow->rx_rb, ACL_FLOWDOWN); + while ((idx = ssm_rbuff_read(flow->rx_rb)) >= 0) + ssm_pool_remove(proc.pool, idx); + __flow_fini(i); } } - shm_flow_set_close(ai.fqset); + pthread_cond_destroy(&proc.cond); + pthread_mutex_destroy(&proc.mtx); - for (i = 0; i < SYS_MAX_FLOWS; ++i) { - pthread_mutex_destroy(&ai.ports[i].state_lock); - pthread_cond_destroy(&ai.ports[i].state_cond); - } + pthread_rwlock_unlock(&proc.lock); +#ifdef PROC_FLOW_STATS + rib_fini(); +#endif timerwheel_fini(); - shm_rdrbuff_close(ai.rdrb); + fset_destroy(proc.frct_set); + + ssm_flow_set_close(proc.fqset); + + pthread_rwlock_destroy(&proc.lock); - free(ai.flows); - free(ai.ports); + free(proc.flows); + free(proc.id_to_fd); - bmp_destroy(ai.fds); - bmp_destroy(ai.fqueues); + ssm_pool_close(proc.pool); - pthread_rwlock_unlock(&ai.lock); + bmp_destroy(proc.fds); + bmp_destroy(proc.fqueues); - pthread_rwlock_destroy(&ai.lock); + proc_exit(); + + memset(&proc, 0, sizeof(proc)); } #if defined(__MACH__) && defined(__APPLE__) @@ -541,310 +848,244 @@ __attribute__((section(FINI_SECTION))) __typeof__(fini) * __fini = fini; int flow_accept(qosspec_t * qs, const struct timespec * timeo) { - irm_msg_t msg = IRM_MSG__INIT; - irm_msg_t * recv_msg; - int fd; - void * pkp; /* public key pair */ - uint8_t s[SYMMKEYSZ]; /* secret key for flow */ - uint8_t buf[MSGBUFSZ]; - int err = -EIRMD; - ssize_t key_len; - - memset(s, 0, SYMMKEYSZ); - - msg.code = IRM_MSG_CODE__IRM_FLOW_ACCEPT; - msg.has_pid = true; - msg.pid = ai.pid; - - if (timeo != NULL) { - msg.has_timeo_sec = true; - msg.has_timeo_nsec = true; - msg.timeo_sec = timeo->tv_sec; - msg.timeo_nsec = timeo->tv_nsec; - } - - key_len = crypt_dh_pkp_create(&pkp, buf); - if (key_len < 0) { - err = -ECRYPT; - goto fail_crypt_pkp; - } - if (key_len > 0) { - msg.has_pk = true; - msg.pk.data = buf; - msg.pk.len = (uint32_t) key_len; - } - - pthread_cleanup_push(crypt_dh_pkp_destroy, pkp); - - recv_msg = send_recv_irm_msg(&msg); - - pthread_cleanup_pop(false); - - if (recv_msg == NULL) - goto fail_recv; - - if (!recv_msg->has_result) - goto fail_result; - - if (recv_msg->result != 0) { - err = recv_msg->result; - goto fail_result; - } - - if (!recv_msg->has_pid || !recv_msg->has_flow_id || - recv_msg->qosspec == NULL) - goto fail_result; + struct flow_info flow; + struct crypt_sk crypt; + uint8_t buf[SOCK_BUF_SIZE]; + buffer_t msg = {SOCK_BUF_SIZE, buf}; + uint8_t key[SYMMKEYSZ]; + int fd; + int err; - if (recv_msg->pk.len != 0 && - crypt_dh_derive(pkp, recv_msg->pk.data, - recv_msg->pk.len, s) < 0) { - err = -ECRYPT; - goto fail_result; - } +#ifdef QOS_DISABLE_CRC + if (qs != NULL) + qs->ber = 1; +#endif + memset(&flow, 0, sizeof(flow)); - crypt_dh_pkp_destroy(pkp); + flow.n_pid = getpid(); + flow.qs = qs == NULL ? qos_raw : *qs; - fd = flow_init(recv_msg->flow_id, recv_msg->pid, - msg_to_spec(recv_msg->qosspec), s); + if (flow_accept__irm_req_ser(&msg, &flow, timeo)) + return -ENOMEM; - irm_msg__free_unpacked(recv_msg, NULL); + err = send_recv_msg(&msg); + if (err < 0) + return err; - if (fd < 0) - return fd; + crypt.key = key; - pthread_rwlock_wrlock(&ai.lock); + err = flow__irm_result_des(&msg, &flow, &crypt); + if (err < 0) + return err; - assert(ai.flows[fd].frcti == NULL); + fd = flow_init(&flow, &crypt); - if (ai.flows[fd].qs.in_order != 0) { - ai.flows[fd].frcti = frcti_create(fd); - if (ai.flows[fd].frcti == NULL) { - pthread_rwlock_unlock(&ai.lock); - flow_dealloc(fd); - return -ENOMEM; - } - } + crypt_secure_clear(key, SYMMKEYSZ); if (qs != NULL) - *qs = ai.flows[fd].qs; - - pthread_rwlock_unlock(&ai.lock); + *qs = flow.qs; return fd; - - fail_result: - irm_msg__free_unpacked(recv_msg, NULL); - fail_recv: - crypt_dh_pkp_destroy(pkp); - fail_crypt_pkp: - return err; } -static int __flow_alloc(const char * dst, - qosspec_t * qs, - const struct timespec * timeo, - bool join) +int flow_alloc(const char * dst, + qosspec_t * qs, + const struct timespec * timeo) { - irm_msg_t msg = IRM_MSG__INIT; - qosspec_msg_t qs_msg = QOSSPEC_MSG__INIT; - irm_msg_t * recv_msg; - int fd; - void * pkp = NULL; /* public key pair */ - uint8_t s[SYMMKEYSZ]; /* secret key for flow */ - uint8_t buf[MSGBUFSZ]; - int err = -EIRMD; - - memset(s, 0, SYMMKEYSZ); + struct flow_info flow; + struct crypt_sk crypt; + uint8_t buf[SOCK_BUF_SIZE]; + buffer_t msg = {SOCK_BUF_SIZE, buf}; + uint8_t key[SYMMKEYSZ]; + int fd; + int err; #ifdef QOS_DISABLE_CRC if (qs != NULL) qs->ber = 1; #endif - msg.code = join ? IRM_MSG_CODE__IRM_FLOW_JOIN - : IRM_MSG_CODE__IRM_FLOW_ALLOC; - msg.dst = (char *) dst; - msg.has_pid = true; - msg.pid = ai.pid; - qs_msg = spec_to_msg(qs); - msg.qosspec = &qs_msg; - if (timeo != NULL) { - msg.has_timeo_sec = true; - msg.has_timeo_nsec = true; - msg.timeo_sec = timeo->tv_sec; - msg.timeo_nsec = timeo->tv_nsec; - } + memset(&flow, 0, sizeof(flow)); - if (!join && qs != NULL && qs->cypher_s != 0) { - ssize_t key_len; + flow.n_pid = getpid(); + flow.qs = qs == NULL ? qos_raw : *qs; - key_len = crypt_dh_pkp_create(&pkp, buf); - if (key_len < 0) { - err = -ECRYPT; - goto fail_crypt_pkp; - } + if (flow_alloc__irm_req_ser(&msg, &flow, dst, timeo)) + return -ENOMEM; - msg.has_pk = true; - msg.pk.data = buf; - msg.pk.len = (uint32_t) key_len; + err = send_recv_msg(&msg); + if (err < 0) { + printf("send_recv_msg error %d\n", err); + return err; } - recv_msg = send_recv_irm_msg(&msg); - if (recv_msg == NULL) - goto fail_send; + crypt.key = key; - if (!recv_msg->has_result) - goto fail_result; + err = flow__irm_result_des(&msg, &flow, &crypt); + if (err < 0) + return err; - if (recv_msg->result != 0) { - err = recv_msg->result; - goto fail_result; - } + fd = flow_init(&flow, &crypt); - if (!recv_msg->has_pid || !recv_msg->has_flow_id) - goto fail_result; + crypt_secure_clear(key, SYMMKEYSZ); - if (!join && qs != NULL && qs->cypher_s != 0) { - if (!recv_msg->has_pk || recv_msg->pk.len == 0) { - err = -ECRYPT; - goto fail_result; - } - - if (crypt_dh_derive(pkp, recv_msg->pk.data, - recv_msg->pk.len, s) < 0) { - err = -ECRYPT; - goto fail_result; - } - - crypt_dh_pkp_destroy(pkp); - } - - fd = flow_init(recv_msg->flow_id, recv_msg->pid, - qs == NULL ? qos_raw : *qs, s); + if (qs != NULL) + *qs = flow.qs; - irm_msg__free_unpacked(recv_msg, NULL); + return fd; +} - if (fd < 0) - return fd; +int flow_join(const char * dst, + const struct timespec * timeo) +{ + struct flow_info flow; + struct crypt_sk crypt; + uint8_t buf[SOCK_BUF_SIZE]; + buffer_t msg = {SOCK_BUF_SIZE, buf}; + uint8_t key[SYMMKEYSZ]; + int fd; + int err; - pthread_rwlock_wrlock(&ai.lock); + memset(&flow, 0, sizeof(flow)); - assert(ai.flows[fd].frcti == NULL); + flow.n_pid = getpid(); + flow.qs = qos_np1; - if (ai.flows[fd].qs.in_order != 0) { - ai.flows[fd].frcti = frcti_create(fd); - if (ai.flows[fd].frcti == NULL) { - pthread_rwlock_unlock(&ai.lock); - flow_dealloc(fd); - return -ENOMEM; - } - } + if (flow_join__irm_req_ser(&msg, &flow, dst, timeo)) + return -ENOMEM; - pthread_rwlock_unlock(&ai.lock); + err = send_recv_msg(&msg); + if (err < 0) + return err; - return fd; + crypt.key = key; - fail_result: - irm_msg__free_unpacked(recv_msg, NULL); - fail_send: - crypt_dh_pkp_destroy(pkp); - fail_crypt_pkp: - return err; -} + err = flow__irm_result_des(&msg, &flow, &crypt); + if (err < 0) + return err; -int flow_alloc(const char * dst, - qosspec_t * qs, - const struct timespec * timeo) -{ - return __flow_alloc(dst, qs, timeo, false); -} + fd = flow_init(&flow, &crypt); -int flow_join(const char * dst, - qosspec_t * qs, - const struct timespec * timeo) -{ - if (qs != NULL && qs->cypher_s != 0) - return -ECRYPT; + crypt_secure_clear(key, SYMMKEYSZ); - return __flow_alloc(dst, qs, timeo, true); + return fd; } +#define PKT_BUF_LEN 2048 int flow_dealloc(int fd) { - irm_msg_t msg = IRM_MSG__INIT; - irm_msg_t * recv_msg; - struct flow * f; - time_t timeo; + struct flow_info info; + uint8_t pkt[PKT_BUF_LEN]; + uint8_t buf[SOCK_BUF_SIZE]; + buffer_t msg = {SOCK_BUF_SIZE, buf}; + struct timespec tic = TIMESPEC_INIT_NS(TICTIME); + struct timespec timeo = TIMESPEC_INIT_S(0); + struct flow * flow; + int err; if (fd < 0 || fd >= SYS_MAX_FLOWS ) return -EINVAL; - msg.code = IRM_MSG_CODE__IRM_FLOW_DEALLOC; - msg.has_flow_id = true; - msg.has_pid = true; - msg.pid = ai.pid; - msg.has_timeo_sec = true; - msg.has_timeo_nsec = true; - msg.timeo_nsec = 0; + memset(&info, 0, sizeof(flow)); - f = &ai.flows[fd]; + flow = &proc.flows[fd]; - pthread_rwlock_rdlock(&ai.lock); + pthread_rwlock_rdlock(&proc.lock); - if (f->flow_id < 0) { - pthread_rwlock_unlock(&ai.lock); + if (flow->info.id < 0) { + pthread_rwlock_unlock(&proc.lock); return -ENOTALLOC; } - msg.flow_id = f->flow_id; + flow->oflags = FLOWFDEFAULT | FLOWFRNOPART; - timeo = frcti_dealloc(f->frcti); - while (timeo < 0) { /* keep the flow active for rtx */ - ssize_t ret; - uint8_t buf[128]; - struct timespec tic = {0, TICTIME}; + flow->rcv_timesout = true; + flow->rcv_timeo = tic; - f->oflags = FLOWFDEFAULT | FLOWFRNOPART; + pthread_rwlock_unlock(&proc.lock); - f->rcv_timesout = true; - f->rcv_timeo = tic; + flow_read(fd, buf, SOCK_BUF_SIZE); - pthread_rwlock_unlock(&ai.lock); + pthread_rwlock_rdlock(&proc.lock); - ret = flow_read(fd, buf, 128); + timeo.tv_sec = frcti_dealloc(flow->frcti); + while (timeo.tv_sec < 0) { /* keep the flow active for rtx */ + ssize_t ret; + + pthread_rwlock_unlock(&proc.lock); + + ret = flow_read(fd, pkt, PKT_BUF_LEN); - pthread_rwlock_rdlock(&ai.lock); + pthread_rwlock_rdlock(&proc.lock); - timeo = frcti_dealloc(f->frcti); + timeo.tv_sec = frcti_dealloc(flow->frcti); - if (ret == -EFLOWDOWN && timeo < 0) - timeo = -timeo; + if (ret == -EFLOWDOWN && timeo.tv_sec < 0) + timeo.tv_sec = -timeo.tv_sec; } - msg.timeo_sec = timeo; + pthread_cleanup_push(__cleanup_rwlock_unlock, &proc.lock); - shm_rbuff_fini(ai.flows[fd].tx_rb); + ssm_rbuff_fini(flow->tx_rb); - pthread_rwlock_unlock(&ai.lock); + pthread_cleanup_pop(true); - recv_msg = send_recv_irm_msg(&msg); - if (recv_msg == NULL) - return -EIRMD; + info.id = flow->info.id; + info.n_pid = getpid(); - if (!recv_msg->has_result) { - irm_msg__free_unpacked(recv_msg, NULL); - return -EIRMD; - } + if (flow_dealloc__irm_req_ser(&msg, &info, &timeo) < 0) + return -ENOMEM; - irm_msg__free_unpacked(recv_msg, NULL); + err = send_recv_msg(&msg); + if (err < 0) + return err; - pthread_rwlock_wrlock(&ai.lock); + err = irm__irm_result_des(&msg); flow_fini(fd); - pthread_rwlock_unlock(&ai.lock); + return err; +} - return 0; +int ipcp_flow_dealloc(int fd) +{ + struct flow_info info; + uint8_t buf[SOCK_BUF_SIZE]; + buffer_t msg = {SOCK_BUF_SIZE, buf}; + struct flow * flow; + int err; + + if (fd < 0 || fd >= SYS_MAX_FLOWS ) + return -EINVAL; + + flow = &proc.flows[fd]; + + memset(&info, 0, sizeof(flow)); + + pthread_rwlock_rdlock(&proc.lock); + + if (flow->info.id < 0) { + pthread_rwlock_unlock(&proc.lock); + return -ENOTALLOC; + } + + info.id = flow->info.id; + info.n_1_pid = flow->info.n_1_pid; + + pthread_rwlock_unlock(&proc.lock); + + if (ipcp_flow_dealloc__irm_req_ser(&msg, &info) < 0) + return -ENOMEM; + + err = send_recv_msg(&msg); + if (err < 0) + return err; + + err = irm__irm_result_des(&msg); + + flow_fini(fd); + + return err; } int fccntl(int fd, @@ -853,6 +1094,7 @@ int fccntl(int fd, { uint32_t * fflags; uint16_t * cflags; + uint16_t csflags; va_list l; struct timespec * timeo; qosspec_t * qs; @@ -864,14 +1106,14 @@ int fccntl(int fd, if (fd < 0 || fd >= SYS_MAX_FLOWS) return -EBADF; - flow = &ai.flows[fd]; + flow = &proc.flows[fd]; va_start(l, cmd); - pthread_rwlock_wrlock(&ai.lock); + pthread_rwlock_wrlock(&proc.lock); - if (flow->flow_id < 0) { - pthread_rwlock_unlock(&ai.lock); + if (flow->info.id < 0) { + pthread_rwlock_unlock(&proc.lock); va_end(l); return -ENOTALLOC; } @@ -909,26 +1151,26 @@ int fccntl(int fd, goto einval; if (!flow->rcv_timesout) goto eperm; - *timeo = flow->snd_timeo; + *timeo = flow->rcv_timeo; break; case FLOWGQOSSPEC: qs = va_arg(l, qosspec_t *); if (qs == NULL) goto einval; - *qs = flow->qs; + *qs = flow->info.qs; break; case FLOWGRXQLEN: qlen = va_arg(l, size_t *); - *qlen = shm_rbuff_queued(flow->rx_rb); + *qlen = ssm_rbuff_queued(flow->rx_rb); break; case FLOWGTXQLEN: qlen = va_arg(l, size_t *); - *qlen = shm_rbuff_queued(flow->tx_rb); + *qlen = ssm_rbuff_queued(flow->tx_rb); break; case FLOWSFLAGS: flow->oflags = va_arg(l, uint32_t); - rx_acl = shm_rbuff_get_acl(flow->rx_rb); - tx_acl = shm_rbuff_get_acl(flow->rx_rb); + rx_acl = ssm_rbuff_get_acl(flow->rx_rb); + tx_acl = ssm_rbuff_get_acl(flow->rx_rb); /* * Making our own flow write only means making the * the other side of the flow read only. @@ -941,19 +1183,19 @@ int fccntl(int fd, if (flow->oflags & FLOWFDOWN) { rx_acl |= ACL_FLOWDOWN; tx_acl |= ACL_FLOWDOWN; - shm_flow_set_notify(flow->set, - flow->flow_id, + ssm_flow_set_notify(flow->set, + flow->info.id, FLOW_DOWN); } else { rx_acl &= ~ACL_FLOWDOWN; tx_acl &= ~ACL_FLOWDOWN; - shm_flow_set_notify(flow->set, - flow->flow_id, + ssm_flow_set_notify(flow->set, + flow->info.id, FLOW_UP); } - shm_rbuff_set_acl(flow->rx_rb, rx_acl); - shm_rbuff_set_acl(flow->tx_rb, tx_acl); + ssm_rbuff_set_acl(flow->rx_rb, rx_acl); + ssm_rbuff_set_acl(flow->tx_rb, tx_acl); break; case FLOWGFLAGS: @@ -963,15 +1205,13 @@ int fccntl(int fd, *fflags = flow->oflags; break; case FRCTSFLAGS: - cflags = va_arg(l, uint16_t *); - if (cflags == NULL) - goto einval; + csflags = (uint16_t) va_arg(l, uint32_t); if (flow->frcti == NULL) goto eperm; - frcti_setflags(flow->frcti, *cflags); + frcti_setflags(flow->frcti, csflags); break; case FRCTGFLAGS: - cflags = (uint16_t *) va_arg(l, int *); + cflags = (uint16_t *) va_arg(l, uint32_t *); if (cflags == NULL) goto einval; if (flow->frcti == NULL) @@ -979,51 +1219,108 @@ int fccntl(int fd, *cflags = frcti_getflags(flow->frcti); break; default: - pthread_rwlock_unlock(&ai.lock); + pthread_rwlock_unlock(&proc.lock); va_end(l); return -ENOTSUP; }; - pthread_rwlock_unlock(&ai.lock); + pthread_rwlock_unlock(&proc.lock); va_end(l); return 0; einval: - pthread_rwlock_unlock(&ai.lock); + pthread_rwlock_unlock(&proc.lock); va_end(l); return -EINVAL; eperm: - pthread_rwlock_unlock(&ai.lock); + pthread_rwlock_unlock(&proc.lock); va_end(l); return -EPERM; } -static int chk_crc(struct shm_du_buff * sdb) +static int chk_crc(struct ssm_pk_buff * spb) { uint32_t crc; - uint8_t * head = shm_du_buff_head(sdb); - uint8_t * tail = shm_du_buff_tail_release(sdb, CRCLEN); + uint8_t * head = ssm_pk_buff_head(spb); + uint8_t * tail = ssm_pk_buff_tail_release(spb, CRCLEN); mem_hash(HASH_CRC32, &crc, head, tail - head); return !(crc == *((uint32_t *) tail)); } -static int add_crc(struct shm_du_buff * sdb) +static int add_crc(struct ssm_pk_buff * spb) { - uint8_t * head = shm_du_buff_head(sdb); - uint8_t * tail = shm_du_buff_tail_alloc(sdb, CRCLEN); + uint8_t * head; + uint8_t * tail; + + tail = ssm_pk_buff_tail_alloc(spb, CRCLEN); if (tail == NULL) - return -1; + return -ENOMEM; + head = ssm_pk_buff_head(spb); mem_hash(HASH_CRC32, tail, head, tail - head); return 0; } +static int flow_tx_spb(struct flow * flow, + struct ssm_pk_buff * spb, + bool block, + struct timespec * abstime) +{ + struct timespec now; + ssize_t idx; + int ret; + + clock_gettime(PTHREAD_COND_CLOCK, &now); + + pthread_rwlock_wrlock(&proc.lock); + + flow->snd_act = now; + + pthread_rwlock_unlock(&proc.lock); + + idx = ssm_pk_buff_get_idx(spb); + + pthread_rwlock_rdlock(&proc.lock); + + if (ssm_pk_buff_len(spb) > 0) { + if (frcti_snd(flow->frcti, spb) < 0) + goto enomem; + + if (spb_encrypt(flow, spb) < 0) + goto enomem; + + if (flow->info.qs.ber == 0 && add_crc(spb) != 0) + goto enomem; + } + + pthread_cleanup_push(__cleanup_rwlock_unlock, &proc.lock); + + if (!block) + ret = ssm_rbuff_write(flow->tx_rb, idx); + else + ret = ssm_rbuff_write_b(flow->tx_rb, idx, abstime); + + if (ret < 0) + ssm_pool_remove(proc.pool, idx); + else + ssm_flow_set_notify(flow->set, flow->info.id, FLOW_PKT); + + pthread_cleanup_pop(true); + + return 0; + +enomem: + pthread_rwlock_unlock(&proc.lock); + ssm_pool_remove(proc.pool, idx); + return -ENOMEM; +} + ssize_t flow_write(int fd, const void * buf, size_t count) @@ -1034,38 +1331,34 @@ ssize_t flow_write(int fd, int flags; struct timespec abs; struct timespec * abstime = NULL; - struct timespec tic = {0, TICTIME}; - struct timespec tictime; - struct shm_du_buff * sdb; + struct ssm_pk_buff * spb; uint8_t * ptr; - if (buf == NULL) - return 0; + if (buf == NULL && count != 0) + return -EINVAL; - if (fd < 0 || fd > PROG_MAX_FLOWS) + if (fd < 0 || fd >= PROG_MAX_FLOWS) return -EBADF; - flow = &ai.flows[fd]; + flow = &proc.flows[fd]; clock_gettime(PTHREAD_COND_CLOCK, &abs); - pthread_rwlock_rdlock(&ai.lock); + pthread_rwlock_wrlock(&proc.lock); - if (flow->flow_id < 0) { - pthread_rwlock_unlock(&ai.lock); + if (flow->info.id < 0) { + pthread_rwlock_unlock(&proc.lock); return -ENOTALLOC; } - ts_add(&tic, &abs, &tictime); - - if (ai.flows[fd].snd_timesout) { + if (flow->snd_timesout) { ts_add(&abs, &flow->snd_timeo, &abs); abstime = &abs; } flags = flow->oflags; - pthread_rwlock_unlock(&ai.lock); + pthread_rwlock_unlock(&proc.lock); if ((flags & FLOWFACCMODE) == FLOWFRDONLY) return -EPERM; @@ -1073,56 +1366,69 @@ ssize_t flow_write(int fd, if (flags & FLOWFWNOBLOCK) { if (!frcti_is_window_open(flow->frcti)) return -EAGAIN; - idx = shm_rdrbuff_alloc(ai.rdrb, count, &ptr, &sdb); + idx = ssm_pool_alloc(proc.pool, count, &ptr, &spb); } else { - while ((ret = frcti_window_wait(flow->frcti, &tictime)) < 0) { - if (ret != -ETIMEDOUT) - return ret; - - if (abstime != NULL && ts_diff_ns(&tictime, &abs) <= 0) - return -ETIMEDOUT; - - frcti_tick(flow->frcti); - - ts_add(&tictime, &tic, &tictime); - } - idx = shm_rdrbuff_alloc_b(ai.rdrb, count, &ptr, &sdb, abstime); + ret = frcti_window_wait(flow->frcti, abstime); + if (ret < 0) + return ret; + idx = ssm_pool_alloc_b(proc.pool, count, &ptr, &spb, abstime); } if (idx < 0) return idx; - memcpy(ptr, buf, count); + if (count > 0) + memcpy(ptr, buf, count); + + ret = flow_tx_spb(flow, spb, !(flags & FLOWFWNOBLOCK), abstime); + + return ret < 0 ? (ssize_t) ret : (ssize_t) count; +} - pthread_rwlock_rdlock(&ai.lock); +static bool invalid_pkt(struct flow * flow, + struct ssm_pk_buff * spb) +{ + if (spb == NULL || ssm_pk_buff_len(spb) == 0) + return true; - if (frcti_snd(flow->frcti, sdb) < 0) - goto enomem; + if (flow->info.qs.ber == 0 && chk_crc(spb) != 0) + return true; - if (flow->qs.cypher_s > 0 && crypt_encrypt(flow, sdb) < 0) - goto enomem; + if (spb_decrypt(flow, spb) < 0) + return true; - if (flow->qs.ber == 0 && add_crc(sdb) != 0) - goto enomem; + return false; +} - if (flags & FLOWFWNOBLOCK) - ret = shm_rbuff_write(flow->tx_rb, idx); - else - ret = shm_rbuff_write_b(flow->tx_rb, idx, abstime); +static ssize_t flow_rx_spb(struct flow * flow, + struct ssm_pk_buff ** spb, + bool block, + struct timespec * abstime) +{ + ssize_t idx; + struct timespec now; - if (ret < 0) - shm_rdrbuff_remove(ai.rdrb, idx); - else - shm_flow_set_notify(flow->set, flow->flow_id, FLOW_PKT); + idx = block ? ssm_rbuff_read_b(flow->rx_rb, abstime) : + ssm_rbuff_read(flow->rx_rb); + if (idx < 0) + return idx; - pthread_rwlock_unlock(&ai.lock); + clock_gettime(PTHREAD_COND_CLOCK, &now); - return ret < 0 ? (ssize_t) ret : (ssize_t) count; + pthread_rwlock_wrlock(&proc.lock); - enomem: - pthread_rwlock_unlock(&ai.lock); - shm_rdrbuff_remove(ai.rdrb, idx); - return -ENOMEM; + flow->rcv_act = now; + + pthread_rwlock_unlock(&proc.lock); + + *spb = ssm_pool_get(proc.pool, idx); + + if (invalid_pkt(flow, *spb)) { + ssm_pool_remove(proc.pool, idx); + return -EAGAIN; + } + + return idx; } ssize_t flow_read(int fd, @@ -1132,120 +1438,100 @@ ssize_t flow_read(int fd, ssize_t idx; ssize_t n; uint8_t * packet; - struct shm_rbuff * rb; - struct shm_du_buff * sdb; + struct ssm_pk_buff * spb; struct timespec abs; - struct timespec tic = {0, TICTIME}; - struct timespec tictime; + struct timespec now; struct timespec * abstime = NULL; struct flow * flow; - bool noblock; + bool block; bool partrd; - if (fd < 0 || fd > PROG_MAX_FLOWS) + if (fd < 0 || fd >= PROG_MAX_FLOWS) return -EBADF; - flow = &ai.flows[fd]; + flow = &proc.flows[fd]; - clock_gettime(PTHREAD_COND_CLOCK, &abs); + clock_gettime(PTHREAD_COND_CLOCK, &now); - pthread_rwlock_rdlock(&ai.lock); + pthread_rwlock_rdlock(&proc.lock); + + if (flow->info.id < 0) { + pthread_rwlock_unlock(&proc.lock); + return -ENOTALLOC; + } if (flow->part_idx == DONE_PART) { - pthread_rwlock_unlock(&ai.lock); + pthread_rwlock_unlock(&proc.lock); flow->part_idx = NO_PART; return 0; } - if (flow->flow_id < 0) { - pthread_rwlock_unlock(&ai.lock); - return -ENOTALLOC; - } - - rb = flow->rx_rb; - noblock = flow->oflags & FLOWFRNOBLOCK; + block = !(flow->oflags & FLOWFRNOBLOCK); partrd = !(flow->oflags & FLOWFRNOPART); - ts_add(&tic, &abs, &tictime); - - if (ai.flows[fd].rcv_timesout) { - ts_add(&abs, &flow->rcv_timeo, &abs); + if (flow->rcv_timesout) { + ts_add(&now, &flow->rcv_timeo, &abs); abstime = &abs; } idx = flow->part_idx; - if (idx < 0) { while ((idx = frcti_queued_pdu(flow->frcti)) < 0) { - pthread_rwlock_unlock(&ai.lock); + pthread_rwlock_unlock(&proc.lock); - idx = noblock ? shm_rbuff_read(rb) : - shm_rbuff_read_b(rb, &tictime); + idx = flow_rx_spb(flow, &spb, block, abstime); if (idx < 0) { - frcti_tick(flow->frcti); - - if (idx != -ETIMEDOUT) + if (block && idx != -EAGAIN) + return idx; + if (!block) return idx; - if (abstime != NULL - && ts_diff_ns(&tictime, &abs) <= 0) - return -ETIMEDOUT; - - ts_add(&tictime, &tic, &tictime); - pthread_rwlock_rdlock(&ai.lock); - continue; - } - - sdb = shm_rdrbuff_get(ai.rdrb, idx); - if (flow->qs.ber == 0 && chk_crc(sdb) != 0) { - pthread_rwlock_rdlock(&ai.lock); - shm_rdrbuff_remove(ai.rdrb, idx); + pthread_rwlock_rdlock(&proc.lock); continue; } - pthread_rwlock_rdlock(&ai.lock); + pthread_rwlock_rdlock(&proc.lock); - if (flow->qs.cypher_s > 0 - && crypt_decrypt(flow, sdb) < 0) { - pthread_rwlock_unlock(&ai.lock); - shm_rdrbuff_remove(ai.rdrb, idx); - return -ENOMEM; - } - - frcti_rcv(flow->frcti, sdb); + frcti_rcv(flow->frcti, spb); } } - frcti_tick(flow->frcti); + spb = ssm_pool_get(proc.pool, idx); + + pthread_rwlock_unlock(&proc.lock); - pthread_rwlock_unlock(&ai.lock); + packet = ssm_pk_buff_head(spb); - n = shm_rdrbuff_read(&packet, ai.rdrb, idx); + n = ssm_pk_buff_len(spb); assert(n >= 0); if (n <= (ssize_t) count) { memcpy(buf, packet, n); - shm_rdrbuff_remove(ai.rdrb, idx); + ipcp_spb_release(spb); - pthread_rwlock_wrlock(&ai.lock); + pthread_rwlock_wrlock(&proc.lock); flow->part_idx = (partrd && n == (ssize_t) count) ? DONE_PART : NO_PART; - pthread_rwlock_unlock(&ai.lock); + flow->rcv_act = now; + + pthread_rwlock_unlock(&proc.lock); return n; } else { if (partrd) { memcpy(buf, packet, count); - sdb = shm_rdrbuff_get(ai.rdrb, idx); - shm_du_buff_head_release(sdb, n); - pthread_rwlock_wrlock(&ai.lock); + ssm_pk_buff_head_release(spb, n); + pthread_rwlock_wrlock(&proc.lock); flow->part_idx = idx; - pthread_rwlock_unlock(&ai.lock); + + flow->rcv_act = now; + + pthread_rwlock_unlock(&proc.lock); return count; } else { - shm_rdrbuff_remove(ai.rdrb, idx); + ipcp_spb_release(spb); return -EMSGSIZE; } } @@ -1253,26 +1539,31 @@ ssize_t flow_read(int fd, /* fqueue functions. */ -struct flow_set * fset_create() +struct flow_set * fset_create(void) { - struct flow_set * set = malloc(sizeof(*set)); + struct flow_set * set; + + set = malloc(sizeof(*set)); if (set == NULL) - return NULL; + goto fail_malloc; - assert(ai.fqueues); + assert(proc.fqueues); - pthread_rwlock_wrlock(&ai.lock); + pthread_rwlock_wrlock(&proc.lock); - set->idx = bmp_allocate(ai.fqueues); - if (!bmp_is_id_valid(ai.fqueues, set->idx)) { - pthread_rwlock_unlock(&ai.lock); - free(set); - return NULL; - } + set->idx = bmp_allocate(proc.fqueues); + if (!bmp_is_id_valid(proc.fqueues, set->idx)) + goto fail_bmp_alloc; - pthread_rwlock_unlock(&ai.lock); + pthread_rwlock_unlock(&proc.lock); return set; + + fail_bmp_alloc: + pthread_rwlock_unlock(&proc.lock); + free(set); + fail_malloc: + return NULL; } void fset_destroy(struct flow_set * set) @@ -1282,22 +1573,22 @@ void fset_destroy(struct flow_set * set) fset_zero(set); - pthread_rwlock_wrlock(&ai.lock); + pthread_rwlock_wrlock(&proc.lock); - bmp_release(ai.fqueues, set->idx); + bmp_release(proc.fqueues, set->idx); - pthread_rwlock_unlock(&ai.lock); + pthread_rwlock_unlock(&proc.lock); free(set); } -struct fqueue * fqueue_create() +struct fqueue * fqueue_create(void) { struct fqueue * fq = malloc(sizeof(*fq)); if (fq == NULL) return NULL; - memset(fq->fqueue, -1, (SHM_BUFFER_SIZE) * sizeof(*fq->fqueue)); + memset(fq->fqueue, -1, SSM_RBUFF_SIZE * sizeof(*fq->fqueue)); fq->fqsize = 0; fq->next = 0; @@ -1314,76 +1605,153 @@ void fset_zero(struct flow_set * set) if (set == NULL) return; - shm_flow_set_zero(ai.fqset, set->idx); + ssm_flow_set_zero(proc.fqset, set->idx); } int fset_add(struct flow_set * set, int fd) { - int ret; - size_t packets; - size_t i; + struct flow * flow; + int ret; - if (set == NULL || fd < 0 || fd > SYS_MAX_FLOWS) + if (set == NULL || fd < 0 || fd >= SYS_MAX_FLOWS) return -EINVAL; - pthread_rwlock_wrlock(&ai.lock); + flow = &proc.flows[fd]; - if (ai.flows[fd].flow_id < 0) { - pthread_rwlock_unlock(&ai.lock); - return -EINVAL; + pthread_rwlock_rdlock(&proc.lock); + + if (flow->info.id < 0) { + ret = -EINVAL; + goto fail; } - ret = shm_flow_set_add(ai.fqset, set->idx, ai.flows[fd].flow_id); + if (flow->frcti != NULL) + ssm_flow_set_del(proc.fqset, 0, flow->info.id); - packets = shm_rbuff_queued(ai.flows[fd].rx_rb); - for (i = 0; i < packets; i++) - shm_flow_set_notify(ai.fqset, ai.flows[fd].flow_id, FLOW_PKT); + ret = ssm_flow_set_add(proc.fqset, set->idx, flow->info.id); + if (ret < 0) + goto fail; - pthread_rwlock_unlock(&ai.lock); + if (ssm_rbuff_queued(flow->rx_rb)) + ssm_flow_set_notify(proc.fqset, flow->info.id, FLOW_PKT); + pthread_rwlock_unlock(&proc.lock); + + return ret; + + fail: + pthread_rwlock_unlock(&proc.lock); return ret; } void fset_del(struct flow_set * set, int fd) { - if (set == NULL || fd < 0 || fd > SYS_MAX_FLOWS) + struct flow * flow; + + if (set == NULL || fd < 0 || fd >= SYS_MAX_FLOWS) return; - pthread_rwlock_rdlock(&ai.lock); + flow = &proc.flows[fd]; + + pthread_rwlock_rdlock(&proc.lock); - if (ai.flows[fd].flow_id >= 0) - shm_flow_set_del(ai.fqset, set->idx, ai.flows[fd].flow_id); + if (flow->info.id >= 0) + ssm_flow_set_del(proc.fqset, set->idx, flow->info.id); - pthread_rwlock_unlock(&ai.lock); + if (flow->frcti != NULL) + ssm_flow_set_add(proc.fqset, 0, proc.flows[fd].info.id); + + pthread_rwlock_unlock(&proc.lock); } bool fset_has(const struct flow_set * set, int fd) { - bool ret = false; + struct flow * flow; + bool ret; - if (set == NULL || fd < 0 || fd > SYS_MAX_FLOWS) + if (set == NULL || fd < 0 || fd >= SYS_MAX_FLOWS) return false; - pthread_rwlock_rdlock(&ai.lock); + flow = &proc.flows[fd]; - if (ai.flows[fd].flow_id < 0) { - pthread_rwlock_unlock(&ai.lock); + pthread_rwlock_rdlock(&proc.lock); + + if (flow->info.id < 0) { + pthread_rwlock_unlock(&proc.lock); return false; } - ret = (shm_flow_set_has(ai.fqset, set->idx, ai.flows[fd].flow_id) == 1); + ret = (ssm_flow_set_has(proc.fqset, set->idx, flow->info.id) == 1); - pthread_rwlock_unlock(&ai.lock); + pthread_rwlock_unlock(&proc.lock); return ret; } +/* Filter fqueue events for non-data packets */ +static int fqueue_filter(struct fqueue * fq) +{ + struct ssm_pk_buff * spb; + int fd; + ssize_t idx; + struct frcti * frcti; + + while (fq->next < fq->fqsize) { + if (fq->fqueue[fq->next].event != FLOW_PKT) + return 1; + + pthread_rwlock_rdlock(&proc.lock); + + fd = proc.id_to_fd[fq->fqueue[fq->next].flow_id].fd; + if (fd < 0) { + ++fq->next; + pthread_rwlock_unlock(&proc.lock); + continue; + } + + frcti = proc.flows[fd].frcti; + if (frcti == NULL) { + pthread_rwlock_unlock(&proc.lock); + return 1; + } + + if (__frcti_pdu_ready(frcti) >= 0) { + pthread_rwlock_unlock(&proc.lock); + return 1; + } + + pthread_rwlock_unlock(&proc.lock); + + idx = flow_rx_spb(&proc.flows[fd], &spb, false, NULL); + if (idx < 0) + return 0; + + pthread_rwlock_rdlock(&proc.lock); + + spb = ssm_pool_get(proc.pool, idx); + + __frcti_rcv(frcti, spb); + + if (__frcti_pdu_ready(frcti) >= 0) { + pthread_rwlock_unlock(&proc.lock); + return 1; + } + + pthread_rwlock_unlock(&proc.lock); + + ++fq->next; + } + + return 0; +} + int fqueue_next(struct fqueue * fq) { - int fd; + int fd; + struct flowevent * e; if (fq == NULL) return -EINVAL; @@ -1391,18 +1759,18 @@ int fqueue_next(struct fqueue * fq) if (fq->fqsize == 0 || fq->next == fq->fqsize) return -EPERM; - pthread_rwlock_rdlock(&ai.lock); - - if (fq->next != 0 && frcti_filter(fq) == 0) { - pthread_rwlock_unlock(&ai.lock); + if (fq->next != 0 && fqueue_filter(fq) == 0) return -EPERM; - } - fd = ai.ports[fq->fqueue[fq->next]].fd; + pthread_rwlock_rdlock(&proc.lock); - fq->next += 2; + e = fq->fqueue + fq->next; - pthread_rwlock_unlock(&ai.lock); + fd = proc.id_to_fd[e->flow_id].fd; + + ++fq->next; + + pthread_rwlock_unlock(&proc.lock); return fd; } @@ -1415,7 +1783,7 @@ enum fqtype fqueue_type(struct fqueue * fq) if (fq->fqsize == 0 || fq->next == 0) return -EPERM; - return fq->fqueue[fq->next - 1]; + return fq->fqueue[(fq->next - 1)].event; } ssize_t fevent(struct flow_set * set, @@ -1423,8 +1791,6 @@ ssize_t fevent(struct flow_set * set, const struct timespec * timeo) { ssize_t ret = 0; - struct timespec tic = {0, TICTIME}; - struct timespec tictime; struct timespec abs; struct timespec * t = NULL; @@ -1436,46 +1802,44 @@ ssize_t fevent(struct flow_set * set, clock_gettime(PTHREAD_COND_CLOCK, &abs); - ts_add(&tic, &abs, &tictime); - t = &tictime; - - if (timeo != NULL) + if (timeo != NULL) { ts_add(&abs, timeo, &abs); + t = &abs; + } while (ret == 0) { - ret = shm_flow_set_wait(ai.fqset, set->idx, fq->fqueue, t); - if (ret == -ETIMEDOUT) { - if (timeo != NULL && ts_diff_ns(t, &abs) < 0) { - fq->fqsize = 0; - return -ETIMEDOUT; - } - ret = 0; - ts_add(t, &tic, t); - pthread_rwlock_rdlock(&ai.lock); - timerwheel_move(); - pthread_rwlock_unlock(&ai.lock); - continue; - } + ret = ssm_flow_set_wait(proc.fqset, set->idx, fq->fqueue, t); + if (ret == -ETIMEDOUT) + return -ETIMEDOUT; - fq->fqsize = ret << 1; + fq->fqsize = ret; fq->next = 0; - ret = frcti_filter(fq); + ret = fqueue_filter(fq); } - assert(ret); + assert(ret != 0); return 1; } /* ipcp-dev functions. */ -int np1_flow_alloc(pid_t n_pid, - int flow_id, - qosspec_t qs) +int np1_flow_alloc(pid_t n_pid, + int flow_id) { - qs.cypher_s = 0; /* No encryption ctx for np1 */ - return flow_init(flow_id, n_pid, qs, NULL); + struct flow_info flow; + struct crypt_sk crypt = { .nid = NID_undef, .key = NULL }; + + memset(&flow, 0, sizeof(flow)); + + flow.id = flow_id; + flow.n_pid = getpid(); + flow.qs = qos_np1; + flow.mpl = 0; + flow.n_1_pid = n_pid; /* This "flow" is upside-down! */ + + return flow_init(&flow, &crypt); } int np1_flow_dealloc(int flow_id, @@ -1491,279 +1855,332 @@ int np1_flow_dealloc(int flow_id, sleep(timeo); - pthread_rwlock_rdlock(&ai.lock); + pthread_rwlock_rdlock(&proc.lock); - fd = ai.ports[flow_id].fd; + fd = proc.id_to_fd[flow_id].fd; - pthread_rwlock_unlock(&ai.lock); + pthread_rwlock_unlock(&proc.lock); return fd; } -int np1_flow_resp(int flow_id) +int np1_flow_resp(int flow_id, + int resp) { int fd; - if (port_wait_assign(flow_id) != PORT_ID_ASSIGNED) + if (resp == 0 && flow_wait_assign(flow_id) != FLOW_ALLOCATED) return -1; - pthread_rwlock_rdlock(&ai.lock); + pthread_rwlock_rdlock(&proc.lock); - fd = ai.ports[flow_id].fd; + fd = proc.id_to_fd[flow_id].fd; - pthread_rwlock_unlock(&ai.lock); + pthread_rwlock_unlock(&proc.lock); return fd; } -int ipcp_create_r(int result) +int ipcp_create_r(const struct ipcp_info * info) { - irm_msg_t msg = IRM_MSG__INIT; - irm_msg_t * recv_msg; - int ret; - - msg.code = IRM_MSG_CODE__IPCP_CREATE_R; - msg.has_pid = true; - msg.pid = getpid(); - msg.has_result = true; - msg.result = result; - - recv_msg = send_recv_irm_msg(&msg); - if (recv_msg == NULL) - return -EIRMD; - - if (!recv_msg->has_result) { - irm_msg__free_unpacked(recv_msg, NULL); - return -1; - } + uint8_t buf[SOCK_BUF_SIZE]; + buffer_t msg = {SOCK_BUF_SIZE, buf}; + int err; - ret = recv_msg->result; - irm_msg__free_unpacked(recv_msg, NULL); + if (ipcp_create_r__irm_req_ser(&msg,info) < 0) + return -ENOMEM; - return ret; + err = send_recv_msg(&msg); + if (err < 0) + return err; + + return irm__irm_result_des(&msg); } -int ipcp_flow_req_arr(const uint8_t * dst, - size_t len, - qosspec_t qs, - const void * data, - size_t dlen) +int ipcp_flow_req_arr(const buffer_t * dst, + qosspec_t qs, + time_t mpl, + const buffer_t * data) { - irm_msg_t msg = IRM_MSG__INIT; - irm_msg_t * recv_msg; - qosspec_msg_t qs_msg; - int fd; - - assert(dst != NULL); - - msg.code = IRM_MSG_CODE__IPCP_FLOW_REQ_ARR; - msg.has_pid = true; - msg.pid = getpid(); - msg.has_hash = true; - msg.hash.len = len; - msg.hash.data = (uint8_t *) dst; - qs_msg = spec_to_msg(&qs); - msg.qosspec = &qs_msg; - msg.has_pk = true; - msg.pk.data = (uint8_t *) data; - msg.pk.len = dlen; - - recv_msg = send_recv_irm_msg(&msg); - if (recv_msg == NULL) - return -EIRMD; - - if (!recv_msg->has_flow_id || !recv_msg->has_pid) { - irm_msg__free_unpacked(recv_msg, NULL); - return -1; - } + struct flow_info flow; + uint8_t buf[SOCK_BUF_SIZE]; + buffer_t msg = {SOCK_BUF_SIZE, buf}; + struct crypt_sk crypt; + uint8_t key[SYMMKEYSZ]; + int err; - if (recv_msg->has_result && recv_msg->result) { - irm_msg__free_unpacked(recv_msg, NULL); - return -1; - } + memset(&flow, 0, sizeof(flow)); - qs.cypher_s = 0; /* No encryption ctx for np1 */ - fd = flow_init(recv_msg->flow_id, recv_msg->pid, qs, NULL); + assert(dst != NULL && dst->len != 0 && dst->data != NULL); - irm_msg__free_unpacked(recv_msg, NULL); + flow.n_1_pid = getpid(); + flow.qs = qs; + flow.mpl = mpl; - return fd; -} + if (ipcp_flow_req_arr__irm_req_ser(&msg, dst, &flow, data) < 0) + return -ENOMEM; -int ipcp_flow_alloc_reply(int fd, - int response, - const void * data, - size_t len) -{ - irm_msg_t msg = IRM_MSG__INIT; - irm_msg_t * recv_msg; - int ret; + err = send_recv_msg(&msg); + if (err < 0) + return err; - assert(fd >= 0 && fd < SYS_MAX_FLOWS); + crypt.key = key; - msg.code = IRM_MSG_CODE__IPCP_FLOW_ALLOC_REPLY; - msg.has_flow_id = true; - msg.has_pk = true; - msg.pk.data = (uint8_t *) data; - msg.pk.len = (uint32_t) len; + err = flow__irm_result_des(&msg, &flow, &crypt); + if (err < 0) + return err; - pthread_rwlock_rdlock(&ai.lock); + assert(crypt.nid == NID_undef); /* np1 flows are not encrypted */ - msg.flow_id = ai.flows[fd].flow_id; + /* inverted for np1_flow */ + flow.n_1_pid = flow.n_pid; + flow.n_pid = getpid(); + flow.mpl = 0; + flow.qs = qos_np1; - pthread_rwlock_unlock(&ai.lock); + crypt.nid = NID_undef; - msg.has_response = true; - msg.response = response; + return flow_init(&flow, &crypt); +} - recv_msg = send_recv_irm_msg(&msg); - if (recv_msg == NULL) - return -EIRMD; +int ipcp_flow_alloc_reply(int fd, + int response, + time_t mpl, + const buffer_t * data) +{ + struct flow_info flow; + uint8_t buf[SOCK_BUF_SIZE]; + buffer_t msg = {SOCK_BUF_SIZE, buf}; + int err; - if (!recv_msg->has_result) { - irm_msg__free_unpacked(recv_msg, NULL); - return -1; - } + assert(fd >= 0 && fd < SYS_MAX_FLOWS); - ret = recv_msg->result; + pthread_rwlock_rdlock(&proc.lock); - irm_msg__free_unpacked(recv_msg, NULL); + flow.id = proc.flows[fd].info.id; - return ret; + pthread_rwlock_unlock(&proc.lock); + + flow.mpl = mpl; + + if (ipcp_flow_alloc_reply__irm_msg_ser(&msg, &flow, response, data) < 0) + return -ENOMEM; + + err = send_recv_msg(&msg); + if (err < 0) + return err; + + return irm__irm_result_des(&msg); } int ipcp_flow_read(int fd, - struct shm_du_buff ** sdb) + struct ssm_pk_buff ** spb) { - struct flow * flow; - struct shm_rbuff * rb; - ssize_t idx = -1; + struct flow * flow; + ssize_t idx = -1; assert(fd >= 0 && fd < SYS_MAX_FLOWS); - assert(sdb); - - flow = &ai.flows[fd]; + assert(spb); - pthread_rwlock_rdlock(&ai.lock); + flow = &proc.flows[fd]; - assert(flow->flow_id >= 0); + pthread_rwlock_rdlock(&proc.lock); - rb = flow->rx_rb; + assert(flow->info.id >= 0); - while ((idx = frcti_queued_pdu(flow->frcti)) < 0) { - pthread_rwlock_unlock(&ai.lock); + while (frcti_queued_pdu(flow->frcti) < 0) { + pthread_rwlock_unlock(&proc.lock); - idx = shm_rbuff_read(rb); + idx = flow_rx_spb(flow, spb, false, NULL); if (idx < 0) return idx; - pthread_rwlock_rdlock(&ai.lock); - - *sdb = shm_rdrbuff_get(ai.rdrb, idx); - if (flow->qs.ber == 0 && chk_crc(*sdb) != 0) - continue; + pthread_rwlock_rdlock(&proc.lock); - frcti_rcv(flow->frcti, *sdb); + frcti_rcv(flow->frcti, *spb); } - frcti_tick(flow->frcti); - - pthread_rwlock_unlock(&ai.lock); - - *sdb = shm_rdrbuff_get(ai.rdrb, idx); + pthread_rwlock_unlock(&proc.lock); return 0; } int ipcp_flow_write(int fd, - struct shm_du_buff * sdb) + struct ssm_pk_buff * spb) { - struct flow * flow; - int ret; - ssize_t idx; + struct flow * flow; + int ret; assert(fd >= 0 && fd < SYS_MAX_FLOWS); - assert(sdb); + assert(spb); - flow = &ai.flows[fd]; + flow = &proc.flows[fd]; - pthread_rwlock_rdlock(&ai.lock); + pthread_rwlock_wrlock(&proc.lock); - if (flow->flow_id < 0) { - pthread_rwlock_unlock(&ai.lock); + if (flow->info.id < 0) { + pthread_rwlock_unlock(&proc.lock); return -ENOTALLOC; } if ((flow->oflags & FLOWFACCMODE) == FLOWFRDONLY) { - pthread_rwlock_unlock(&ai.lock); + pthread_rwlock_unlock(&proc.lock); return -EPERM; } - assert(flow->tx_rb); + pthread_rwlock_unlock(&proc.lock); - idx = shm_du_buff_get_idx(sdb); + ret = flow_tx_spb(flow, spb, true, NULL); - if (frcti_snd(flow->frcti, sdb) < 0) { - pthread_rwlock_unlock(&ai.lock); + return ret; +} + +static int pool_copy_spb(struct ssm_pool * src_pool, + ssize_t src_idx, + struct ssm_pool * dst_pool, + struct ssm_pk_buff ** dst_spb) +{ + struct ssm_pk_buff * src; + uint8_t * ptr; + size_t len; + + src = ssm_pool_get(src_pool, src_idx); + len = ssm_pk_buff_len(src); + + if (ssm_pool_alloc(dst_pool, len, &ptr, dst_spb) < 0) { + ssm_pool_remove(src_pool, src_idx); return -ENOMEM; } - if (flow->qs.ber == 0 && add_crc(sdb) != 0) { - pthread_rwlock_unlock(&ai.lock); - shm_rdrbuff_remove(ai.rdrb, idx); - return -ENOMEM; + memcpy(ptr, ssm_pk_buff_head(src), len); + ssm_pool_remove(src_pool, src_idx); + + return 0; +} + +int np1_flow_read(int fd, + struct ssm_pk_buff ** spb, + struct ssm_pool * pool) +{ + struct flow * flow; + ssize_t idx = -1; + + assert(fd >= 0 && fd < SYS_MAX_FLOWS); + assert(spb); + + flow = &proc.flows[fd]; + + assert(flow->info.id >= 0); + + pthread_rwlock_rdlock(&proc.lock); + + idx = ssm_rbuff_read(flow->rx_rb); + if (idx < 0) { + pthread_rwlock_unlock(&proc.lock); + return idx; } - ret = shm_rbuff_write_b(flow->tx_rb, idx, NULL); - if (ret == 0) - shm_flow_set_notify(flow->set, flow->flow_id, FLOW_PKT); - else - shm_rdrbuff_remove(ai.rdrb, idx); + pthread_rwlock_unlock(&proc.lock); + + if (pool == NULL) { + *spb = ssm_pool_get(proc.pool, idx); + } else { + /* Cross-pool copy: PUP -> GSPP */ + if (pool_copy_spb(pool, idx, proc.pool, spb) < 0) + return -ENOMEM; + } + + return 0; +} + +int np1_flow_write(int fd, + struct ssm_pk_buff * spb, + struct ssm_pool * pool) +{ + struct flow * flow; + struct ssm_pk_buff * dst; + int ret; + ssize_t idx; + + assert(fd >= 0 && fd < SYS_MAX_FLOWS); + assert(spb); - pthread_rwlock_unlock(&ai.lock); + flow = &proc.flows[fd]; - assert(ret <= 0); + pthread_rwlock_rdlock(&proc.lock); + + if (flow->info.id < 0) { + pthread_rwlock_unlock(&proc.lock); + return -ENOTALLOC; + } + + if ((flow->oflags & FLOWFACCMODE) == FLOWFRDONLY) { + pthread_rwlock_unlock(&proc.lock); + return -EPERM; + } + + pthread_rwlock_unlock(&proc.lock); + + idx = ssm_pk_buff_get_idx(spb); + + if (pool == NULL) { + ret = ssm_rbuff_write_b(flow->tx_rb, idx, NULL); + if (ret < 0) + ssm_pool_remove(proc.pool, idx); + else + ssm_flow_set_notify(flow->set, flow->info.id, FLOW_PKT); + } else { + /* Cross-pool copy: GSPP -> PUP */ + if (pool_copy_spb(proc.pool, idx, pool, &dst) < 0) + return -ENOMEM; + idx = ssm_pk_buff_get_idx(dst); + ret = ssm_rbuff_write_b(flow->tx_rb, idx, NULL); + if (ret < 0) + ssm_pool_remove(pool, idx); + else + ssm_flow_set_notify(flow->set, flow->info.id, FLOW_PKT); + } return ret; } -int ipcp_sdb_reserve(struct shm_du_buff ** sdb, +int ipcp_spb_reserve(struct ssm_pk_buff ** spb, size_t len) { - return shm_rdrbuff_alloc_b(ai.rdrb, len, NULL, sdb, NULL) < 0 ? -1 : 0; + return ssm_pool_alloc_b(proc.pool, len, NULL, spb, NULL) < 0 ? -1 : 0; } -void ipcp_sdb_release(struct shm_du_buff * sdb) +void ipcp_spb_release(struct ssm_pk_buff * spb) { - shm_rdrbuff_remove(ai.rdrb, shm_du_buff_get_idx(sdb)); + ssm_pool_remove(proc.pool, ssm_pk_buff_get_idx(spb)); } int ipcp_flow_fini(int fd) { - struct shm_rbuff * rx_rb; + struct ssm_rbuff * rx_rb; assert(fd >= 0 && fd < SYS_MAX_FLOWS); - pthread_rwlock_rdlock(&ai.lock); + pthread_rwlock_rdlock(&proc.lock); - if (ai.flows[fd].flow_id < 0) { - pthread_rwlock_unlock(&ai.lock); + if (proc.flows[fd].info.id < 0) { + pthread_rwlock_unlock(&proc.lock); return -1; } - shm_rbuff_set_acl(ai.flows[fd].rx_rb, ACL_FLOWDOWN); - shm_rbuff_set_acl(ai.flows[fd].tx_rb, ACL_FLOWDOWN); + ssm_rbuff_set_acl(proc.flows[fd].rx_rb, ACL_FLOWDOWN); + ssm_rbuff_set_acl(proc.flows[fd].tx_rb, ACL_FLOWDOWN); - shm_flow_set_notify(ai.flows[fd].set, - ai.flows[fd].flow_id, + ssm_flow_set_notify(proc.flows[fd].set, + proc.flows[fd].info.id, FLOW_DEALLOC); - rx_rb = ai.flows[fd].rx_rb; + rx_rb = proc.flows[fd].rx_rb; - pthread_rwlock_unlock(&ai.lock); + pthread_rwlock_unlock(&proc.lock); if (rx_rb != NULL) - shm_rbuff_fini(rx_rb); + ssm_rbuff_fini(rx_rb); return 0; } @@ -1774,13 +2191,13 @@ int ipcp_flow_get_qoscube(int fd, assert(fd >= 0 && fd < SYS_MAX_FLOWS); assert(cube); - pthread_rwlock_rdlock(&ai.lock); + pthread_rwlock_rdlock(&proc.lock); - assert(ai.flows[fd].flow_id >= 0); + assert(proc.flows[fd].info.id >= 0); - *cube = qos_spec_to_cube(ai.flows[fd].qs); + *cube = qos_spec_to_cube(proc.flows[fd].info.qs); - pthread_rwlock_unlock(&ai.lock); + pthread_rwlock_unlock(&proc.lock); return 0; } @@ -1789,55 +2206,76 @@ size_t ipcp_flow_queued(int fd) { size_t q; - pthread_rwlock_rdlock(&ai.lock); + pthread_rwlock_rdlock(&proc.lock); - assert(ai.flows[fd].flow_id >= 0); + assert(proc.flows[fd].info.id >= 0); - q = shm_rbuff_queued(ai.flows[fd].tx_rb); + q = ssm_rbuff_queued(proc.flows[fd].tx_rb); - pthread_rwlock_unlock(&ai.lock); + pthread_rwlock_unlock(&proc.lock); return q; } -ssize_t local_flow_read(int fd) +int local_flow_transfer(int src_fd, + int dst_fd, + struct ssm_pool * src_pool, + struct ssm_pool * dst_pool) { - ssize_t ret; - - assert(fd >= 0); + struct flow * src_flow; + struct flow * dst_flow; + struct ssm_pk_buff * dst_spb; + struct ssm_pool * sp; + struct ssm_pool * dp; + ssize_t idx; + int ret; - pthread_rwlock_rdlock(&ai.lock); + assert(src_fd >= 0); + assert(dst_fd >= 0); - ret = shm_rbuff_read(ai.flows[fd].rx_rb); + src_flow = &proc.flows[src_fd]; + dst_flow = &proc.flows[dst_fd]; - pthread_rwlock_unlock(&ai.lock); + sp = src_pool == NULL ? proc.pool : src_pool; + dp = dst_pool == NULL ? proc.pool : dst_pool; - return ret; -} + pthread_rwlock_rdlock(&proc.lock); -int local_flow_write(int fd, - size_t idx) -{ - struct flow * flow; - int ret; + idx = ssm_rbuff_read(src_flow->rx_rb); + if (idx < 0) { + pthread_rwlock_unlock(&proc.lock); + return idx; + } - assert(fd >= 0); + if (dst_flow->info.id < 0) { + pthread_rwlock_unlock(&proc.lock); + ssm_pool_remove(sp, idx); + return -ENOTALLOC; + } - flow = &ai.flows[fd]; + pthread_rwlock_unlock(&proc.lock); - pthread_rwlock_rdlock(&ai.lock); + if (sp == dp) { + /* Same pool: zero-copy */ + ret = ssm_rbuff_write_b(dst_flow->tx_rb, idx, NULL); + if (ret < 0) + ssm_pool_remove(sp, idx); + else + ssm_flow_set_notify(dst_flow->set, + dst_flow->info.id, FLOW_PKT); + } else { + /* Different pools: single copy */ + if (pool_copy_spb(sp, idx, dp, &dst_spb) < 0) + return -ENOMEM; - if (flow->flow_id < 0) { - pthread_rwlock_unlock(&ai.lock); - return -ENOTALLOC; + idx = ssm_pk_buff_get_idx(dst_spb); + ret = ssm_rbuff_write_b(dst_flow->tx_rb, idx, NULL); + if (ret < 0) + ssm_pool_remove(dp, idx); + else + ssm_flow_set_notify(dst_flow->set, + dst_flow->info.id, FLOW_PKT); } - ret = shm_rbuff_write_b(flow->tx_rb, idx, NULL); - if (ret == 0) - shm_flow_set_notify(flow->set, flow->flow_id, FLOW_PKT); - else - shm_rdrbuff_remove(ai.rdrb, idx); - - pthread_rwlock_unlock(&ai.lock); return ret; } diff --git a/src/lib/frct.c b/src/lib/frct.c index a1e792af..39a82966 100644 --- a/src/lib/frct.c +++ b/src/lib/frct.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Flow and Retransmission Control * @@ -20,6 +20,8 @@ * Foundation, Inc., http://www.fsf.org/about/contact/. */ +#include <ouroboros/endian.h> + #define DELT_RDV (100 * MILLION) /* ns */ #define MAX_RDV (1 * BILLION) /* ns */ @@ -52,10 +54,20 @@ struct frcti { uint32_t rttseq; struct timespec t_probe; /* Probe time */ bool probe; /* Probe active */ - +#ifdef PROC_FLOW_STATS + size_t n_rtx; /* Number of rxm packets */ + size_t n_prb; /* Number of rtt probes */ + size_t n_rtt; /* Number of estimates */ + size_t n_dup; /* Duplicates received */ + size_t n_dak; /* Delayed ACKs received */ + size_t n_rdv; /* Number of rdv packets */ + size_t n_out; /* Packets out of window */ + size_t n_rqo; /* Packets out of rqueue */ +#endif struct frct_cr snd_cr; struct frct_cr rcv_cr; + ssize_t rq[RQ_SIZE]; pthread_rwlock_t lock; @@ -106,11 +118,11 @@ static int frct_rib_read(const char * path, fd = atoi(path); - flow = &ai.flows[fd]; + flow = &proc.flows[fd]; clock_gettime(PTHREAD_COND_CLOCK, &now); - pthread_rwlock_rdlock(&ai.lock); + pthread_rwlock_rdlock(&proc.lock); frcti = flow->frcti; @@ -125,12 +137,20 @@ static int frct_rib_read(const char * path, "Retransmit timeout RTO (ns): %20ld\n" "Sender left window edge: %20u\n" "Sender right window edge: %20u\n" - "Sender inactive (ns): %20ld\n" + "Sender inactive (ns): %20lld\n" "Sender current sequence number: %20u\n" "Receiver left window edge: %20u\n" "Receiver right window edge: %20u\n" - "Receiver inactive (ns): %20ld\n" - "Receiver last ack: %20u\n", + "Receiver inactive (ns): %20lld\n" + "Receiver last ack: %20u\n" + "Number of pkt retransmissions: %20zu\n" + "Number of rtt probes: %20zu\n" + "Number of rtt estimates: %20zu\n" + "Number of duplicates received: %20zu\n" + "Number of delayed acks received: %20zu\n" + "Number of rendez-vous sent: %20zu\n" + "Number of packets out of window: %20zu\n" + "Number of packets out of rqueue: %20zu\n", frcti->mpl, frcti->a, frcti->r, @@ -139,16 +159,24 @@ static int frct_rib_read(const char * path, frcti->rto, frcti->snd_cr.lwe, frcti->snd_cr.rwe, - ts_diff_ns(&frcti->snd_cr.act, &now), + ts_diff_ns(&now, &frcti->snd_cr.act), frcti->snd_cr.seqno, frcti->rcv_cr.lwe, frcti->rcv_cr.rwe, - ts_diff_ns(&frcti->rcv_cr.act, &now), - frcti->rcv_cr.seqno); + ts_diff_ns(&now, &frcti->rcv_cr.act), + frcti->rcv_cr.seqno, + frcti->n_rtx, + frcti->n_prb, + frcti->n_rtt, + frcti->n_dup, + frcti->n_dak, + frcti->n_rdv, + frcti->n_out, + frcti->n_rqo); pthread_rwlock_unlock(&flow->frcti->lock); - pthread_rwlock_unlock(&ai.lock); + pthread_rwlock_unlock(&proc.lock); return strlen(buf); } @@ -156,10 +184,19 @@ static int frct_rib_read(const char * path, static int frct_rib_readdir(char *** buf) { *buf = malloc(sizeof(**buf)); + if (*buf == NULL) + goto fail_malloc; (*buf)[0] = strdup("frct"); + if ((*buf)[0] == NULL) + goto fail_strdup; return 1; + + fail_strdup: + free(*buf); + fail_malloc: + return -ENOMEM; } static int frct_rib_getattr(const char * path, @@ -168,7 +205,7 @@ static int frct_rib_getattr(const char * path, (void) path; (void) attr; - attr->size = 1024; + attr->size = 1189; attr->mtime = 0; return 0; @@ -200,21 +237,21 @@ static void __send_frct_pkt(int fd, uint32_t ackno, uint32_t rwe) { - struct shm_du_buff * sdb; + struct ssm_pk_buff * spb; struct frct_pci * pci; ssize_t idx; struct flow * f; /* Raw calls needed to bypass frcti. */ #ifdef RXM_BLOCKING - idx = shm_rdrbuff_alloc_b(ai.rdrb, sizeof(*pci), NULL, &sdb, NULL); + idx = ssm_pool_alloc_b(proc.pool, sizeof(*pci), NULL, &spb, NULL); #else - idx = shm_rdrbuff_alloc(ai.rdrb, sizeof(*pci), NULL, &sdb); + idx = ssm_pool_alloc(proc.pool, sizeof(*pci), NULL, &spb); #endif if (idx < 0) return; - pci = (struct frct_pci *) shm_du_buff_head(sdb); + pci = (struct frct_pci *) ssm_pk_buff_head(spb); memset(pci, 0, sizeof(*pci)); *((uint32_t *) pci) = hton32(rwe); @@ -222,17 +259,25 @@ static void __send_frct_pkt(int fd, pci->flags = flags; pci->ackno = hton32(ackno); - f = &ai.flows[fd]; + f = &proc.flows[fd]; + + if (spb_encrypt(f, spb) < 0) + goto fail; + #ifdef RXM_BLOCKING - if (shm_rbuff_write_b(f->tx_rb, idx, NULL)) { + if (ssm_rbuff_write_b(f->tx_rb, idx, NULL)) #else - if (shm_rbuff_write(f->tx_rb, idx)) { + if (ssm_rbuff_write(f->tx_rb, idx)) #endif - ipcp_sdb_release(sdb); - return; - } + goto fail; + + ssm_flow_set_notify(f->set, f->info.id, FLOW_PKT); + + return; - shm_flow_set_notify(f->set, f->flow_id, FLOW_PKT); + fail: + ipcp_spb_release(spb); + return; } static void send_frct_pkt(struct frcti * frcti) @@ -245,9 +290,11 @@ static void send_frct_pkt(struct frcti * frcti) assert(frcti); - pthread_rwlock_rdlock(&frcti->lock); + clock_gettime(PTHREAD_COND_CLOCK, &now); - if (frcti->rcv_cr.lwe == frcti->rcv_cr.seqno) { + pthread_rwlock_wrlock(&frcti->lock); + + if (!after(frcti->rcv_cr.lwe, frcti->rcv_cr.seqno)) { pthread_rwlock_unlock(&frcti->lock); return; } @@ -256,65 +303,46 @@ static void send_frct_pkt(struct frcti * frcti) ackno = frcti->rcv_cr.lwe; rwe = frcti->rcv_cr.rwe; - clock_gettime(PTHREAD_COND_CLOCK, &now); - - diff = ts_diff_ns(&frcti->rcv_cr.act, &now); - - pthread_rwlock_unlock(&frcti->lock); - - if (diff > frcti->a || diff < DELT_ACK) + diff = ts_diff_ns(&now, &frcti->rcv_cr.act); + if (diff > frcti->a) { + pthread_rwlock_unlock(&frcti->lock); return; + } - __send_frct_pkt(fd, FRCT_ACK | FRCT_FC, ackno, rwe); - - pthread_rwlock_wrlock(&frcti->lock); + diff = ts_diff_ns(&now, &frcti->snd_cr.act); + if (diff < TICTIME) { + pthread_rwlock_unlock(&frcti->lock); + return; + } - if (after(frcti->rcv_cr.lwe, frcti->rcv_cr.seqno)) - frcti->rcv_cr.seqno = frcti->rcv_cr.lwe; + frcti->rcv_cr.seqno = frcti->rcv_cr.lwe; pthread_rwlock_unlock(&frcti->lock); + __send_frct_pkt(fd, FRCT_ACK | FRCT_FC, ackno, rwe); } static void __send_rdv(int fd) { - struct shm_du_buff * sdb; - struct frct_pci * pci; - ssize_t idx; - struct flow * f; - - /* Raw calls needed to bypass frcti. */ - idx = shm_rdrbuff_alloc_b(ai.rdrb, sizeof(*pci), NULL, &sdb, NULL); - if (idx < 0) - return; - - pci = (struct frct_pci *) shm_du_buff_head(sdb); - memset(pci, 0, sizeof(*pci)); - - pci->flags = FRCT_RDVS; - - f = &ai.flows[fd]; - - if (shm_rbuff_write_b(f->tx_rb, idx, NULL)) { - ipcp_sdb_release(sdb); - return; - } - - shm_flow_set_notify(f->set, f->flow_id, FLOW_PKT); + __send_frct_pkt(fd, FRCT_RDVS, 0, 0); } -static struct frcti * frcti_create(int fd) +static struct frcti * frcti_create(int fd, + time_t a, + time_t r, + time_t mpl) { struct frcti * frcti; ssize_t idx; struct timespec now; - time_t mpl; - time_t a; - time_t r; pthread_condattr_t cattr; #ifdef PROC_FLOW_STATS char frctstr[FRCT_NAME_STRLEN + 1]; #endif + mpl *= MILLION; + a *= BILLION; + r *= BILLION; + frcti = malloc(sizeof(*frcti)); if (frcti == NULL) goto fail_malloc; @@ -347,9 +375,9 @@ static struct frcti * frcti_create(int fd) clock_gettime(PTHREAD_COND_CLOCK, &now); - frcti->mpl = mpl = DELT_MPL; - frcti->a = a = DELT_A; - frcti->r = r = DELT_R; + frcti->mpl = mpl; + frcti->a = a; + frcti->r = r; frcti->rdv = DELT_RDV; frcti->fd = fd; @@ -358,10 +386,19 @@ static struct frcti * frcti_create(int fd) frcti->probe = false; frcti->srtt = 0; /* Updated on first ACK */ - frcti->mdev = 10 * MILLION; /* Initial rxm will be after 20 ms */ - frcti->rto = 20 * MILLION; /* Initial rxm will be after 20 ms */ - - if (ai.flows[fd].qs.loss == 0) { + frcti->mdev = 10 * MILLION; /* Updated on first ACK */ + frcti->rto = BILLION; /* Initial rxm will be after 1 s */ +#ifdef PROC_FLOW_STATS + frcti->n_rtx = 0; + frcti->n_prb = 0; + frcti->n_rtt = 0; + frcti->n_dup = 0; + frcti->n_dak = 0; + frcti->n_rdv = 0; + frcti->n_out = 0; + frcti->n_rqo = 0; +#endif + if (proc.flows[fd].info.qs.loss == 0) { frcti->snd_cr.cflags |= FRCTFRTX | FRCTFLINGER; frcti->rcv_cr.cflags |= FRCTFRTX; } @@ -442,14 +479,11 @@ static void frcti_setflags(struct frcti * frcti, #define frcti_queued_pdu(frcti) \ (frcti == NULL ? idx : __frcti_queued_pdu(frcti)) -#define frcti_snd(frcti, sdb) \ - (frcti == NULL ? 0 : __frcti_snd(frcti, sdb)) - -#define frcti_rcv(frcti, sdb) \ - (frcti == NULL ? 0 : __frcti_rcv(frcti, sdb)) +#define frcti_snd(frcti, spb) \ + (frcti == NULL ? 0 : __frcti_snd(frcti, spb)) -#define frcti_tick(frcti) \ - (frcti == NULL ? 0 : __frcti_tick()) +#define frcti_rcv(frcti, spb) \ + (frcti == NULL ? 0 : __frcti_rcv(frcti, spb)) #define frcti_dealloc(frcti) \ (frcti == NULL ? 0 : __frcti_dealloc(frcti)) @@ -483,17 +517,21 @@ static bool __frcti_is_window_open(struct frcti * frcti) frcti->t_rdvs = now; } else { time_t diff; - diff = ts_diff_ns(&frcti->t_wnd, &now); + diff = ts_diff_ns(&now, &frcti->t_wnd); if (diff > MAX_RDV) { pthread_mutex_unlock(&frcti->mtx); pthread_rwlock_unlock(&frcti->lock); return false; } - diff = ts_diff_ns(&frcti->t_rdvs, &now); + diff = ts_diff_ns(&now, &frcti->t_rdvs); if (diff > frcti->rdv) { frcti->t_rdvs = now; __send_rdv(frcti->fd); +#ifdef PROC_FLOW_STATS + frcti->n_rdv++; +#endif + } } @@ -520,7 +558,6 @@ static int __frcti_window_wait(struct frcti * frcti, while (snd_cr->seqno == snd_cr->rwe && ret != -ETIMEDOUT) { struct timespec now; - pthread_rwlock_unlock(&frcti->lock); pthread_mutex_lock(&frcti->mtx); @@ -534,9 +571,7 @@ static int __frcti_window_wait(struct frcti * frcti, pthread_cleanup_push(__cleanup_mutex_unlock, &frcti->mtx); - ret = -pthread_cond_timedwait(&frcti->cond, - &frcti->mtx, - abstime); + ret = -__timedwait(&frcti->cond, &frcti->mtx, abstime); pthread_cleanup_pop(false); @@ -545,13 +580,13 @@ static int __frcti_window_wait(struct frcti * frcti, clock_gettime(PTHREAD_COND_CLOCK, &now); - diff = ts_diff_ns(&frcti->t_wnd, &now); + diff = ts_diff_ns(&now, &frcti->t_wnd); if (diff > MAX_RDV) { pthread_mutex_unlock(&frcti->mtx); return -ECONNRESET; /* write fails! */ } - diff = ts_diff_ns(&frcti->t_rdvs, &now); + diff = ts_diff_ns(&now, &frcti->t_rdvs); if (diff > frcti->rdv) { frcti->t_rdvs = now; __send_rdv(frcti->fd); @@ -648,7 +683,7 @@ static time_t __frcti_dealloc(struct frcti * frcti) } static int __frcti_snd(struct frcti * frcti, - struct shm_du_buff * sdb) + struct ssm_pk_buff * spb) { struct frct_pci * pci; struct timespec now; @@ -658,13 +693,14 @@ static int __frcti_snd(struct frcti * frcti, bool rtx; assert(frcti); + assert(ssm_pk_buff_len(spb) != 0); snd_cr = &frcti->snd_cr; rcv_cr = &frcti->rcv_cr; timerwheel_move(); - pci = (struct frct_pci *) shm_du_buff_head_alloc(sdb, FRCT_PCILEN); + pci = (struct frct_pci *) ssm_pk_buff_head_alloc(spb, FRCT_PCILEN); if (pci == NULL) return -ENOMEM; @@ -706,9 +742,11 @@ static int __frcti_snd(struct frcti * frcti, frcti->rttseq = snd_cr->seqno; frcti->t_probe = now; frcti->probe = true; +#ifdef PROC_FLOW_STATS + frcti->n_prb++; +#endif } - - if (now.tv_sec - rcv_cr->act.tv_sec <= frcti->a) { + if ((now.tv_sec - rcv_cr->act.tv_sec) * BILLION <= frcti->a) { pci->flags |= FRCT_ACK; pci->ackno = hton32(rcv_cr->lwe); rcv_cr->seqno = rcv_cr->lwe; @@ -721,7 +759,7 @@ static int __frcti_snd(struct frcti * frcti, pthread_rwlock_unlock(&frcti->lock); if (rtx) - timerwheel_rxm(frcti, seqno, sdb); + timerwheel_rxm(frcti, seqno, spb); return 0; } @@ -738,22 +776,24 @@ static void rtt_estimator(struct frcti * frcti, } else { time_t delta = mrtt - srtt; srtt += (delta >> 3); - rttvar += (ABS(delta) - rttvar) >> 2; + delta = (ABS(delta) - rttvar) >> 2; +#ifdef FRCT_LINUX_RTT_ESTIMATOR + if (delta < 0) + delta >>= 3; +#endif + rttvar += delta; } - - frcti->srtt = MAX(1000U, srtt); - frcti->mdev = MAX(100U, rttvar); - frcti->rto = MAX(RTO_MIN, frcti->srtt + (frcti->mdev << 1)); -} - -static void __frcti_tick(void) -{ - timerwheel_move(); +#ifdef PROC_FLOW_STATS + frcti->n_rtt++; +#endif + frcti->srtt = MAX(1000L, srtt); + frcti->mdev = MAX(100L, rttvar); + frcti->rto = MAX(RTO_MIN, frcti->srtt + (frcti->mdev << MDEV_MUL)); } /* Always queues the next application packet on the RQ. */ static void __frcti_rcv(struct frcti * frcti, - struct shm_du_buff * sdb) + struct ssm_pk_buff * spb) { ssize_t idx; size_t pos; @@ -773,9 +813,9 @@ static void __frcti_rcv(struct frcti * frcti, clock_gettime(PTHREAD_COND_CLOCK, &now); - pci = (struct frct_pci *) shm_du_buff_head_release(sdb, FRCT_PCILEN); + pci = (struct frct_pci *) ssm_pk_buff_head_release(spb, FRCT_PCILEN); - idx = shm_du_buff_get_idx(sdb); + idx = ssm_pk_buff_get_idx(spb); seqno = ntoh32(pci->seqno); pos = seqno & (RQ_SIZE - 1); @@ -785,11 +825,14 @@ static void __frcti_rcv(struct frcti * frcti, if (pci->flags & FRCT_DRF) { /* New run. */ rcv_cr->lwe = seqno; rcv_cr->rwe = seqno + RQ_SIZE; - } else { + rcv_cr->seqno = seqno; + } else if (pci->flags & FRCT_DATA) { goto drop_packet; } } + rcv_cr->act = now; + /* For now, just send an immediate window update. */ if (pci->flags & FRCT_RDVS) { fd = frcti->fd; @@ -798,7 +841,7 @@ static void __frcti_rcv(struct frcti * frcti, __send_frct_pkt(fd, FRCT_FC, 0, rwe); - shm_rdrbuff_remove(ai.rdrb, idx); + ssm_pool_remove(proc.pool, idx); return; } @@ -808,7 +851,11 @@ static void __frcti_rcv(struct frcti * frcti, frcti->snd_cr.lwe = ackno; if (frcti->probe && after(ackno, frcti->rttseq)) { - rtt_estimator(frcti, ts_diff_ns(&frcti->t_probe, &now)); +#ifdef PROC_FLOW_STATS + if (!(pci->flags & FRCT_DATA)) + frcti->n_dak++; +#endif + rtt_estimator(frcti, ts_diff_ns(&now, &frcti->t_probe)); frcti->probe = false; } } @@ -838,20 +885,33 @@ static void __frcti_rcv(struct frcti * frcti, if (before(seqno, rcv_cr->lwe)) { rcv_cr->seqno = seqno; /* Ensures we send a new ACK. */ +#ifdef PROC_FLOW_STATS + frcti->n_dup++; +#endif goto drop_packet; } if (rcv_cr->cflags & FRCTFRTX) { - if (!before(seqno, rcv_cr->rwe)) /* Out of window. */ + if (!before(seqno, rcv_cr->rwe)) { /* Out of window. */ +#ifdef PROC_FLOW_STATS + frcti->n_out++; +#endif goto drop_packet; + } - if (!before(seqno, rcv_cr->lwe + RQ_SIZE)) + if (!before(seqno, rcv_cr->lwe + RQ_SIZE)) { +#ifdef PROC_FLOW_STATS + frcti->n_rqo++; +#endif goto drop_packet; /* Out of rq. */ - - if (frcti->rq[pos] != -1) + } + if (frcti->rq[pos] != -1) { +#ifdef PROC_FLOW_STATS + frcti->n_dup++; +#endif goto drop_packet; /* Duplicate in rq. */ - + } fd = frcti->fd; } else { rcv_cr->lwe = seqno; @@ -859,72 +919,16 @@ static void __frcti_rcv(struct frcti * frcti, frcti->rq[pos] = idx; - rcv_cr->act = now; - pthread_rwlock_unlock(&frcti->lock); if (fd != -1) - timerwheel_ack(fd, frcti); + timerwheel_delayed_ack(fd, frcti); return; drop_packet: pthread_rwlock_unlock(&frcti->lock); - + ssm_pool_remove(proc.pool, idx); send_frct_pkt(frcti); - - shm_rdrbuff_remove(ai.rdrb, idx); return; } - -/* Filter fqueue events for non-data packets */ -int frcti_filter(struct fqueue * fq) -{ - struct shm_du_buff * sdb; - int fd; - ssize_t idx; - struct frcti * frcti; - struct shm_rbuff * rb; - - while (fq->next < fq->fqsize) { - if (fq->fqueue[fq->next + 1] != FLOW_PKT) - return 1; - - pthread_rwlock_rdlock(&ai.lock); - - fd = ai.ports[fq->fqueue[fq->next]].fd; - rb = ai.flows[fd].rx_rb; - frcti = ai.flows[fd].frcti; - - if (frcti == NULL) { - pthread_rwlock_unlock(&ai.lock); - return 1; - } - - if (__frcti_pdu_ready(frcti) >= 0) { - pthread_rwlock_unlock(&ai.lock); - return 1; - } - - idx = shm_rbuff_read(rb); - if (idx < 0) { - pthread_rwlock_unlock(&ai.lock); - return 0; - } - - sdb = shm_rdrbuff_get(ai.rdrb, idx); - - __frcti_rcv(frcti, sdb); - - if (__frcti_pdu_ready(frcti) >= 0) { - pthread_rwlock_unlock(&ai.lock); - return 1; - } - - pthread_rwlock_unlock(&ai.lock); - - fq->next += 2; - } - - return fq->next < fq->fqsize; -} diff --git a/src/lib/hash.c b/src/lib/hash.c index 4ce3f3b4..b465f894 100644 --- a/src/lib/hash.c +++ b/src/lib/hash.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Hashing * @@ -29,43 +29,50 @@ #include "config.h" +#include <ouroboros/endian.h> #include <ouroboros/hash.h> -#ifndef HAVE_LIBGCRYPT +#ifdef HAVE_LIBGCRYPT +#include <gcrypt.h> +#else #include <ouroboros/crc32.h> #include <ouroboros/md5.h> #include <ouroboros/sha3.h> -#else -#include <gcrypt.h> #endif #include <string.h> #include <assert.h> #include <stdbool.h> +#ifdef HAVE_LIBGCRYPT +int gcry_algo_tbl [] = { + /* DIR_HASH policies first */ + GCRY_MD_SHA3_224, + GCRY_MD_SHA3_256, + GCRY_MD_SHA3_384, + GCRY_MD_SHA3_512, + /* Below for internal use only */ + GCRY_MD_CRC32, + GCRY_MD_MD5, +}; +#else +int hash_len_tbl [] = { + /* DIR_HASH policies first */ + SHA3_224_HASH_LEN, + SHA3_256_HASH_LEN, + SHA3_384_HASH_LEN, + SHA3_512_HASH_LEN, + /* Below for internal use only */ + CRC32_HASH_LEN, + MD5_HASH_LEN +}; +#endif + uint16_t hash_len(enum hash_algo algo) { #ifdef HAVE_LIBGCRYPT - return (uint16_t) gcry_md_get_algo_dlen(algo); + return (uint16_t) gcry_md_get_algo_dlen(gcry_algo_tbl[algo]); #else - switch (algo) { - case HASH_CRC32: - return CRC32_HASH_LEN; - case HASH_MD5: - return MD5_HASH_LEN; - case HASH_SHA3_224: - return SHA3_224_HASH_LEN; - case HASH_SHA3_256: - return SHA3_256_HASH_LEN; - case HASH_SHA3_384: - return SHA3_384_HASH_LEN; - case HASH_SHA3_512: - return SHA3_512_HASH_LEN; - default: - assert(false); - break; - } - - return 0; + return hash_len_tbl[algo]; #endif } @@ -75,7 +82,7 @@ void mem_hash(enum hash_algo algo, size_t len) { #ifdef HAVE_LIBGCRYPT - gcry_md_hash_buffer(algo, dst, buf, len); + gcry_md_hash_buffer(gcry_algo_tbl[algo], dst, buf, len); #else struct sha3_ctx sha3_ctx; struct md5_ctx md5_ctx; @@ -84,6 +91,7 @@ void mem_hash(enum hash_algo algo, case HASH_CRC32: memset(dst, 0, CRC32_HASH_LEN); crc32((uint32_t *) dst, buf, len); + *(uint32_t *) dst = htobe32(*(uint32_t *) dst); break; case HASH_MD5: rhash_md5_init(&md5_ctx); diff --git a/src/lib/ipcp_config.proto b/src/lib/ipcp_config.proto deleted file mode 100644 index 185d9af7..00000000 --- a/src/lib/ipcp_config.proto +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2021 - * - * Layer configuration message - * - * Dimitri Staessens <dimitri@ouroboros.rocks> - * Sander Vrijders <sander@ouroboros.rocks> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * version 2.1 as published by the Free Software Foundation. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., http://www.fsf.org/about/contact/. - */ - -syntax = "proto2"; - -message layer_info_msg { - required string layer_name = 1; - required uint32 dir_hash_algo = 2; -} - -message ipcp_config_msg { - required layer_info_msg layer_info = 1; - required int32 ipcp_type = 2; - // Config for unicast IPCP - optional uint32 addr_size = 3; - optional uint32 eid_size = 4; - optional uint32 max_ttl = 5; - optional uint32 addr_auth_type = 6; - optional uint32 routing_type = 7; - optional uint32 cong_avoid = 8; - // Config for UDP - optional uint32 ip_addr = 9; - optional uint32 dns_addr = 10; - optional uint32 port = 11; - // Config for the Ethernet - optional string dev = 12; - // Config for DIX Ethernet - optional uint32 ethertype = 13; -} - -enum enroll_code { - ENROLL_REQ = 1; - ENROLL_BOOT = 2; - ENROLL_DONE = 4; -}; - -message enroll_msg { - required enroll_code code = 1; - optional ipcp_config_msg conf = 2; - optional int64 t_sec = 3; - optional uint32 t_nsec = 4; - optional int32 result = 5; -};
\ No newline at end of file diff --git a/src/lib/irm.c b/src/lib/irm.c index 64353f45..8333d0d3 100644 --- a/src/lib/irm.c +++ b/src/lib/irm.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * The API to instruct the IRM * @@ -31,28 +31,37 @@ #include <ouroboros/irm.h> #include <ouroboros/utils.h> #include <ouroboros/sockets.h> +#include <ouroboros/protobuf.h> #include <stdbool.h> #include <string.h> #include <stdlib.h> #include <sys/stat.h> -pid_t irm_create_ipcp(const char * name, - enum ipcp_type type) +int irm_create_ipcp(const char * name, + enum ipcp_type type) { - irm_msg_t msg = IRM_MSG__INIT; - irm_msg_t * recv_msg = NULL; - int ret = -1; + irm_msg_t msg = IRM_MSG__INIT; + irm_msg_t * recv_msg; + int ret; + struct ipcp_info info; - if (name == NULL) + if (name == NULL || strlen(name) > IPCP_NAME_SIZE) return -EINVAL; - msg.code = IRM_MSG_CODE__IRM_CREATE_IPCP; - msg.name = (char *) name; - msg.has_ipcp_type = true; - msg.ipcp_type = type; + memset(&info, 0, sizeof(info)); + + info.type = type; + strcpy(info.name, name); + + msg.code = IRM_MSG_CODE__IRM_CREATE_IPCP; + msg.ipcp_info = ipcp_info_s_to_msg(&info); + if (msg.ipcp_info == NULL) + return -ENOMEM; recv_msg = send_recv_irm_msg(&msg); + ipcp_info_msg__free_unpacked(msg.ipcp_info, NULL); + if (recv_msg == NULL) return -EIRMD; @@ -98,11 +107,9 @@ int irm_destroy_ipcp(pid_t pid) int irm_bootstrap_ipcp(pid_t pid, const struct ipcp_config * conf) { - irm_msg_t msg = IRM_MSG__INIT; - ipcp_config_msg_t config = IPCP_CONFIG_MSG__INIT; - layer_info_msg_t layer_info = LAYER_INFO_MSG__INIT; - irm_msg_t * recv_msg = NULL; - int ret = -1; + irm_msg_t msg = IRM_MSG__INIT; + irm_msg_t * recv_msg; + int ret; if (pid == -1 || conf == NULL) return -EINVAL; @@ -110,56 +117,10 @@ int irm_bootstrap_ipcp(pid_t pid, msg.code = IRM_MSG_CODE__IRM_BOOTSTRAP_IPCP; msg.has_pid = true; msg.pid = pid; - - config.layer_info = &layer_info; - msg.conf = &config; - layer_info.layer_name = (char *) conf->layer_info.layer_name; - - config.ipcp_type = conf->type; - - if (conf->type != IPCP_UDP) - layer_info.dir_hash_algo = conf->layer_info.dir_hash_algo; - - switch (conf->type) { - case IPCP_UNICAST: - config.has_addr_size = true; - config.addr_size = conf->addr_size; - config.has_eid_size = true; - config.eid_size = conf->eid_size; - config.has_max_ttl = true; - config.max_ttl = conf->max_ttl; - config.has_addr_auth_type = true; - config.addr_auth_type = conf->addr_auth_type; - config.has_routing_type = true; - config.routing_type = conf->routing_type; - config.has_cong_avoid = true; - config.cong_avoid = conf->cong_avoid; - break; - case IPCP_UDP: - config.has_ip_addr = true; - config.ip_addr = conf->ip_addr; - config.has_dns_addr = true; - config.dns_addr = conf->dns_addr; - config.has_port = true; - config.port = conf->port; - break; - case IPCP_LOCAL: - /* FALLTHRU */ - case IPCP_BROADCAST: - break; - case IPCP_ETH_LLC: - config.dev = conf->dev; - break; - case IPCP_ETH_DIX: - config.dev = conf->dev; - config.has_ethertype = true; - config.ethertype = conf->ethertype; - break; - default: - return -EIPCPTYPE; - } + msg.conf = ipcp_config_s_to_msg(conf); recv_msg = send_recv_irm_msg(&msg); + ipcp_config_msg__free_unpacked(msg.conf, NULL); if (recv_msg == NULL) return -EIRMD; @@ -179,8 +140,7 @@ int irm_connect_ipcp(pid_t pid, const char * component, qosspec_t qs) { - irm_msg_t msg = IRM_MSG__INIT; - qosspec_msg_t qs_msg = QOSSPEC_MSG__INIT; + irm_msg_t msg = IRM_MSG__INIT; irm_msg_t * recv_msg; int ret; @@ -190,10 +150,11 @@ int irm_connect_ipcp(pid_t pid, msg.comp = (char *) component; msg.has_pid = true; msg.pid = pid; - qs_msg = spec_to_msg(&qs); - msg.qosspec = &qs_msg; + msg.qosspec = qos_spec_s_to_msg(&qs); recv_msg = send_recv_irm_msg(&msg); + qosspec_msg__free_unpacked(msg.qosspec, NULL); + if (recv_msg == NULL) return -EIRMD; @@ -237,7 +198,7 @@ int irm_disconnect_ipcp(pid_t pid, return ret; } -ssize_t irm_list_ipcps(struct ipcp_info ** ipcps) +ssize_t irm_list_ipcps(struct ipcp_list_info ** ipcps) { irm_msg_t msg = IRM_MSG__INIT; irm_msg_t * recv_msg; @@ -407,50 +368,69 @@ int irm_bind_program(const char * prog, int argc, char ** argv) { - irm_msg_t msg = IRM_MSG__INIT; - irm_msg_t * recv_msg = NULL; - int ret = -1; - char * full_name; + irm_msg_t msg = IRM_MSG__INIT; + irm_msg_t * recv_msg; + char ** exec; + int ret; + int i; if (prog == NULL || name == NULL) return -EINVAL; - full_name = strdup(prog); - if (full_name == NULL) - return -ENOMEM; + exec = malloc((argc + 2) * sizeof(*exec)); + if (exec== NULL) { + ret = -ENOMEM; + goto fail_exec; + } - if ((ret = check_prog_path(&full_name)) < 0) { - free(full_name); - return ret; + exec[0] = strdup(prog); + if (exec[0] == NULL) { + ret = -ENOMEM; + goto fail_exec0; } + ret = check_prog_path(&exec[0]); + if (ret < 0) + goto fail; + + for (i = 0; i < argc; i++) + exec[i + 1] = argv[i]; + + exec[argc + 1] = ""; + msg.code = IRM_MSG_CODE__IRM_BIND_PROGRAM; msg.name = (char *) name; - msg.prog = full_name; - if (argv != NULL) { - msg.n_args = argc; - msg.args = (char **) argv; - } + msg.n_exec = argc + 2; + msg.exec = exec; msg.has_opts = true; msg.opts = opts; recv_msg = send_recv_irm_msg(&msg); - - free(full_name); - - if (recv_msg == NULL) - return -EIRMD; + if (recv_msg == NULL) { + ret = -EIRMD; + goto fail; + } if (!recv_msg->has_result) { irm_msg__free_unpacked(recv_msg, NULL); - return -1; + ret = -EPERM; + goto fail; } ret = recv_msg->result; irm_msg__free_unpacked(recv_msg, NULL); + free(exec[0]); + free(exec); + + return ret; + fail: + free(exec[0]); + fail_exec0: + free(exec); + fail_exec: return ret; } @@ -543,32 +523,23 @@ int irm_unbind_process(pid_t pid, return ret; } -int irm_create_name(const char * name, - enum pol_balance pol) +int irm_create_name(struct name_info * info) { irm_msg_t msg = IRM_MSG__INIT; - name_info_msg_t ni_msg = NAME_INFO_MSG__INIT; irm_msg_t * recv_msg; int ret; - if (name == NULL) + if (info == NULL) return -EINVAL; - msg.code = IRM_MSG_CODE__IRM_CREATE_NAME; - ni_msg.name = (char *) name; - ni_msg.pol_lb = pol; - msg.n_names = 1; - - msg.names = malloc(sizeof(*msg.names)); - if (msg.names == NULL) { - return -ENOMEM; - } - - msg.names[0] = &ni_msg; + msg.code = IRM_MSG_CODE__IRM_CREATE_NAME; + msg.name_info = name_info_s_to_msg(info); + if (msg.name_info == NULL) + goto fail_info_msg; recv_msg = send_recv_irm_msg(&msg); - free(msg.names); + name_info_msg__free_unpacked(msg.name_info, NULL); if (recv_msg == NULL) return -EIRMD; @@ -582,6 +553,9 @@ int irm_create_name(const char * name, irm_msg__free_unpacked(recv_msg, NULL); return ret; + + fail_info_msg: + return -ENOMEM; } int irm_destroy_name(const char * name) diff --git a/src/lib/irmd_messages.proto b/src/lib/irmd_messages.proto deleted file mode 100644 index 515d486f..00000000 --- a/src/lib/irmd_messages.proto +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2021 - * - * IRMd message - * - * Dimitri Staessens <dimitri@ouroboros.rocks> - * Sander Vrijders <sander@ouroboros.rocks> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * version 2.1 as published by the Free Software Foundation. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., http://www.fsf.org/about/contact/. - */ - -syntax = "proto2"; - -import "ipcp_config.proto"; -import "qosspec.proto"; - -enum irm_msg_code { - IRM_CREATE_IPCP = 1; - IPCP_CREATE_R = 2; - IRM_DESTROY_IPCP = 3; - IRM_LIST_IPCPS = 4; - IRM_BOOTSTRAP_IPCP = 5; - IRM_ENROLL_IPCP = 6; - IRM_CONNECT_IPCP = 7; - IRM_DISCONNECT_IPCP = 8; - IRM_BIND_PROGRAM = 9; - IRM_UNBIND_PROGRAM = 10; - IRM_PROC_ANNOUNCE = 11; - IRM_BIND_PROCESS = 12; - IRM_UNBIND_PROCESS = 13; - IRM_CREATE_NAME = 14; - IRM_DESTROY_NAME = 15; - IRM_LIST_NAMES = 16; - IRM_REG_NAME = 17; - IRM_UNREG_NAME = 18; - IRM_FLOW_ALLOC = 19; - IRM_FLOW_ACCEPT = 20; - IRM_FLOW_JOIN = 21; - IRM_FLOW_DEALLOC = 22; - IPCP_FLOW_REQ_ARR = 23; - IPCP_FLOW_ALLOC_REPLY = 24; - IRM_REPLY = 25; -}; - -message ipcp_info_msg { - required uint32 pid = 1; - required uint32 type = 2; - required string name = 3; - required string layer = 4; -}; - -message name_info_msg { - required string name = 1; - required uint32 pol_lb = 2; -}; - -message irm_msg { - required irm_msg_code code = 1; - optional string prog = 2; - optional sint32 pid = 3; - optional string name = 4; - optional uint32 ipcp_type = 5; - optional string layer = 6; - repeated string args = 7; - optional sint32 response = 8; - optional string dst = 9; - optional bytes hash = 10; - optional sint32 flow_id = 11; - optional qosspec_msg qosspec = 12; - optional ipcp_config_msg conf = 13; - optional uint32 opts = 14; - repeated ipcp_info_msg ipcps = 15; - repeated name_info_msg names = 16; - optional uint32 timeo_sec = 17; - optional uint32 timeo_nsec = 18; - optional string comp = 19; - optional bytes pk = 20; /* piggyback */ - optional sint32 result = 21; -}; diff --git a/src/lib/list.c b/src/lib/list.c index 55c5d04a..62b2eb27 100644 --- a/src/lib/list.c +++ b/src/lib/list.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Simple doubly linked list implementation. * @@ -62,10 +62,10 @@ void list_add_tail(struct list_head * n, void list_del(struct list_head * e) { del_list(e->prv, e->nxt); - e->nxt = e->prv = NULL; + e->nxt = e->prv = e; } -bool list_is_empty(struct list_head * h) +bool list_is_empty(const struct list_head * h) { return h->nxt == h; } diff --git a/src/lib/lockfile.c b/src/lib/lockfile.c index b0e1115f..cf6d3c94 100644 --- a/src/lib/lockfile.c +++ b/src/lib/lockfile.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Lockfile for Ouroboros * @@ -36,81 +36,69 @@ #include <sys/stat.h> #define LF_SIZE (sizeof(pid_t)) +#define LF_PROT (PROT_READ | PROT_WRITE) struct lockfile { pid_t * pid; }; -struct lockfile * lockfile_create() { - int fd; - mode_t mask; - struct lockfile * lf = malloc(sizeof(*lf)); - if (lf == NULL) - return NULL; - - mask = umask(0); +static struct lockfile * __lockfile_open(int oflag) +{ + int fd; + struct lockfile * lf; - fd = shm_open(SHM_LOCKFILE_NAME, O_CREAT | O_EXCL | O_RDWR, 0666); - if (fd == -1) { - free(lf); - return NULL; - } + lf = malloc(sizeof(*lf)); + if (lf == NULL) + goto fail_lockfile; - umask(mask); + fd = shm_open(SHM_LOCKFILE_NAME, oflag, 0666); + if (fd == -1) + goto fail_shm_open; - if (ftruncate(fd, LF_SIZE - 1) < 0) { - free(lf); - return NULL; - } + if ((oflag & O_CREAT) && ftruncate(fd, LF_SIZE) < 0) + goto fail_truncate; - lf->pid = mmap(NULL, - LF_SIZE, PROT_READ | PROT_WRITE, - MAP_SHARED, - fd, - 0); + lf->pid = mmap(NULL, LF_SIZE, LF_PROT, MAP_SHARED, fd, 0); + if (lf->pid == MAP_FAILED) + goto fail_mmap; close (fd); - if (lf->pid == MAP_FAILED) { - shm_unlink(SHM_LOCKFILE_NAME); - free(lf); - return NULL; - } - - *lf->pid = getpid(); - return lf; + + fail_mmap: + shm_unlink(SHM_LOCKFILE_NAME); + fail_truncate: + close(fd); + fail_shm_open: + free(lf); + fail_lockfile: + return NULL; } -struct lockfile * lockfile_open() { - int fd; - struct lockfile * lf = malloc(sizeof(*lf)); - if (lf == NULL) - return NULL; +struct lockfile * lockfile_create(void) +{ + struct lockfile * lf; + mode_t mask; - fd = shm_open(SHM_LOCKFILE_NAME, O_RDWR, 0666); - if (fd < 0) { - free(lf); - return NULL; - } + mask = umask(0); - lf->pid = mmap(NULL, - LF_SIZE, PROT_READ | PROT_WRITE, - MAP_SHARED, - fd, - 0); + lf = __lockfile_open(O_CREAT | O_EXCL | O_RDWR); + if (lf == NULL) + return NULL; - close(fd); + umask(mask); - if (lf->pid == MAP_FAILED) { - shm_unlink(SHM_LOCKFILE_NAME); - free(lf); - return NULL; - } + *lf->pid = getpid(); return lf; } +struct lockfile * lockfile_open(void) +{ + return __lockfile_open(O_RDWR); +} + void lockfile_close(struct lockfile * lf) { assert(lf); @@ -127,11 +115,9 @@ void lockfile_destroy(struct lockfile * lf) if (getpid() != *lf->pid && kill(*lf->pid, 0) == 0) return; - munmap(lf->pid, LF_SIZE); + lockfile_close(lf); shm_unlink(SHM_LOCKFILE_NAME); - - free(lf); } pid_t lockfile_owner(struct lockfile * lf) diff --git a/src/lib/logs.c b/src/lib/logs.c index edfeac15..d90bcd63 100644 --- a/src/lib/logs.c +++ b/src/lib/logs.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Logging facilities * diff --git a/src/lib/md5.c b/src/lib/md5.c index a9152ee2..ad0dd4d7 100644 --- a/src/lib/md5.c +++ b/src/lib/md5.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * MD5 algorithm * diff --git a/src/lib/notifier.c b/src/lib/notifier.c index 1f600765..4fccd371 100644 --- a/src/lib/notifier.c +++ b/src/lib/notifier.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Notifier event system using callbacks * @@ -95,18 +95,14 @@ int notifier_reg(notifier_fn_t callback, pthread_rwlock_wrlock(¬ifier.lock); list_for_each(p, ¬ifier.listeners) { - struct listener * l = list_entry(p, struct listener, next); - if (l->callback == callback) { - pthread_rwlock_unlock(¬ifier.lock); - return -EPERM; - } + l = list_entry(p, struct listener, next); + if (l->callback == callback) + goto fail; } l = malloc(sizeof(*l)); - if (l == NULL) { - pthread_rwlock_unlock(¬ifier.lock); - return -ENOMEM; - } + if (l == NULL) + goto fail; l->callback = callback; l->obj = obj; @@ -116,6 +112,10 @@ int notifier_reg(notifier_fn_t callback, pthread_rwlock_unlock(¬ifier.lock); return 0; + fail: + pthread_rwlock_unlock(¬ifier.lock); + return -1; + } void notifier_unreg(notifier_fn_t callback) diff --git a/src/lib/cacep.proto b/src/lib/pb/cep.proto index f0ef1e98..d31cf4f7 100644 --- a/src/lib/cacep.proto +++ b/src/lib/pb/cep.proto @@ -1,7 +1,7 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * - * Message for Connection Information in CACEP + * Message for Connection Information in OCEP * * Dimitri Staessens <dimitri@ouroboros.rocks> * Sander Vrijders <sander@ouroboros.rocks> @@ -27,7 +27,7 @@ message fixed_conc_syntax_msg { repeated uint32 lens = 2; } -message cacep_msg { +message cep_msg { required string comp_name = 1; required string protocol = 2; required int32 pref_version = 3; @@ -36,4 +36,4 @@ message cacep_msg { repeated int32 supp_syntax = 6; optional fixed_conc_syntax_msg syntax_spec = 7; required uint64 address = 8; -}
\ No newline at end of file +} diff --git a/src/lib/qosspec.proto b/src/lib/pb/enroll.proto index 8a355363..60e964c6 100644 --- a/src/lib/qosspec.proto +++ b/src/lib/pb/enroll.proto @@ -1,7 +1,7 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * - * QoS specification message + * Enrollment protocol * * Dimitri Staessens <dimitri@ouroboros.rocks> * Sander Vrijders <sander@ouroboros.rocks> @@ -21,14 +21,21 @@ */ syntax = "proto2"; +import "ipcp_config.proto"; -message qosspec_msg { - required uint32 delay = 1; /* In ms */ - required uint64 bandwidth = 2; /* In bits/s */ - required uint32 availability = 3; /* Class of 9s */ - required uint32 loss = 4; /* Packet loss */ - required uint32 ber = 5; /* Bit error rate, ppb */ - required uint32 in_order = 6; /* In-order delivery */ - required uint32 max_gap = 7; /* In ms */ - required uint32 cypher_s = 8; /* Crypto strength in bits */ -}; +message enroll_req_msg { + required bytes id = 1; +} + +message enroll_resp_msg { + required bytes id = 1; + required int64 t_sec = 2; + required int32 t_nsec = 3; + required int32 response = 4; + optional ipcp_config_msg conf = 5; +} + +message enroll_ack_msg { + required bytes id = 1; + required int32 result = 2; +} diff --git a/src/lib/ipcpd_messages.proto b/src/lib/pb/ipcp.proto index a8c3bfb4..deddf6af 100644 --- a/src/lib/ipcpd_messages.proto +++ b/src/lib/pb/ipcp.proto @@ -1,7 +1,7 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * - * IPCPd message + * Messages sent to IPCPds * * Dimitri Staessens <dimitri@ouroboros.rocks> * Sander Vrijders <sander@ouroboros.rocks> @@ -23,7 +23,8 @@ syntax = "proto2"; import "ipcp_config.proto"; -import "qosspec.proto"; +import "model.proto"; + enum ipcp_msg_code { IPCP_BOOTSTRAP = 1; @@ -38,7 +39,7 @@ enum ipcp_msg_code { IPCP_CONNECT = 10; IPCP_DISCONNECT = 11; IPCP_REPLY = 12; -}; +} message ipcp_msg { required ipcp_msg_code code = 1; @@ -53,5 +54,7 @@ message ipcp_msg { optional int32 response = 10; optional string comp = 11; optional uint32 timeo_sec = 12; - optional int32 result = 13; -}; + optional sint32 mpl = 13; + optional int32 result = 14; + optional uint32 uid = 15; /* 0 = GSPP, >0 = PUP uid */ +} diff --git a/src/lib/pb/ipcp_config.proto b/src/lib/pb/ipcp_config.proto new file mode 100644 index 00000000..1308c6d1 --- /dev/null +++ b/src/lib/pb/ipcp_config.proto @@ -0,0 +1,93 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * Layer configuration message + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +syntax = "proto2"; + +import "model.proto"; + + +message ls_config_msg { + required uint32 pol = 1; + required uint32 t_recalc = 2; + required uint32 t_update = 3; + required uint32 t_timeo = 4; +} + +message routing_config_msg { + required uint32 pol = 1; + optional ls_config_msg ls = 2; +} + +message dt_config_msg { + required uint32 addr_size = 1; + required uint32 eid_size = 2; + required uint32 max_ttl = 3; + required routing_config_msg routing = 4; +} + +message dir_dht_config_msg { + required uint32 alpha = 1; + required uint32 k = 2; + required uint32 t_expire = 3; + required uint32 t_refresh = 4; + required uint32 t_replicate = 5; + required uint64 peer = 6; +} + +message dir_config_msg { + required uint32 pol = 1; + optional dir_dht_config_msg dht = 2; +} + +message uni_config_msg { + required dt_config_msg dt = 1; + required dir_config_msg dir = 2; + required uint32 addr_auth_type = 3; + required uint32 cong_avoid = 4; +} + +message eth_config_msg { + required string dev = 1; + required uint32 ethertype = 2; +} + +message udp4_config_msg { + required uint32 ip_addr = 1; + required uint32 port = 2; + required uint32 dns_addr = 3; /* set to 0 if unused */ +} + +message udp6_config_msg { + required bytes ip_addr = 1; + required uint32 port = 2; + required bytes dns_addr = 3; /* set to NULL if unused */ +} + + +message ipcp_config_msg { + required layer_info_msg layer_info = 1; + required uint32 ipcp_type = 2; + optional uni_config_msg unicast = 3; + optional udp4_config_msg udp4 = 4; + optional udp6_config_msg udp6 = 5; + optional eth_config_msg eth = 6; +} diff --git a/src/lib/pb/irm.proto b/src/lib/pb/irm.proto new file mode 100644 index 00000000..1845c694 --- /dev/null +++ b/src/lib/pb/irm.proto @@ -0,0 +1,99 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * Messages sent to IRMd + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +syntax = "proto2"; + +import "ipcp_config.proto"; +import "model.proto"; + +enum irm_msg_code { + IRM_CREATE_IPCP = 1; + IPCP_CREATE_R = 2; + IRM_DESTROY_IPCP = 3; + IRM_LIST_IPCPS = 4; + IRM_BOOTSTRAP_IPCP = 5; + IRM_ENROLL_IPCP = 6; + IRM_CONNECT_IPCP = 7; + IRM_DISCONNECT_IPCP = 8; + IRM_BIND_PROGRAM = 9; + IRM_UNBIND_PROGRAM = 10; + IRM_PROC_ANNOUNCE = 11; + IRM_PROC_EXIT = 12; + IRM_BIND_PROCESS = 13; + IRM_UNBIND_PROCESS = 14; + IRM_CREATE_NAME = 15; + IRM_DESTROY_NAME = 16; + IRM_LIST_NAMES = 17; + IRM_REG_NAME = 18; + IRM_UNREG_NAME = 19; + IRM_FLOW_ALLOC = 20; + IRM_FLOW_ACCEPT = 21; + IRM_FLOW_JOIN = 22; + IRM_FLOW_DEALLOC = 23; + IPCP_FLOW_DEALLOC = 24; + IPCP_FLOW_REQ_ARR = 25; + IPCP_FLOW_ALLOC_REPLY = 26; + IRM_REPLY = 27; +} + +message timespec_msg { + required uint64 tv_sec = 1; + required uint32 tv_nsec = 2; +} + +message ipcp_list_msg { + required uint32 pid = 1; + required uint32 type = 2; + required string name = 3; + required string layer = 4; + required uint32 hash_algo = 5; +} + +message irm_msg { + required irm_msg_code code = 1; + optional string prog = 2; + optional sint32 pid = 3; + optional string name = 4; + optional flow_info_msg flow_info = 5; + optional ipcp_info_msg ipcp_info = 6; + optional name_info_msg name_info = 7; + optional string layer = 8; + repeated string exec = 9; + optional sint32 response = 10; + optional string dst = 11; + optional bytes hash = 12; + optional sint32 flow_id = 13; + optional qosspec_msg qosspec = 14; + optional ipcp_config_msg conf = 15; + optional uint32 opts = 16; + repeated ipcp_list_msg ipcps = 17; + repeated name_info_msg names = 18; + optional timespec_msg timeo = 19; + optional sint32 mpl = 20; + optional string comp = 21; + optional bytes pk = 22; /* piggyback */ + optional uint32 timeo_sec = 23; + optional uint32 timeo_nsec = 24; + optional sint32 result = 25; + optional bytes sym_key = 26; /* symmetric encryption key */ + optional sint32 cipher_nid = 27; /* cipher NID */ +} diff --git a/src/lib/pb/model.proto b/src/lib/pb/model.proto new file mode 100644 index 00000000..82700a9a --- /dev/null +++ b/src/lib/pb/model.proto @@ -0,0 +1,65 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * Model description messages + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +syntax = "proto2"; + +message qosspec_msg { + required uint32 delay = 1; /* In ms. */ + required uint64 bandwidth = 2; /* In bits/s. */ + required uint32 availability = 3; /* Class of 9s. */ + required uint32 loss = 4; /* Packet loss. */ + required uint32 ber = 5; /* Bit error rate, ppb. */ + required uint32 in_order = 6; /* In-order delivery. */ + required uint32 max_gap = 7; /* In ms. */ + required uint32 timeout = 8; /* Timeout in ms. */ +} + +message flow_info_msg { + required uint32 id = 1; + required uint32 n_pid = 2; + required uint32 n_1_pid = 3; + required uint32 mpl = 4; + required uint32 state = 5; + required qosspec_msg qos = 6; + required uint32 uid = 7; +} + +message name_info_msg { + required string name = 1; + required uint32 pol_lb = 2; + required string skey = 3; + required string scrt = 4; + required string ckey = 5; + required string ccrt = 6; +} + +message layer_info_msg { + required string name = 1; + required uint32 dir_hash_algo = 2; +} + +message ipcp_info_msg { + required uint32 type = 1; + required string name = 2; + required uint32 pid = 3; + required uint32 state = 4; +} diff --git a/src/lib/protobuf.c b/src/lib/protobuf.c new file mode 100644 index 00000000..4617323a --- /dev/null +++ b/src/lib/protobuf.c @@ -0,0 +1,921 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * Protobuf syntax conversion + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#define _DEFAULT_SOURCE + +#include <ouroboros/protobuf.h> +#include <ouroboros/crypt.h> +#include <ouroboros/proc.h> + +#include <stdlib.h> +#include <string.h> +#include <time.h> + +timespec_msg_t * timespec_s_to_msg(const struct timespec * s) +{ + timespec_msg_t * msg; + + assert(s != NULL); + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + goto fail_malloc; + + timespec_msg__init(msg); + + msg->tv_sec = s->tv_sec; + msg->tv_nsec = s->tv_nsec; + + return msg; + + fail_malloc: + return NULL; +} + +struct timespec timespec_msg_to_s(timespec_msg_t * msg) +{ + struct timespec s; + + assert(msg != NULL); + + s.tv_sec = msg->tv_sec; + s.tv_nsec = msg->tv_nsec; + + return s; +} + +flow_info_msg_t * flow_info_s_to_msg(const struct flow_info * s) +{ + flow_info_msg_t * msg; + + assert(s != NULL); + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + goto fail_malloc; + + flow_info_msg__init(msg); + + msg->id = s->id; + msg->n_pid = s->n_pid; + msg->n_1_pid = s->n_1_pid; + msg->mpl = s->mpl; + msg->state = s->state; + msg->uid = s->uid; + msg->qos = qos_spec_s_to_msg(&s->qs); + if (msg->qos == NULL) + goto fail_msg; + + return msg; + + fail_msg: + flow_info_msg__free_unpacked(msg, NULL); + fail_malloc: + return NULL; +} + +struct flow_info flow_info_msg_to_s(const flow_info_msg_t * msg) +{ + struct flow_info s; + + assert(msg != NULL); + + memset(&s, 0, sizeof(s)); + + s.id = msg->id; + s.n_pid = msg->n_pid; + s.n_1_pid = msg->n_1_pid; + s.mpl = msg->mpl; + s.state = msg->state; + s.uid = msg->uid; + s.qs = qos_spec_msg_to_s(msg->qos); + + return s; +} + +name_info_msg_t * name_info_s_to_msg(const struct name_info * info) +{ + name_info_msg_t * msg; + + assert(info != NULL); + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + goto fail_malloc; + + name_info_msg__init(msg); + + msg->name = strdup(info->name); + if (msg->name == NULL) + goto fail_msg; + + msg->skey = strdup(info->s.key); + if (msg->skey == NULL) + goto fail_msg; + + msg->scrt = strdup(info->s.crt); + if (msg->scrt == NULL) + goto fail_msg; + + msg->ckey = strdup(info->c.key); + if (msg->skey == NULL) + goto fail_msg; + + msg->ccrt = strdup(info->c.crt); + if (msg->ccrt == NULL) + goto fail_msg; + + msg->pol_lb = info->pol_lb; + + return msg; + + fail_msg: + name_info_msg__free_unpacked(msg, NULL); + fail_malloc: + return NULL; +} + +struct name_info name_info_msg_to_s(const name_info_msg_t * msg) +{ + struct name_info s; + + assert(msg != NULL); + assert(strlen(msg->name) <= NAME_SIZE); + + strcpy(s.name, msg->name); + strcpy(s.s.key, msg->skey); + strcpy(s.s.crt, msg->scrt); + strcpy(s.c.key, msg->ckey); + strcpy(s.c.crt, msg->ccrt); + + s.pol_lb = msg->pol_lb; + + return s; +} + +layer_info_msg_t * layer_info_s_to_msg(const struct layer_info * s) +{ + layer_info_msg_t * msg; + + assert(s != NULL); + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + goto fail_malloc; + + layer_info_msg__init(msg); + + msg->name = strdup(s->name); + if (msg->name == NULL) + goto fail_msg; + + msg->dir_hash_algo = s->dir_hash_algo; + + return msg; + + fail_msg: + layer_info_msg__free_unpacked(msg, NULL); + fail_malloc: + return NULL; +} + +struct layer_info layer_info_msg_to_s(const layer_info_msg_t * msg) +{ + struct layer_info s; + + assert(msg != NULL); + assert(strlen(msg->name) <= LAYER_NAME_SIZE); + + s.dir_hash_algo = msg->dir_hash_algo; + strcpy(s.name, msg->name); + + return s; +} + +ipcp_info_msg_t * ipcp_info_s_to_msg(const struct ipcp_info * s) +{ + ipcp_info_msg_t * msg; + + assert(s != NULL); + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + goto fail_malloc; + + ipcp_info_msg__init(msg); + + msg->name = strdup(s->name); + if (msg->name == NULL) + goto fail_msg; + + msg->type = s->type; + msg->pid = s->pid; + msg->state = s->state; + + return msg; + fail_msg: + ipcp_info_msg__free_unpacked(msg, NULL); + fail_malloc: + return NULL; +} + +struct ipcp_info ipcp_info_msg_to_s(const ipcp_info_msg_t * msg) +{ + struct ipcp_info s; + + assert(msg != NULL); + assert(msg->name != NULL); + assert(strlen(msg->name) <= NAME_SIZE); + + strcpy(s.name, msg->name); + s.type = msg->type; + s.pid = msg->pid; + s.state = msg->state; + + return s; +} + +ls_config_msg_t * ls_config_s_to_msg(const struct ls_config * s) +{ + ls_config_msg_t * msg; + + assert(s != NULL); + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + goto fail_malloc; + + ls_config_msg__init(msg); + + msg->pol = s->pol; + msg->t_recalc = s->t_recalc; + msg->t_update = s->t_update; + msg->t_timeo = s->t_timeo; + + return msg; + + fail_malloc: + return NULL; +} + +struct ls_config ls_config_msg_to_s(const ls_config_msg_t * msg) +{ + struct ls_config s; + + assert(msg != NULL); + + s.pol = msg->pol; + s.t_recalc = msg->t_recalc; + s.t_update = msg->t_update; + s.t_timeo = msg->t_timeo; + + return s; +} + +routing_config_msg_t * routing_config_s_to_msg(const struct routing_config * s) +{ + routing_config_msg_t * msg; + + assert(s != NULL); + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + return NULL; + + routing_config_msg__init(msg); + + switch (s->pol) { + case ROUTING_LINK_STATE: + msg->ls = ls_config_s_to_msg(&s->ls); + if (msg->ls == NULL) + goto fail_ls; + break; + default: + /* No checks here */ + break; + } + + msg->pol = s->pol; + + return msg; + + fail_ls: + routing_config_msg__free_unpacked(msg, NULL); + return NULL; +} + +struct routing_config routing_config_msg_to_s(const routing_config_msg_t * msg) +{ + struct routing_config s; + + assert(msg != NULL); + + switch (msg->pol) { + case ROUTING_LINK_STATE: + s.ls = ls_config_msg_to_s(msg->ls); + break; + default: + /* No checks here */ + break; + } + + s.pol = msg->pol; + + return s; +} + +dt_config_msg_t * dt_config_s_to_msg(const struct dt_config * s) +{ + dt_config_msg_t * msg; + + assert(s != NULL); + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + return NULL; + + dt_config_msg__init(msg); + + msg->addr_size = s->addr_size; + msg->eid_size = s->eid_size; + msg->max_ttl = s->max_ttl; + msg->routing = routing_config_s_to_msg(&s->routing); + if (msg->routing == NULL) + goto fail_routing; + + return msg; + fail_routing: + dt_config_msg__free_unpacked(msg, NULL); + return NULL; +} + +struct dt_config dt_config_msg_to_s(const dt_config_msg_t * msg) +{ + struct dt_config s; + + assert(msg != NULL); + + s.addr_size = msg->addr_size; + s.eid_size = msg->eid_size; + s.max_ttl = msg->max_ttl; + s.routing = routing_config_msg_to_s(msg->routing); + + return s; +} + +struct dir_dht_config dir_dht_config_msg_to_s(const dir_dht_config_msg_t * msg) +{ + struct dir_dht_config s; + + assert(msg != NULL); + + s.params.alpha = msg->alpha; + s.params.k = msg->k; + s.params.t_expire = msg->t_expire; + s.params.t_refresh = msg->t_refresh; + s.params.t_replicate = msg->t_replicate; + s.peer = msg->peer; + + return s; +} + +dir_dht_config_msg_t * dir_dht_config_s_to_msg(const struct dir_dht_config * s) +{ + dir_dht_config_msg_t * msg; + + assert(s != NULL); + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + return NULL; + + dir_dht_config_msg__init(msg); + + msg->alpha = s->params.alpha; + msg->k = s->params.k; + msg->t_expire = s->params.t_expire; + msg->t_refresh = s->params.t_refresh; + msg->t_replicate = s->params.t_replicate; + msg->peer = s->peer; + + return msg; +} + +struct dir_config dir_config_msg_to_s(const dir_config_msg_t * msg) +{ + struct dir_config s; + + assert(msg != NULL); + + switch (msg->pol) { + case DIR_DHT: + s.dht = dir_dht_config_msg_to_s(msg->dht); + break; + default: + /* No checks here */ + break; + } + + s.pol = msg->pol; + + return s; +} + +dir_config_msg_t * dir_config_s_to_msg(const struct dir_config * s) +{ + dir_config_msg_t * msg; + + assert(s != NULL); + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + return NULL; + + dir_config_msg__init(msg); + + switch (s->pol) { + case DIR_DHT: + msg->dht = dir_dht_config_s_to_msg(&s->dht); + if (msg->dht == NULL) + goto fail_msg; + break; + default: + /* No checks here */ + break; + } + + msg->pol = s->pol; + + return msg; + + fail_msg: + dir_config_msg__free_unpacked(msg, NULL); + return NULL; +} + + +uni_config_msg_t * uni_config_s_to_msg(const struct uni_config * s) +{ + uni_config_msg_t * msg; + + assert(s != NULL); + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + goto fail_malloc; + + uni_config_msg__init(msg); + + msg->dt = dt_config_s_to_msg(&s->dt); + if (msg->dt == NULL) + goto fail_msg; + + msg->dir = dir_config_s_to_msg(&s->dir); + if (msg->dir == NULL) + goto fail_msg; + + + msg->addr_auth_type = s->addr_auth_type; + msg->cong_avoid = s->cong_avoid; + + return msg; + + fail_msg: + uni_config_msg__free_unpacked(msg, NULL); + fail_malloc: + return NULL; +} + +struct uni_config uni_config_msg_to_s(const uni_config_msg_t * msg) +{ + struct uni_config s; + + s.dt = dt_config_msg_to_s(msg->dt); + s.dir = dir_config_msg_to_s(msg->dir); + + s.addr_auth_type = msg->addr_auth_type; + s.cong_avoid = msg->cong_avoid; + + return s; +} + +udp4_config_msg_t * udp4_config_s_to_msg(const struct udp4_config * s) +{ + udp4_config_msg_t * msg; + + assert(s != NULL); + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + return NULL; + + udp4_config_msg__init(msg); + + msg->ip_addr = s->ip_addr.s_addr; + msg->dns_addr = s->dns_addr.s_addr; + msg->port = s->port; + + return msg; +} + +struct udp4_config udp4_config_msg_to_s(const udp4_config_msg_t * msg) +{ + struct udp4_config s; + + assert(msg != NULL); + + s.ip_addr.s_addr = msg->ip_addr; + s.dns_addr.s_addr = msg->dns_addr; + s.port = msg->port; + + return s; +} + +#define IN6_LEN sizeof(struct in6_addr) +udp6_config_msg_t * udp6_config_s_to_msg(const struct udp6_config * s) +{ + udp6_config_msg_t * msg; + + assert(s != NULL); + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + goto fail_malloc; + + udp6_config_msg__init(msg); + + msg->ip_addr.data = malloc(IN6_LEN); + if (msg->ip_addr.data == NULL) + goto fail_msg; + + msg->ip_addr.len = IN6_LEN; + memcpy(msg->ip_addr.data, &s->ip_addr.s6_addr, IN6_LEN); + + msg->dns_addr.data = malloc(IN6_LEN); + if (msg->dns_addr.data == NULL) + goto fail_msg; + + msg->dns_addr.len = IN6_LEN; + memcpy(msg->dns_addr.data, &s->dns_addr.s6_addr, IN6_LEN); + + msg->port = s->port; + + return msg; + + fail_msg: + udp6_config_msg__free_unpacked(msg, NULL); + fail_malloc: + return NULL; +} + +struct udp6_config udp6_config_msg_to_s(const udp6_config_msg_t * msg) +{ + struct udp6_config s; + + assert(msg != NULL); + + assert(msg->ip_addr.len == IN6_LEN); + assert(msg->dns_addr.len == IN6_LEN); + + memcpy(&s.ip_addr.s6_addr, msg->ip_addr.data, IN6_LEN); + memcpy(&s.dns_addr.s6_addr, msg->dns_addr.data, IN6_LEN); + s.port = msg->port; + + return s; +} + +eth_config_msg_t * eth_config_s_to_msg(const struct eth_config * s) +{ + eth_config_msg_t * msg; + + assert(s != NULL); + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + goto fail_malloc; + + eth_config_msg__init(msg); + + msg->dev = strdup(s->dev); + if (msg->dev == NULL) + goto fail_msg; + + msg->ethertype = s->ethertype; + + return msg; + + fail_msg: + eth_config_msg__free_unpacked(msg, NULL); + fail_malloc: + return NULL; +} + +struct eth_config eth_config_msg_to_s(const eth_config_msg_t * msg) +{ + struct eth_config s; + + assert(msg != NULL); + assert(strlen(msg->dev) <= DEV_NAME_SIZE); + + strcpy(s.dev, msg->dev); + s.ethertype = msg->ethertype; + + return s; +} + + +ipcp_config_msg_t * ipcp_config_s_to_msg(const struct ipcp_config * s) +{ + ipcp_config_msg_t * msg; + + assert(s != NULL); + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + goto fail_malloc; + + ipcp_config_msg__init(msg); + + switch (s->type) { + case IPCP_LOCAL: + break; + case IPCP_UNICAST: + msg->unicast = uni_config_s_to_msg(&s->unicast); + if (msg->unicast == NULL) + goto fail_msg; + break; + case IPCP_BROADCAST: + break; + case IPCP_ETH_LLC: + /* FALLTHRU */ + case IPCP_ETH_DIX: + msg->eth = eth_config_s_to_msg(&s->eth); + if (msg->eth == NULL) + goto fail_msg; + break; + case IPCP_UDP4: + msg->udp4 = udp4_config_s_to_msg(&s->udp4); + if (msg->udp4 == NULL) + goto fail_msg; + break; + case IPCP_UDP6: + msg->udp6 = udp6_config_s_to_msg(&s->udp6); + if (msg->udp6 == NULL) + goto fail_msg; + break; + default: + /* No checks here */ + break; + } + + msg->ipcp_type = s->type; + + msg->layer_info = layer_info_s_to_msg(&s->layer_info); + if (msg->layer_info == NULL) + goto fail_msg; + + return msg; + + fail_msg: + ipcp_config_msg__free_unpacked(msg, NULL); + fail_malloc: + return NULL; +} + +struct ipcp_config ipcp_config_msg_to_s(const ipcp_config_msg_t * msg) +{ + struct ipcp_config s; + + assert(msg != NULL); + + s.type = msg->ipcp_type; + + s.layer_info = layer_info_msg_to_s(msg->layer_info); + + switch(msg->ipcp_type) { + case IPCP_LOCAL: + break; + case IPCP_UNICAST: + s.unicast = uni_config_msg_to_s(msg->unicast); + break; + case IPCP_ETH_LLC: + /* FALLTHRU */ + case IPCP_ETH_DIX: + s.eth = eth_config_msg_to_s(msg->eth); + break; + case IPCP_UDP4: + s.udp4 = udp4_config_msg_to_s(msg->udp4); + break; + case IPCP_UDP6: + s.udp6 = udp6_config_msg_to_s(msg->udp6); + break; + case IPCP_BROADCAST: + break; + default: + /* No checks here */ + break; + } + + return s; +} + +qosspec_msg_t * qos_spec_s_to_msg(const struct qos_spec * s) +{ + qosspec_msg_t * msg; + + assert(s != NULL); + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + return NULL; + + qosspec_msg__init(msg); + + msg->delay = s->delay; + msg->bandwidth = s->bandwidth; + msg->availability = s->availability; + msg->loss = s->loss; + msg->ber = s->ber; + msg->in_order = s->in_order; + msg->max_gap = s->max_gap; + msg->timeout = s->timeout; + + return msg; +} + +struct qos_spec qos_spec_msg_to_s(const qosspec_msg_t * msg) +{ + struct qos_spec s; + + assert(msg != NULL); + + s.delay = msg->delay; + s.bandwidth = msg->bandwidth; + s.availability = msg->availability; + s.loss = msg->loss; + s.ber = msg->ber; + s.in_order = msg->in_order; + s.max_gap = msg->max_gap; + s.timeout = msg->timeout; + + return s; +} + +enroll_req_msg_t * enroll_req_s_to_msg(const struct enroll_req * s) +{ + enroll_req_msg_t * msg; + uint8_t * id; + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + goto fail_msg; + + id = malloc(ENROLL_ID_LEN); + if (id == NULL) + goto fail_id; + + memcpy(id, s->id, ENROLL_ID_LEN); + + enroll_req_msg__init(msg); + + msg->id.len = ENROLL_ID_LEN; + msg->id.data = id; + + return msg; + + fail_id: + free(msg); + fail_msg: + return NULL; +} + +struct enroll_req enroll_req_msg_to_s(const enroll_req_msg_t * msg) +{ + struct enroll_req s; + + assert(msg != NULL); + + memcpy(s.id, msg->id.data, ENROLL_ID_LEN); + + return s; +} + +enroll_resp_msg_t * enroll_resp_s_to_msg(const struct enroll_resp * s) +{ + enroll_resp_msg_t * msg; + uint8_t * id; + + assert(s != NULL); + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + goto fail_msg; + + id = malloc(ENROLL_ID_LEN); + if (id == NULL) + goto fail_id; + + memcpy(id, s->id, ENROLL_ID_LEN); + + enroll_resp_msg__init(msg); + + msg->id.len = ENROLL_ID_LEN; + msg->id.data = id; + + msg->t_sec = s->t.tv_sec; + msg->t_nsec = s->t.tv_nsec; + msg->response = s->response; + if (msg->response < 0) + return msg; + + msg->conf = ipcp_config_s_to_msg(&s->conf); + if (msg->conf == NULL) + goto fail_id; + + return msg; + + fail_id: + enroll_resp_msg__free_unpacked(msg, NULL); + fail_msg: + return NULL; +} + +struct enroll_resp enroll_resp_msg_to_s(const enroll_resp_msg_t * msg) +{ + struct enroll_resp s; + + assert (msg != NULL); + + s.response = msg->response; + if (s.response >= 0) + s.conf = ipcp_config_msg_to_s(msg->conf); + + s.t.tv_sec = msg->t_sec; + s.t.tv_nsec = msg->t_nsec; + + memcpy(s.id, msg->id.data, ENROLL_ID_LEN); + + return s; +} + +enroll_ack_msg_t * enroll_ack_s_to_msg(const struct enroll_ack * s) +{ + enroll_ack_msg_t * msg; + uint8_t * id; + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + goto fail_msg; + + id = malloc(ENROLL_ID_LEN); + if (id == NULL) + goto fail_id; + + memcpy(id, s->id, ENROLL_ID_LEN); + + enroll_ack_msg__init(msg); + + msg->id.len = ENROLL_ID_LEN; + msg->id.data = id; + + msg->result = s->result; + + return msg; + + fail_id: + enroll_ack_msg__free_unpacked(msg, NULL); + fail_msg: + return NULL; +} + +struct enroll_ack enroll_ack_msg_to_s(const enroll_ack_msg_t * msg) +{ + struct enroll_ack s; + + assert(msg != NULL); + + memcpy(s.id, msg->id.data, ENROLL_ID_LEN); + + s.result = msg->result; + + return s; +} diff --git a/src/lib/qoscube.c b/src/lib/qoscube.c index b9482263..267b3a87 100644 --- a/src/lib/qoscube.c +++ b/src/lib/qoscube.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Quality of Service cube * diff --git a/src/lib/random.c b/src/lib/random.c index ee992fda..2dc5f02f 100644 --- a/src/lib/random.c +++ b/src/lib/random.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Pseudo random generator * @@ -24,22 +24,12 @@ #include <ouroboros/random.h> -#if defined(__APPLE__) /* Barf */ -#undef __OSX_AVAILABLE -#define __OSX_AVAILABLE(arg) -#undef __IOS_AVAILABLE -#define __IOS_AVAILABLE(arg) -#undef __TVOS_AVAILABLE -#define __TVOS_AVAILABLE(arg) -#undef __WATCHOS_AVAILABLE -#define __WATCHOS_AVAILABLE(arg) -#include <sys/random.h> +#if defined(__APPLE__) || defined(__FreeBSD__) +#include <stdlib.h> #elif defined(HAVE_SYS_RANDOM) #include <sys/random.h> #elif defined(HAVE_LIBGCRYPT) #include <gcrypt.h> -#elif defined(__FreeBSD__) -#include <stdlib.h> #elif defined(HAVE_OPENSSL_RNG) #include <openssl/rand.h> #include <limits.h> @@ -48,13 +38,11 @@ int random_buffer(void * buf, size_t len) { -#if defined(__APPLE__) - return getentropy(buf, len); -#elif defined(__FreeBSD__) +#if defined(__APPLE__) || defined(__FreeBSD__) arc4random_buf(buf, len); return 0; #elif defined(HAVE_SYS_RANDOM) - return getrandom(buf, len, GRND_NONBLOCK); /* glibc 2.25 */ + return getrandom(buf, len, GRND_NONBLOCK); #elif defined(HAVE_LIBGCRYPT) gcry_randomize(buf, len, GCRY_STRONG_RANDOM); return 0; diff --git a/src/lib/rib.c b/src/lib/rib.c index 27c66f2f..97a20f47 100644 --- a/src/lib/rib.c +++ b/src/lib/rib.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * RIB export using FUSE * @@ -139,6 +139,10 @@ static int rib_readdir(const char * path, (void) offset; (void) info; + /* Fix ls calling readdir in an infinite loop on raspbian. */ + if (info != NULL && info->nonseekable != 0) + return -ENOENT; + filler(buf, ".", NULL, 0); filler(buf, "..", NULL, 0); @@ -203,12 +207,15 @@ static size_t __getattr(const char * path, if (strcmp(comp, r->path) == 0) { size_t ret = r->ops->getattr(path + 1, &attr); pthread_rwlock_unlock(&rib.lock); - st->st_mode = S_IFREG | 0755; - st->st_nlink = 1; - st->st_uid = getuid(); - st->st_gid = getgid(); - st->st_size = attr.size; - st->st_mtime = attr.mtime; + st->st_mode = S_IFREG | 0644; + st->st_blocks = 1; + st->st_nlink = 1; + st->st_uid = getuid(); + st->st_gid = getgid(); + st->st_size = attr.size; + st->st_atime = attr.mtime; + st->st_mtime = attr.mtime; + st->st_ctime = attr.mtime; return ret; } } @@ -252,6 +259,8 @@ static int rib_getattr(const char * path, st->st_uid = getuid(); st->st_gid = getgid(); st->st_mtime = now.tv_sec; + st->st_atime = now.tv_sec; + st->st_ctime = now.tv_sec; return 0; } @@ -338,7 +347,7 @@ int rib_init(const char * mountpt) fuse_opt_free_args(&args); rmdir(rib.mnt); fail_mnt: - memset(rib.mnt, 0, RIB_PATH_LEN + 1); + memset(rib.mnt, 0, sizeof(rib.mnt)); fail: return -1; #else @@ -377,6 +386,18 @@ void rib_fini(void) pthread_rwlock_unlock(&rib.lock); pthread_rwlock_destroy(&rib.lock); + + memset(rib.mnt, 0, sizeof(rib.mnt)); +#endif +} + +void rib_cleanup(const char * mnt) +{ +#ifdef HAVE_FUSE + fuse_unmount(mnt, NULL); + rmdir(mnt); +#else + (void) mnt; #endif } diff --git a/src/lib/serdes-irm.c b/src/lib/serdes-irm.c new file mode 100644 index 00000000..8cbe20b1 --- /dev/null +++ b/src/lib/serdes-irm.c @@ -0,0 +1,473 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * Ouroboros IRM Protocol - serialization/deserialization + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#define _POSIX_C_SOURCE 200809L + +#include "config.h" + +#include <ouroboros/crypt.h> +#include <ouroboros/errno.h> +#include <ouroboros/serdes-irm.h> +#include <ouroboros/protobuf.h> + +#include <stdlib.h> +#include <string.h> + +int flow_accept__irm_req_ser(buffer_t * buf, + const struct flow_info * flow, + const struct timespec * timeo) +{ + irm_msg_t * msg; + size_t len; + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + goto fail_malloc; + + irm_msg__init(msg); + + msg->code = IRM_MSG_CODE__IRM_FLOW_ACCEPT; + msg->flow_info = flow_info_s_to_msg(flow); + if (msg->flow_info == NULL) + goto fail_msg; + + msg->timeo = timeo == NULL ? NULL : timespec_s_to_msg(timeo); + if (timeo != NULL && msg->timeo == NULL) + goto fail_msg; + + len = irm_msg__get_packed_size(msg); + if (len == 0 || len > buf->len) + goto fail_msg; + + buf->len = len; + + irm_msg__pack(msg, buf->data); + irm_msg__free_unpacked(msg, NULL); + + return 0; + + fail_msg: + irm_msg__free_unpacked(msg, NULL); + fail_malloc: + return -ENOMEM; +} + +static int __flow_alloc_ser(buffer_t * buf, + const struct flow_info * flow, + const char * dst, + const struct timespec * timeo, + int msg_code) +{ + irm_msg_t * msg; + size_t len; + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + goto fail_malloc; + + irm_msg__init(msg); + + msg->code = msg_code; + msg->flow_info = flow_info_s_to_msg(flow); + if (msg->flow_info == NULL) + goto fail_msg; + + msg->dst = strdup(dst); + if (msg->dst == NULL) + goto fail_msg; + + msg->timeo = timeo == NULL ? NULL : timespec_s_to_msg(timeo); + if (timeo != NULL && msg->timeo == NULL) + goto fail_msg; + + len = irm_msg__get_packed_size(msg); + if (len == 0 || len > buf->len) + goto fail_msg; + + buf->len = len; + + irm_msg__pack(msg, buf->data); + irm_msg__free_unpacked(msg, NULL); + + return 0; + + fail_msg: + irm_msg__free_unpacked(msg, NULL); + fail_malloc: + return -ENOMEM; +} + +int flow_alloc__irm_req_ser(buffer_t * buf, + const struct flow_info * flow, + const char * dst, + const struct timespec * timeo) +{ + return __flow_alloc_ser(buf, flow, dst, timeo, + IRM_MSG_CODE__IRM_FLOW_ALLOC); +} + +int flow_join__irm_req_ser(buffer_t * buf, + const struct flow_info * flow, + const char * dst, + const struct timespec * timeo) +{ + return __flow_alloc_ser(buf, flow, dst, timeo, + IRM_MSG_CODE__IRM_FLOW_JOIN); +} + +int flow__irm_result_des(buffer_t * buf, + struct flow_info * flow, + struct crypt_sk * sk) +{ + irm_msg_t * msg; + int err; + + msg = irm_msg__unpack(NULL, buf->len, buf->data); + if (msg == NULL) { + err = -EIRMD; + goto fail_msg; + } + + if (!msg->has_result) { + err = -EIRMD; + goto fail; + } + + if (msg->result < 0) { + err = msg->result; + goto fail; + } + + if (msg->flow_info == NULL) { + err = -EBADF; + goto fail; + } + + *flow = flow_info_msg_to_s(msg->flow_info); + + if (msg->has_cipher_nid) + sk->nid = msg->cipher_nid; + else + sk->nid = NID_undef; + + if (msg->sym_key.len == SYMMKEYSZ) + memcpy(sk->key, msg->sym_key.data, SYMMKEYSZ); + else + memset(sk->key, 0, SYMMKEYSZ); + + irm_msg__free_unpacked(msg, NULL); + + return 0; + fail: + irm_msg__free_unpacked(msg, NULL); + fail_msg: + return err; +} + +int flow_dealloc__irm_req_ser(buffer_t * buf, + const struct flow_info * flow, + const struct timespec * timeo) +{ + irm_msg_t * msg; + size_t len; + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + goto fail_malloc; + + irm_msg__init(msg); + + msg->code = IRM_MSG_CODE__IRM_FLOW_DEALLOC; + msg->flow_info = flow_info_s_to_msg(flow); + if (msg->flow_info == NULL) + goto fail_msg; + + msg->timeo = timespec_s_to_msg(timeo); + if (msg->timeo == NULL) + goto fail_msg; + + len = irm_msg__get_packed_size(msg); + if (len == 0 || len > buf->len) + goto fail_msg; + + buf->len = len; + + irm_msg__pack(msg, buf->data); + irm_msg__free_unpacked(msg, NULL); + + return 0; + + fail_msg: + irm_msg__free_unpacked(msg, NULL); + fail_malloc: + return -ENOMEM; +} + +int ipcp_flow_dealloc__irm_req_ser(buffer_t * buf, + const struct flow_info * flow) +{ + irm_msg_t * msg; + size_t len; + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + goto fail_malloc; + + irm_msg__init(msg); + + msg->code = IRM_MSG_CODE__IPCP_FLOW_DEALLOC; + msg->flow_info = flow_info_s_to_msg(flow); + if (msg->flow_info == NULL) + goto fail_msg; + + len = irm_msg__get_packed_size(msg); + if (len == 0 || len > buf->len) + goto fail_msg; + + buf->len = len; + + irm_msg__pack(msg, buf->data); + irm_msg__free_unpacked(msg, NULL); + + return 0; + fail_msg: + irm_msg__free_unpacked(msg, NULL); + fail_malloc: + return -ENOMEM; +} + + +int ipcp_create_r__irm_req_ser(buffer_t * buf, + const struct ipcp_info * ipcp) +{ + irm_msg_t * msg; + size_t len; + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + goto fail_malloc; + + irm_msg__init(msg); + + msg->code = IRM_MSG_CODE__IPCP_CREATE_R; + msg->ipcp_info = ipcp_info_s_to_msg(ipcp); + if (msg->ipcp_info == NULL) + goto fail_msg; + + len = irm_msg__get_packed_size(msg); + if (len == 0 || len > buf->len) + goto fail_msg; + + buf->len = len; + + irm_msg__pack(msg, buf->data); + irm_msg__free_unpacked(msg, NULL); + + return 0; + fail_msg: + irm_msg__free_unpacked(msg, NULL); + fail_malloc: + return -ENOMEM; +} + +int proc_announce__irm_req_ser(buffer_t * buf, + const struct proc_info * proc) +{ + irm_msg_t * msg; + size_t len; + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + goto fail_malloc; + + irm_msg__init(msg); + + msg->code = IRM_MSG_CODE__IRM_PROC_ANNOUNCE; + msg->has_pid = true; + msg->pid = proc->pid; + msg->prog = strdup(proc->prog); + if (msg->prog == NULL) + goto fail_msg; + + len = irm_msg__get_packed_size(msg); + if (len == 0 || len > buf->len) + goto fail_msg; + + buf->len = len; + + irm_msg__pack(msg, buf->data); + irm_msg__free_unpacked(msg, NULL); + + return 0; + fail_msg: + irm_msg__free_unpacked(msg, NULL); + fail_malloc: + return -ENOMEM; +} + +int proc_exit__irm_req_ser(buffer_t * buf) +{ + irm_msg_t * msg; + size_t len; + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + goto fail_malloc; + + irm_msg__init(msg); + + msg->code = IRM_MSG_CODE__IRM_PROC_EXIT; + msg->has_pid = true; + msg->pid = getpid(); + + len = irm_msg__get_packed_size(msg); + if (len == 0 || len > buf->len) + goto fail_msg; + + buf->len = len; + + irm_msg__pack(msg, buf->data); + irm_msg__free_unpacked(msg, NULL); + + return 0; + fail_msg: + irm_msg__free_unpacked(msg, NULL); + fail_malloc: + return -ENOMEM; +} + +int ipcp_flow_req_arr__irm_req_ser(buffer_t * buf, + const buffer_t * dst, + const struct flow_info * flow, + const buffer_t * data) +{ + irm_msg_t * msg; + size_t len; + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + goto fail_malloc; + + irm_msg__init(msg); + + msg->code = IRM_MSG_CODE__IPCP_FLOW_REQ_ARR; + msg->flow_info = flow_info_s_to_msg(flow); + if (msg->flow_info == NULL) + goto fail_msg; + + msg->has_hash = true; + msg->hash.len = dst->len; + msg->hash.data = dst->data; + msg->has_pk = true; + msg->pk.len = data->len; + msg->pk.data = data->data; + + len = irm_msg__get_packed_size(msg); + if (len == 0 || len > buf->len) + goto fail_msg; + + buf->len = len; + + irm_msg__pack(msg, buf->data); + + /* Don't free * dst or data! */ + msg->hash.len = 0; + msg->hash.data = NULL; + msg->pk.len = 0; + msg->pk.data = NULL; + irm_msg__free_unpacked(msg, NULL); + + return 0; + fail_msg: + irm_msg__free_unpacked(msg, NULL); + fail_malloc: + return -ENOMEM; +} + +int ipcp_flow_alloc_reply__irm_msg_ser(buffer_t * buf, + const struct flow_info * flow, + int response, + const buffer_t * data) +{ + irm_msg_t * msg; + size_t len; + + msg = malloc(sizeof(*msg)); + if (msg == NULL) + goto fail_malloc; + + irm_msg__init(msg); + + msg->code = IRM_MSG_CODE__IPCP_FLOW_ALLOC_REPLY; + msg->flow_info = flow_info_s_to_msg(flow); + if (msg->flow_info == NULL) + goto fail_msg; + + msg->has_pk = true; + msg->pk.len = data->len; + msg->pk.data = data->data; + msg->has_response = true; + msg->response = response; + + len = irm_msg__get_packed_size(msg); + if (len == 0 || len > buf->len) + goto fail_msg; + + buf->len = len; + + irm_msg__pack(msg, buf->data); + + /* Don't free * data! */ + msg->pk.len = 0; + msg->pk.data = NULL; + + irm_msg__free_unpacked(msg, NULL); + + return 0; + fail_msg: + irm_msg__free_unpacked(msg, NULL); + fail_malloc: + return -ENOMEM; +} + +int irm__irm_result_des(buffer_t * buf) +{ + irm_msg_t * msg; + int err; + + msg = irm_msg__unpack(NULL, buf->len, buf->data); + if (msg == NULL) { + err = -EIRMD; + goto fail_msg; + } + + if (!msg->has_result) { + err = -EIRMD; + goto fail; + } + + err = msg->result; + fail: + irm_msg__free_unpacked(msg, NULL); + fail_msg: + return err; +} diff --git a/src/lib/serdes-oep.c b/src/lib/serdes-oep.c new file mode 100644 index 00000000..8a836b3b --- /dev/null +++ b/src/lib/serdes-oep.c @@ -0,0 +1,161 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * Ouroboros Enrollment Protocol - serialization/deserialization + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#define _POSIX_C_SOURCE 200112L + +#include <ouroboros/protobuf.h> +#include <ouroboros/serdes-oep.h> + +ssize_t enroll_req_ser(const struct enroll_req * req, + buffer_t buf) +{ + enroll_req_msg_t * msg; + ssize_t sz; + + msg = enroll_req_s_to_msg(req); + if (msg == NULL) + goto fail_msg; + + sz = enroll_req_msg__get_packed_size(msg); + if (sz < 0 || (size_t) sz > buf.len) + goto fail_pack; + + enroll_req_msg__pack(msg, buf.data); + + enroll_req_msg__free_unpacked(msg, NULL); + + return sz; + + fail_pack: + enroll_req_msg__free_unpacked(msg, NULL); + fail_msg: + return -1; +} + +int enroll_req_des(struct enroll_req * req, + const buffer_t buf) +{ + enroll_req_msg_t * msg; + + msg = enroll_req_msg__unpack(NULL, buf.len, buf.data); + if (msg == NULL) + goto fail_unpack; + + if (msg->id.len != ENROLL_ID_LEN) + goto fail_id; + + *req = enroll_req_msg_to_s(msg); + + enroll_req_msg__free_unpacked(msg, NULL); + + return 0; + + fail_id: + enroll_req_msg__free_unpacked(msg, NULL); + fail_unpack: + return -1; +} + +ssize_t enroll_resp_ser(const struct enroll_resp * resp, + buffer_t buf) +{ + enroll_resp_msg_t * msg; + ssize_t sz; + + msg = enroll_resp_s_to_msg(resp); + if (msg == NULL) + goto fail_msg; + + sz = enroll_resp_msg__get_packed_size(msg); + if (sz < 0 || (size_t) sz > buf.len) + goto fail_pack; + + enroll_resp_msg__pack(msg, buf.data); + + enroll_resp_msg__free_unpacked(msg, NULL); + + return sz; + + fail_pack: + enroll_resp_msg__free_unpacked(msg, NULL); + fail_msg: + return -1; +} + +int enroll_resp_des(struct enroll_resp * resp, + const buffer_t buf) +{ + enroll_resp_msg_t * msg; + + msg = enroll_resp_msg__unpack(NULL, buf.len, buf.data); + if (msg == NULL) + return -1; + + *resp = enroll_resp_msg_to_s(msg); + + enroll_resp_msg__free_unpacked(msg, NULL); + + return 0; +} + +ssize_t enroll_ack_ser(const struct enroll_ack * ack, + buffer_t buf) +{ + enroll_ack_msg_t * msg; + ssize_t sz; + + msg = enroll_ack_s_to_msg(ack); + if (msg == NULL) + goto fail_msg; + + sz = enroll_ack_msg__get_packed_size(msg); + if (sz < 0 || (size_t) sz > buf.len) + goto fail_pack; + + enroll_ack_msg__pack(msg, buf.data); + + enroll_ack_msg__free_unpacked(msg, NULL); + + return sz; + + fail_pack: + enroll_ack_msg__free_unpacked(msg, NULL); + fail_msg: + return -1; + +} + +int enroll_ack_des(struct enroll_ack * ack, + const buffer_t buf) +{ + enroll_ack_msg_t * msg; + + msg = enroll_ack_msg__unpack(NULL, buf.len, buf.data); + if (msg == NULL) + return -1; + + *ack = enroll_ack_msg_to_s(msg); + + enroll_ack_msg__free_unpacked(msg, NULL); + + return 0; +} diff --git a/src/lib/sha3.c b/src/lib/sha3.c index 76ddbae6..b9d6b07f 100644 --- a/src/lib/sha3.c +++ b/src/lib/sha3.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * SHA3 algorithm * diff --git a/src/lib/shm_rbuff.c b/src/lib/shm_rbuff.c deleted file mode 100644 index 361d5bb0..00000000 --- a/src/lib/shm_rbuff.c +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2021 - * - * Ring buffer implementations for incoming packets - * - * Dimitri Staessens <dimitri@ouroboros.rocks> - * Sander Vrijders <sander@ouroboros.rocks> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * version 2.1 as published by the Free Software Foundation. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., http://www.fsf.org/about/contact/. - */ - -#define _POSIX_C_SOURCE 200809L - -#include "config.h" - -#include <ouroboros/shm_rbuff.h> -#include <ouroboros/lockfile.h> -#include <ouroboros/time_utils.h> -#include <ouroboros/errno.h> -#include <ouroboros/fccntl.h> -#include <ouroboros/pthread.h> - -#include <sys/mman.h> -#include <fcntl.h> -#include <stdlib.h> -#include <string.h> -#include <stdio.h> -#include <stdint.h> -#include <unistd.h> -#include <signal.h> -#include <sys/stat.h> -#include <assert.h> -#include <stdbool.h> - -#define FN_MAX_CHARS 255 - -#define SHM_RB_FILE_SIZE ((SHM_RBUFF_SIZE) * sizeof(ssize_t) \ - + 3 * sizeof(size_t) \ - + sizeof(pthread_mutex_t) \ - + 2 * sizeof (pthread_cond_t)) - -#define shm_rbuff_used(rb) ((*rb->head + (SHM_RBUFF_SIZE) - *rb->tail) \ - & ((SHM_RBUFF_SIZE) - 1)) -#define shm_rbuff_free(rb) (shm_rbuff_used(rb) + 1 < (SHM_RBUFF_SIZE)) -#define shm_rbuff_empty(rb) (*rb->head == *rb->tail) -#define head_el_ptr(rb) (rb->shm_base + *rb->head) -#define tail_el_ptr(rb) (rb->shm_base + *rb->tail) - -struct shm_rbuff { - ssize_t * shm_base; /* start of entry */ - size_t * head; /* start of ringbuffer head */ - size_t * tail; /* start of ringbuffer tail */ - size_t * acl; /* access control */ - pthread_mutex_t * lock; /* lock all free space in shm */ - pthread_cond_t * add; /* packet arrived */ - pthread_cond_t * del; /* packet removed */ - pid_t pid; /* pid of the owner */ - int flow_id; /* flow_id of the flow */ -}; - -void shm_rbuff_close(struct shm_rbuff * rb) -{ - assert(rb); - - munmap(rb->shm_base, SHM_RB_FILE_SIZE); - - free(rb); -} - -#define MM_FLAGS (PROT_READ | PROT_WRITE) - -struct shm_rbuff * rbuff_create(pid_t pid, - int flow_id, - int flags) -{ - struct shm_rbuff * rb; - int fd; - ssize_t * shm_base; - char fn[FN_MAX_CHARS]; - - sprintf(fn, SHM_RBUFF_PREFIX "%d.%d", pid, flow_id); - - rb = malloc(sizeof(*rb)); - if (rb == NULL) - goto fail_malloc; - - fd = shm_open(fn, flags, 0666); - if (fd == -1) - goto fail_open; - - if ((flags & O_CREAT) && ftruncate(fd, SHM_RB_FILE_SIZE - 1) < 0) - goto fail_truncate; - - shm_base = mmap(NULL, SHM_RB_FILE_SIZE, MM_FLAGS, MAP_SHARED, fd, 0); - if (shm_base == MAP_FAILED) - goto fail_truncate; - - close(fd); - - rb->shm_base = shm_base; - rb->head = (size_t *) (rb->shm_base + (SHM_RBUFF_SIZE)); - rb->tail = rb->head + 1; - rb->acl = rb->tail + 1; - rb->lock = (pthread_mutex_t *) (rb->acl + 1); - rb->add = (pthread_cond_t *) (rb->lock + 1); - rb->del = rb->add + 1; - rb->pid = pid; - rb->flow_id = flow_id; - - return rb; - - fail_truncate: - close(fd); - if (flags & O_CREAT) - shm_unlink(fn); - fail_open: - free(rb); - fail_malloc: - return NULL; -} - -struct shm_rbuff * shm_rbuff_create(pid_t pid, - int flow_id) -{ - struct shm_rbuff * rb; - pthread_mutexattr_t mattr; - pthread_condattr_t cattr; - mode_t mask; - - mask = umask(0); - - rb = rbuff_create(pid, flow_id, O_CREAT | O_EXCL | O_RDWR); - - umask(mask); - - if (rb == NULL) - goto fail_rb; - - if (pthread_mutexattr_init(&mattr)) - goto fail_mattr; - - pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED); -#ifdef HAVE_ROBUST_MUTEX - pthread_mutexattr_setrobust(&mattr, PTHREAD_MUTEX_ROBUST); -#endif - if (pthread_mutex_init(rb->lock, &mattr)) - goto fail_mutex; - - if (pthread_condattr_init(&cattr)) - goto fail_cattr; - - pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED); -#ifndef __APPLE__ - pthread_condattr_setclock(&cattr, PTHREAD_COND_CLOCK); -#endif - if (pthread_cond_init(rb->add, &cattr)) - goto fail_add; - - if (pthread_cond_init(rb->del, &cattr)) - goto fail_del; - - *rb->acl = ACL_RDWR; - *rb->head = 0; - *rb->tail = 0; - - rb->pid = pid; - rb->flow_id = flow_id; - - pthread_mutexattr_destroy(&mattr); - pthread_condattr_destroy(&cattr); - - return rb; - - fail_del: - pthread_cond_destroy(rb->add); - fail_add: - pthread_condattr_destroy(&cattr); - fail_cattr: - pthread_mutex_destroy(rb->lock); - fail_mutex: - pthread_mutexattr_destroy(&mattr); - fail_mattr: - shm_rbuff_destroy(rb); - fail_rb: - return NULL; -} - -struct shm_rbuff * shm_rbuff_open(pid_t pid, - int flow_id) -{ - return rbuff_create(pid, flow_id, O_RDWR); -} - -#if (defined(SHM_RBUFF_LOCKLESS) && \ - (defined(__GNUC__) || defined (__clang__))) -#include "shm_rbuff_ll.c" -#else -#include "shm_rbuff_pthr.c" -#endif diff --git a/src/lib/shm_rbuff_ll.c b/src/lib/shm_rbuff_ll.c deleted file mode 100644 index eef8a2fb..00000000 --- a/src/lib/shm_rbuff_ll.c +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2021 - * - * Lockless ring buffer for incoming packets - * - * Dimitri Staessens <dimitri@ouroboros.rocks> - * Sander Vrijders <sander@ouroboros.rocks> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * version 2.1 as published by the Free Software Foundation. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., http://www.fsf.org/about/contact/. - */ - -#define RB_HEAD __sync_fetch_and_add(rb->head, 0) -#define RB_TAIL __sync_fetch_and_add(rb->tail, 0) - -void shm_rbuff_destroy(struct shm_rbuff * rb) -{ - char fn[FN_MAX_CHARS]; - - assert(rb); - - sprintf(fn, SHM_RBUFF_PREFIX "%d.%d", rb->pid, rb->flow_id); - - shm_rbuff_close(rb); - - shm_unlink(fn); -} - -int shm_rbuff_write(struct shm_rbuff * rb, - size_t idx) -{ - size_t ohead; - size_t nhead; - bool was_empty = false; - - assert(rb); - assert(idx < SHM_BUFFER_SIZE); - - if (__sync_fetch_and_add(rb->acl, 0) != ACL_RDWR) { - if (__sync_fetch_and_add(rb->acl, 0) & ACL_FLOWDOWN) - return -EFLOWDOWN; - else if (__sync_fetch_and_add(rb->acl, 0) & ACL_RDONLY) - return -ENOTALLOC; - } - - if (!shm_rbuff_free(rb)) - return -EAGAIN; - - if (shm_rbuff_empty(rb)) - was_empty = true; - - nhead = RB_HEAD; - - *(rb->shm_base + nhead) = (ssize_t) idx; - - do { - ohead = nhead; - nhead = (ohead + 1) & ((SHM_RBUFF_SIZE) - 1); - nhead = __sync_val_compare_and_swap(rb->head, ohead, nhead); - } while (nhead != ohead); - - if (was_empty) - pthread_cond_broadcast(rb->add); - - return 0; -} - -/* FIXME: this is a copy of the pthr implementation */ -int shm_rbuff_write_b(struct shm_rbuff * rb, - size_t idx, - const struct timespec * abstime) -{ - int ret = 0; - - assert(rb); - assert(idx < SHM_BUFFER_SIZE); - -#ifndef HAVE_ROBUST_MUTEX - pthread_mutex_lock(rb->lock); -#else - if (pthread_mutex_lock(rb->lock) == EOWNERDEAD) - pthread_mutex_consistent(rb->lock); -#endif - - if (*rb->acl != ACL_RDWR) { - if (*rb->acl & ACL_FLOWDOWN) - ret = -EFLOWDOWN; - else if (*rb->acl & ACL_RDONLY) - ret = -ENOTALLOC; - goto err; - } - - pthread_cleanup_push(__cleanup_mutex_unlock, rb->lock); - - while (!shm_rbuff_free(rb) && ret != -ETIMEDOUT) { - if (abstime != NULL) - ret = -pthread_cond_timedwait(rb->add, - rb->lock, - abstime); - else - ret = -pthread_cond_wait(rb->add, rb->lock); -#ifdef HAVE_ROBUST_MUTEX - if (ret == -EOWNERDEAD) - pthread_mutex_consistent(rb->lock); -#endif - } - - if (shm_rbuff_empty(rb)) - pthread_cond_broadcast(rb->add); - - if (ret != -ETIMEDOUT) { - *head_el_ptr(rb) = (ssize_t) idx; - *rb->head = (*rb->head + 1) & ((SHM_RBUFF_SIZE) -1); - } - - pthread_cleanup_pop(true); - - return ret; - err: - pthread_mutex_unlock(rb->lock); - return ret; -} - -ssize_t shm_rbuff_read(struct shm_rbuff * rb) -{ - size_t otail; - size_t ntail; - - assert(rb); - - if (shm_rbuff_empty(rb)) - return __sync_fetch_and_add(rb->acl, 0) & ACL_FLOWDOWN ? - -EFLOWDOWN : -EAGAIN; - - ntail = RB_TAIL; - - do { - otail = ntail; - ntail = (otail + 1) & ((SHM_RBUFF_SIZE) - 1); - ntail = __sync_val_compare_and_swap(rb->tail, otail, ntail); - } while (ntail != otail); - - pthread_cond_broadcast(rb->del); - - return *(rb->shm_base + ntail); -} - -ssize_t shm_rbuff_read_b(struct shm_rbuff * rb, - const struct timespec * abstime) -{ - ssize_t idx = -1; - - assert(rb); - - /* try a non-blocking read first */ - idx = shm_rbuff_read(rb); - if (idx != -EAGAIN) - return idx; - -#ifndef HAVE_ROBUST_MUTEX - pthread_mutex_lock(rb->lock); -#else - if (pthread_mutex_lock(rb->lock) == EOWNERDEAD) - pthread_mutex_consistent(rb->lock); -#endif - pthread_cleanup_push(__cleanup_mutex_unlock, rb->lock); - - while (shm_rbuff_empty(rb) && (idx != -ETIMEDOUT)) { - if (abstime != NULL) - idx = -pthread_cond_timedwait(rb->add, - rb->lock, - abstime); - else - idx = -pthread_cond_wait(rb->add, rb->lock); -#ifdef HAVE_ROBUST_MUTEX - if (idx == -EOWNERDEAD) - pthread_mutex_consistent(rb->lock); -#endif - } - - if (idx != -ETIMEDOUT) { - /* do a nonblocking read */ - idx = shm_rbuff_read(rb); - assert(idx >= 0); - } - - pthread_cleanup_pop(true); - - return idx; -} - -void shm_rbuff_set_acl(struct shm_rbuff * rb, - uint32_t flags) -{ - assert(rb); - - __sync_bool_compare_and_swap(rb->acl, *rb->acl, flags); -} - -uint32_t shm_rbuff_get_acl(struct shm_rbuff * rb) -{ - assert(rb); - - return __sync_fetch_and_add(rb->acl, 0); -} - -void shm_rbuff_fini(struct shm_rbuff * rb) -{ - assert(rb); - - if (shm_rbuff_empty(rb)) - return; - -#ifndef HAVE_ROBUST_MUTEX - pthread_mutex_lock(rb->lock); -#else - if (pthread_mutex_lock(rb->lock) == EOWNERDEAD) - pthread_mutex_consistent(rb->lock); -#endif - - pthread_cleanup_push(__cleanup_mutex_unlock, rb->lock); - - while (!shm_rbuff_empty(rb)) -#ifndef HAVE_ROBUST_MUTEX - pthread_cond_wait(rb->del, rb->lock); -#else - if (pthread_cond_wait(rb->del, rb->lock) == EOWNERDEAD) - pthread_mutex_consistent(rb->lock); -#endif - pthread_cleanup_pop(true); -} - -size_t shm_rbuff_queued(struct shm_rbuff * rb) -{ - assert(rb); - - return shm_rbuff_used(rb); -} diff --git a/src/lib/shm_rbuff_pthr.c b/src/lib/shm_rbuff_pthr.c deleted file mode 100644 index cedbc7b1..00000000 --- a/src/lib/shm_rbuff_pthr.c +++ /dev/null @@ -1,294 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2021 - * - * Ring buffer for incoming packets - * - * Dimitri Staessens <dimitri@ouroboros.rocks> - * Sander Vrijders <sander@ouroboros.rocks> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * version 2.1 as published by the Free Software Foundation. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., http://www.fsf.org/about/contact/. - */ - -void shm_rbuff_destroy(struct shm_rbuff * rb) -{ - char fn[FN_MAX_CHARS]; - - assert(rb); - -#ifdef CONFIG_OUROBOROS_DEBUG - pthread_mutex_lock(rb->lock); - - assert(shm_rbuff_empty(rb)); - - pthread_mutex_unlock(rb->lock); -#endif - sprintf(fn, SHM_RBUFF_PREFIX "%d.%d", rb->pid, rb->flow_id); - - shm_rbuff_close(rb); - - shm_unlink(fn); -} - -int shm_rbuff_write(struct shm_rbuff * rb, - size_t idx) -{ - int ret = 0; - - assert(rb); - assert(idx < SHM_BUFFER_SIZE); - -#ifndef HAVE_ROBUST_MUTEX - pthread_mutex_lock(rb->lock); -#else - if (pthread_mutex_lock(rb->lock) == EOWNERDEAD) - pthread_mutex_consistent(rb->lock); -#endif - - if (*rb->acl != ACL_RDWR) { - if (*rb->acl & ACL_FLOWDOWN) - ret = -EFLOWDOWN; - else if (*rb->acl & ACL_RDONLY) - ret = -ENOTALLOC; - goto err; - } - - if (!shm_rbuff_free(rb)) { - ret = -EAGAIN; - goto err; - } - - if (shm_rbuff_empty(rb)) - pthread_cond_broadcast(rb->add); - - *head_el_ptr(rb) = (ssize_t) idx; - *rb->head = (*rb->head + 1) & ((SHM_RBUFF_SIZE) - 1); - - pthread_mutex_unlock(rb->lock); - - return 0; - err: - pthread_mutex_unlock(rb->lock); - return ret; -} - -int shm_rbuff_write_b(struct shm_rbuff * rb, - size_t idx, - const struct timespec * abstime) -{ - int ret = 0; - - assert(rb); - assert(idx < SHM_BUFFER_SIZE); - -#ifndef HAVE_ROBUST_MUTEX - pthread_mutex_lock(rb->lock); -#else - if (pthread_mutex_lock(rb->lock) == EOWNERDEAD) - pthread_mutex_consistent(rb->lock); -#endif - - if (*rb->acl != ACL_RDWR) { - if (*rb->acl & ACL_FLOWDOWN) - ret = -EFLOWDOWN; - else if (*rb->acl & ACL_RDONLY) - ret = -ENOTALLOC; - goto err; - } - - pthread_cleanup_push(__cleanup_mutex_unlock, rb->lock); - - while (!shm_rbuff_free(rb) - && ret != -ETIMEDOUT - && !(*rb->acl & ACL_FLOWDOWN)) { - if (abstime != NULL) - ret = -pthread_cond_timedwait(rb->del, - rb->lock, - abstime); - else - ret = -pthread_cond_wait(rb->del, rb->lock); -#ifdef HAVE_ROBUST_MUTEX - if (ret == -EOWNERDEAD) - pthread_mutex_consistent(rb->lock); -#endif - } - - if (ret != -ETIMEDOUT) { - if (shm_rbuff_empty(rb)) - pthread_cond_broadcast(rb->add); - *head_el_ptr(rb) = (ssize_t) idx; - *rb->head = (*rb->head + 1) & ((SHM_RBUFF_SIZE) - 1); - } - - pthread_cleanup_pop(true); - - return ret; - err: - pthread_mutex_unlock(rb->lock); - return ret; -} - -ssize_t shm_rbuff_read(struct shm_rbuff * rb) -{ - ssize_t ret = 0; - - assert(rb); - -#ifndef HAVE_ROBUST_MUTEX - pthread_mutex_lock(rb->lock); -#else - if (pthread_mutex_lock(rb->lock) == EOWNERDEAD) - pthread_mutex_consistent(rb->lock); -#endif - - if (shm_rbuff_empty(rb)) { - ret = *rb->acl & ACL_FLOWDOWN ? -EFLOWDOWN : -EAGAIN; - pthread_mutex_unlock(rb->lock); - return ret; - } - - ret = *tail_el_ptr(rb); - *rb->tail = (*rb->tail + 1) & ((SHM_RBUFF_SIZE) - 1); - pthread_cond_broadcast(rb->del); - - pthread_mutex_unlock(rb->lock); - - return ret; -} - -ssize_t shm_rbuff_read_b(struct shm_rbuff * rb, - const struct timespec * abstime) -{ - ssize_t idx = -1; - - assert(rb); - -#ifndef HAVE_ROBUST_MUTEX - pthread_mutex_lock(rb->lock); -#else - if (pthread_mutex_lock(rb->lock) == EOWNERDEAD) - pthread_mutex_consistent(rb->lock); -#endif - - if (shm_rbuff_empty(rb) && (*rb->acl & ACL_FLOWDOWN)) { - pthread_mutex_unlock(rb->lock); - return -EFLOWDOWN; - } - - pthread_cleanup_push(__cleanup_mutex_unlock, rb->lock); - - while (shm_rbuff_empty(rb) - && (idx != -ETIMEDOUT) - && !(*rb->acl & ACL_FLOWDOWN)) { - if (abstime != NULL) - idx = -pthread_cond_timedwait(rb->add, - rb->lock, - abstime); - else - idx = -pthread_cond_wait(rb->add, rb->lock); -#ifdef HAVE_ROBUST_MUTEX - if (idx == -EOWNERDEAD) - pthread_mutex_consistent(rb->lock); -#endif - } - - if (idx != -ETIMEDOUT) { - idx = *tail_el_ptr(rb); - *rb->tail = (*rb->tail + 1) & ((SHM_RBUFF_SIZE) - 1); - pthread_cond_broadcast(rb->del); - } - - pthread_cleanup_pop(true); - - return idx; -} - -void shm_rbuff_set_acl(struct shm_rbuff * rb, - uint32_t flags) -{ - assert(rb); - -#ifndef HAVE_ROBUST_MUTEX - pthread_mutex_lock(rb->lock); -#else - if (pthread_mutex_lock(rb->lock) == EOWNERDEAD) - pthread_mutex_consistent(rb->lock); -#endif - *rb->acl = (size_t) flags; - - pthread_cond_broadcast(rb->del); - pthread_cond_broadcast(rb->add); - - pthread_mutex_unlock(rb->lock); -} - -uint32_t shm_rbuff_get_acl(struct shm_rbuff * rb) -{ - uint32_t flags; - - assert(rb); - -#ifndef HAVE_ROBUST_MUTEX - pthread_mutex_lock(rb->lock); -#else - if (pthread_mutex_lock(rb->lock) == EOWNERDEAD) - pthread_mutex_consistent(rb->lock); -#endif - flags = (uint32_t) *rb->acl; - - pthread_mutex_unlock(rb->lock); - - return flags; -} - -void shm_rbuff_fini(struct shm_rbuff * rb) -{ - assert(rb); - -#ifndef HAVE_ROBUST_MUTEX - pthread_mutex_lock(rb->lock); -#else - if (pthread_mutex_lock(rb->lock) == EOWNERDEAD) - pthread_mutex_consistent(rb->lock); -#endif - pthread_cleanup_push(__cleanup_mutex_unlock, rb->lock); - - while (!shm_rbuff_empty(rb)) -#ifndef HAVE_ROBUST_MUTEX - pthread_cond_wait(rb->del, rb->lock); -#else - if (pthread_cond_wait(rb->del, rb->lock) == EOWNERDEAD) - pthread_mutex_consistent(rb->lock); -#endif - pthread_cleanup_pop(true); -} - -size_t shm_rbuff_queued(struct shm_rbuff * rb) -{ - size_t ret; - - assert(rb); - -#ifndef HAVE_ROBUST_MUTEX - pthread_mutex_lock(rb->lock); -#else - if (pthread_mutex_lock(rb->lock) == EOWNERDEAD) - pthread_mutex_consistent(rb->lock); -#endif - - ret = shm_rbuff_used(rb); - - pthread_mutex_unlock(rb->lock); - - return ret; -} diff --git a/src/lib/shm_rdrbuff.c b/src/lib/shm_rdrbuff.c deleted file mode 100644 index e3552100..00000000 --- a/src/lib/shm_rdrbuff.c +++ /dev/null @@ -1,611 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2021 - * - * Random Deletion Ring Buffer for Data Units - * - * Dimitri Staessens <dimitri@ouroboros.rocks> - * Sander Vrijders <sander@ouroboros.rocks> - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public License - * version 2.1 as published by the Free Software Foundation. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., http://www.fsf.org/about/contact/. - */ - -#define _POSIX_C_SOURCE 200809L - -#include "config.h" - -#include <ouroboros/errno.h> -#include <ouroboros/shm_rdrbuff.h> -#include <ouroboros/shm_du_buff.h> -#include <ouroboros/time_utils.h> -#include <ouroboros/pthread.h> - -#include <sys/mman.h> -#include <fcntl.h> -#include <unistd.h> -#include <stdlib.h> -#include <string.h> -#include <stdio.h> -#include <signal.h> -#include <sys/stat.h> -#include <stdbool.h> -#include <assert.h> - -#define SHM_BLOCKS_SIZE ((SHM_BUFFER_SIZE) * SHM_RDRB_BLOCK_SIZE) -#define SHM_FILE_SIZE (SHM_BLOCKS_SIZE + 2 * sizeof(size_t) \ - + sizeof(pthread_mutex_t) + 2 * sizeof(pthread_cond_t) \ - + sizeof(pid_t)) -#define DU_BUFF_OVERHEAD (DU_BUFF_HEADSPACE + DU_BUFF_TAILSPACE) - -#define get_head_ptr(rdrb) \ - idx_to_du_buff_ptr(rdrb, *rdrb->head) - -#define get_tail_ptr(rdrb) \ - idx_to_du_buff_ptr(rdrb, *rdrb->tail) - -#define idx_to_du_buff_ptr(rdrb, idx) \ - ((struct shm_du_buff *) (rdrb->shm_base + idx * SHM_RDRB_BLOCK_SIZE)) - -#define shm_rdrb_used(rdrb) \ - (((*rdrb->head + (SHM_BUFFER_SIZE) - *rdrb->tail) + 1) \ - & ((SHM_BUFFER_SIZE) - 1)) - -#define shm_rdrb_free(rdrb, i) \ - (shm_rdrb_used(rdrb) + i < (SHM_BUFFER_SIZE)) - -#define shm_rdrb_empty(rdrb) \ - (*rdrb->tail == *rdrb->head) - -struct shm_du_buff { - size_t size; -#ifdef SHM_RDRB_MULTI_BLOCK - size_t blocks; -#endif - size_t du_head; - size_t du_tail; - size_t refs; - size_t idx; -}; - -struct shm_rdrbuff { - uint8_t * shm_base; /* start of blocks */ - size_t * head; /* start of ringbuffer head */ - size_t * tail; /* start of ringbuffer tail */ - pthread_mutex_t * lock; /* lock all free space in shm */ - pthread_cond_t * healthy; /* flag when packet is read */ - pid_t * pid; /* pid of the irmd owner */ -}; - -static void garbage_collect(struct shm_rdrbuff * rdrb) -{ -#ifdef SHM_RDRB_MULTI_BLOCK - struct shm_du_buff * sdb; - while (!shm_rdrb_empty(rdrb) && - (sdb = get_tail_ptr(rdrb))->refs == 0) - *rdrb->tail = (*rdrb->tail + sdb->blocks) - & ((SHM_BUFFER_SIZE) - 1); -#else - while (!shm_rdrb_empty(rdrb) && get_tail_ptr(rdrb)->refs == 0) - *rdrb->tail = (*rdrb->tail + 1) & ((SHM_BUFFER_SIZE) - 1); -#endif - pthread_cond_broadcast(rdrb->healthy); -} - -#ifdef HAVE_ROBUST_MUTEX -static void sanitize(struct shm_rdrbuff * rdrb) -{ - --get_head_ptr(rdrb)->refs; - garbage_collect(rdrb); - pthread_mutex_consistent(rdrb->lock); -} -#endif - -static char * rdrb_filename(void) -{ - char * str; - - str = malloc(strlen(SHM_RDRB_NAME) + 1); - if (str == NULL) - return NULL; - - sprintf(str, "%s", SHM_RDRB_NAME); - - return str; -} - -void shm_rdrbuff_close(struct shm_rdrbuff * rdrb) -{ - assert(rdrb); - - munmap(rdrb->shm_base, SHM_FILE_SIZE); - free(rdrb); -} - -void shm_rdrbuff_destroy(struct shm_rdrbuff * rdrb) -{ - char * shm_rdrb_fn; - - assert(rdrb); - - if (getpid() != *rdrb->pid && kill(*rdrb->pid, 0) == 0) { - free(rdrb); - return; - } - - shm_rdrbuff_close(rdrb); - - shm_rdrb_fn = rdrb_filename(); - if (shm_rdrb_fn == NULL) - return; - - shm_unlink(shm_rdrb_fn); - free(shm_rdrb_fn); -} - -#define MM_FLAGS (PROT_READ | PROT_WRITE) - -static struct shm_rdrbuff * rdrb_create(int flags) -{ - struct shm_rdrbuff * rdrb; - int fd; - uint8_t * shm_base; - char * shm_rdrb_fn; - - shm_rdrb_fn = rdrb_filename(); - if (shm_rdrb_fn == NULL) - goto fail_fn; - - rdrb = malloc(sizeof *rdrb); - if (rdrb == NULL) - goto fail_rdrb; - - fd = shm_open(shm_rdrb_fn, flags, 0666); - if (fd == -1) - goto fail_open; - - if ((flags & O_CREAT) && ftruncate(fd, SHM_FILE_SIZE - 1) < 0) - goto fail_truncate; - - shm_base = mmap(NULL, SHM_FILE_SIZE, MM_FLAGS, MAP_SHARED, fd, 0); - if (shm_base == MAP_FAILED) - goto fail_truncate; - - close(fd); - - rdrb->shm_base = shm_base; - rdrb->head = (size_t *) ((uint8_t *) rdrb->shm_base + SHM_BLOCKS_SIZE); - rdrb->tail = rdrb->head + 1; - rdrb->lock = (pthread_mutex_t *) (rdrb->tail + 1); - rdrb->healthy = (pthread_cond_t *) (rdrb->lock + 1); - rdrb->pid = (pid_t *) (rdrb->healthy + 1); - - free(shm_rdrb_fn); - - return rdrb; - - fail_truncate: - close(fd); - if (flags & O_CREAT) - shm_unlink(shm_rdrb_fn); - fail_open: - free(rdrb); - fail_rdrb: - free(shm_rdrb_fn); - fail_fn: - return NULL; -} - -struct shm_rdrbuff * shm_rdrbuff_create() -{ - struct shm_rdrbuff * rdrb; - mode_t mask; - pthread_mutexattr_t mattr; - pthread_condattr_t cattr; - - mask = umask(0); - - rdrb = rdrb_create(O_CREAT | O_EXCL | O_RDWR); - - umask(mask); - - if (rdrb == NULL) - goto fail_rdrb; - - if (pthread_mutexattr_init(&mattr)) - goto fail_mattr; - - pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED); -#ifdef HAVE_ROBUST_MUTEX - pthread_mutexattr_setrobust(&mattr, PTHREAD_MUTEX_ROBUST); -#endif - if (pthread_mutex_init(rdrb->lock, &mattr)) - goto fail_mutex; - - if (pthread_condattr_init(&cattr)) - goto fail_cattr; - - pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED); -#ifndef __APPLE__ - pthread_condattr_setclock(&cattr, PTHREAD_COND_CLOCK); -#endif - if (pthread_cond_init(rdrb->healthy, &cattr)) - goto fail_healthy; - - *rdrb->head = 0; - *rdrb->tail = 0; - - *rdrb->pid = getpid(); - - pthread_mutexattr_destroy(&mattr); - pthread_condattr_destroy(&cattr); - - return rdrb; - - fail_healthy: - pthread_condattr_destroy(&cattr); - fail_cattr: - pthread_mutex_destroy(rdrb->lock); - fail_mutex: - pthread_mutexattr_destroy(&mattr); - fail_mattr: - shm_rdrbuff_destroy(rdrb); - fail_rdrb: - return NULL; -} - -struct shm_rdrbuff * shm_rdrbuff_open() -{ - return rdrb_create(O_RDWR); -} - -void shm_rdrbuff_purge(void) -{ - char * shm_rdrb_fn; - - shm_rdrb_fn = rdrb_filename(); - if (shm_rdrb_fn == NULL) - return; - - shm_unlink(shm_rdrb_fn); - free(shm_rdrb_fn); -} - -ssize_t shm_rdrbuff_alloc(struct shm_rdrbuff * rdrb, - size_t len, - uint8_t ** ptr, - struct shm_du_buff ** psdb) -{ - struct shm_du_buff * sdb; - size_t size = DU_BUFF_OVERHEAD + len; -#ifdef SHM_RDRB_MULTI_BLOCK - size_t blocks = 0; - size_t padblocks = 0; -#endif - ssize_t sz = size + sizeof(*sdb); - - assert(rdrb); - assert(psdb); - -#ifndef SHM_RDRB_MULTI_BLOCK - if (sz > SHM_RDRB_BLOCK_SIZE) - return -EMSGSIZE; -#else - while (sz > 0) { - sz -= SHM_RDRB_BLOCK_SIZE; - ++blocks; - } -#endif -#ifndef HAVE_ROBUST_MUTEX - pthread_mutex_lock(rdrb->lock); -#else - if (pthread_mutex_lock(rdrb->lock) == EOWNERDEAD) - sanitize(rdrb); -#endif -#ifdef SHM_RDRB_MULTI_BLOCK - if (blocks + *rdrb->head > (SHM_BUFFER_SIZE)) - padblocks = (SHM_BUFFER_SIZE) - *rdrb->head; - - if (!shm_rdrb_free(rdrb, blocks + padblocks)) { -#else - if (!shm_rdrb_free(rdrb, 1)) { -#endif - pthread_mutex_unlock(rdrb->lock); - return -EAGAIN; - } - -#ifdef SHM_RDRB_MULTI_BLOCK - if (padblocks) { - sdb = get_head_ptr(rdrb); - sdb->size = 0; - sdb->blocks = padblocks; - sdb->refs = 0; - sdb->du_head = 0; - sdb->du_tail = 0; - sdb->idx = *rdrb->head; - - *rdrb->head = 0; - } -#endif - sdb = get_head_ptr(rdrb); - sdb->refs = 1; - sdb->idx = *rdrb->head; -#ifdef SHM_RDRB_MULTI_BLOCK - sdb->blocks = blocks; - - *rdrb->head = (*rdrb->head + blocks) & ((SHM_BUFFER_SIZE) - 1); -#else - *rdrb->head = (*rdrb->head + 1) & ((SHM_BUFFER_SIZE) - 1); -#endif - pthread_mutex_unlock(rdrb->lock); - - sdb->size = size; - sdb->du_head = DU_BUFF_HEADSPACE; - sdb->du_tail = sdb->du_head + len; - - *psdb = sdb; - if (ptr != NULL) - *ptr = (uint8_t *) (sdb + 1) + sdb->du_head; - - return sdb->idx; -} - -ssize_t shm_rdrbuff_alloc_b(struct shm_rdrbuff * rdrb, - size_t len, - uint8_t ** ptr, - struct shm_du_buff ** psdb, - const struct timespec * abstime) -{ - struct shm_du_buff * sdb; - size_t size = DU_BUFF_OVERHEAD + len; -#ifdef SHM_RDRB_MULTI_BLOCK - size_t blocks = 0; - size_t padblocks = 0; -#endif - ssize_t sz = size + sizeof(*sdb); - int ret = 0; - - assert(rdrb); - assert(psdb); - -#ifndef SHM_RDRB_MULTI_BLOCK - if (sz > SHM_RDRB_BLOCK_SIZE) - return -EMSGSIZE; -#else - while (sz > 0) { - sz -= SHM_RDRB_BLOCK_SIZE; - ++blocks; - } -#endif -#ifndef HAVE_ROBUST_MUTEX - pthread_mutex_lock(rdrb->lock); -#else - if (pthread_mutex_lock(rdrb->lock) == EOWNERDEAD) - sanitize(rdrb); -#endif - pthread_cleanup_push(__cleanup_mutex_unlock, rdrb->lock); - -#ifdef SHM_RDRB_MULTI_BLOCK - if (blocks + *rdrb->head > (SHM_BUFFER_SIZE)) - padblocks = (SHM_BUFFER_SIZE) - *rdrb->head; - - while (!shm_rdrb_free(rdrb, blocks + padblocks) && ret != ETIMEDOUT) { -#else - while (!shm_rdrb_free(rdrb, 1) && ret != ETIMEDOUT) { -#endif - if (abstime != NULL) - ret = pthread_cond_timedwait(rdrb->healthy, - rdrb->lock, - abstime); - else - ret = pthread_cond_wait(rdrb->healthy, rdrb->lock); - -#ifdef SHM_RDRB_MULTI_BLOCK - if (blocks + *rdrb->head > (SHM_BUFFER_SIZE)) - padblocks = (SHM_BUFFER_SIZE) - *rdrb->head; -#endif - } - - if (ret != ETIMEDOUT) { -#ifdef SHM_RDRB_MULTI_BLOCK - if (padblocks) { - sdb = get_head_ptr(rdrb); - sdb->size = 0; - sdb->blocks = padblocks; - sdb->refs = 0; - sdb->du_head = 0; - sdb->du_tail = 0; - sdb->idx = *rdrb->head; - - *rdrb->head = 0; - } -#endif - sdb = get_head_ptr(rdrb); - sdb->refs = 1; - sdb->idx = *rdrb->head; -#ifdef SHM_RDRB_MULTI_BLOCK - sdb->blocks = blocks; - - *rdrb->head = (*rdrb->head + blocks) & ((SHM_BUFFER_SIZE) - 1); -#else - *rdrb->head = (*rdrb->head + 1) & ((SHM_BUFFER_SIZE) - 1); -#endif - } - - pthread_cleanup_pop(true); - - if (ret == ETIMEDOUT) - return -ETIMEDOUT; - - sdb->size = size; - sdb->du_head = DU_BUFF_HEADSPACE; - sdb->du_tail = sdb->du_head + len; - - *psdb = sdb; - if (ptr != NULL) - *ptr = (uint8_t *) (sdb + 1) + sdb->du_head; - - return sdb->idx; -} - -ssize_t shm_rdrbuff_read(uint8_t ** dst, - struct shm_rdrbuff * rdrb, - size_t idx) -{ - struct shm_du_buff * sdb; - - assert(dst); - assert(rdrb); - assert(idx < (SHM_BUFFER_SIZE)); - - sdb = idx_to_du_buff_ptr(rdrb, idx); - *dst = ((uint8_t *) (sdb + 1)) + sdb->du_head; - - return (ssize_t) (sdb->du_tail - sdb->du_head); -} - -struct shm_du_buff * shm_rdrbuff_get(struct shm_rdrbuff * rdrb, - size_t idx) -{ - assert(rdrb); - assert(idx < (SHM_BUFFER_SIZE)); - - return idx_to_du_buff_ptr(rdrb, idx); -} - -int shm_rdrbuff_remove(struct shm_rdrbuff * rdrb, - size_t idx) -{ - struct shm_du_buff * sdb; - - assert(rdrb); - assert(idx < (SHM_BUFFER_SIZE)); - -#ifndef HAVE_ROBUST_MUTEX - pthread_mutex_lock(rdrb->lock); -#else - if (pthread_mutex_lock(rdrb->lock) == EOWNERDEAD) - sanitize(rdrb); -#endif - /* assert(!shm_rdrb_empty(rdrb)); */ - - sdb = idx_to_du_buff_ptr(rdrb, idx); - - if (sdb->refs == 1) { /* only stack needs it, can be removed */ - sdb->refs = 0; - if (idx == *rdrb->tail) - garbage_collect(rdrb); - } - - pthread_mutex_unlock(rdrb->lock); - - return 0; -} - -size_t shm_du_buff_get_idx(struct shm_du_buff * sdb) -{ - assert(sdb); - - return sdb->idx; -} - -uint8_t * shm_du_buff_head(struct shm_du_buff * sdb) -{ - assert(sdb); - - return (uint8_t *) (sdb + 1) + sdb->du_head; -} - -uint8_t * shm_du_buff_tail(struct shm_du_buff * sdb) -{ - assert(sdb); - - return (uint8_t *) (sdb + 1) + sdb->du_tail; -} - -uint8_t * shm_du_buff_head_alloc(struct shm_du_buff * sdb, - size_t size) -{ - assert(sdb); - - if (sdb->du_head < size) - return NULL; - - sdb->du_head -= size; - - return (uint8_t *) (sdb + 1) + sdb->du_head; -} - -uint8_t * shm_du_buff_tail_alloc(struct shm_du_buff * sdb, - size_t size) -{ - uint8_t * buf; - - assert(sdb); - - if (sdb->du_tail + size >= sdb->size) - return NULL; - - buf = (uint8_t *) (sdb + 1) + sdb->du_tail; - - sdb->du_tail += size; - - return buf; -} - -uint8_t * shm_du_buff_head_release(struct shm_du_buff * sdb, - size_t size) -{ - uint8_t * buf; - - assert(sdb); - assert(!(size > sdb->du_tail - sdb->du_head)); - - buf = (uint8_t *) (sdb + 1) + sdb->du_head; - - sdb->du_head += size; - - return buf; -} - -uint8_t * shm_du_buff_tail_release(struct shm_du_buff * sdb, - size_t size) -{ - assert(sdb); - assert(!(size > sdb->du_tail - sdb->du_head)); - - sdb->du_tail -= size; - - return (uint8_t *) (sdb + 1) + sdb->du_tail; -} - -void shm_du_buff_truncate(struct shm_du_buff * sdb, - size_t len) -{ - assert(sdb); - assert(len <= sdb->size); - - sdb->du_tail = sdb->du_head + len; -} - -int shm_du_buff_wait_ack(struct shm_du_buff * sdb) -{ - __sync_add_and_fetch(&sdb->refs, 1); - - return 0; -} - -int shm_du_buff_ack(struct shm_du_buff * sdb) -{ - __sync_sub_and_fetch(&sdb->refs, 1); - return 0; -} diff --git a/src/lib/sockets.c b/src/lib/sockets.c index 8179d2b3..5dfbcb5c 100644 --- a/src/lib/sockets.c +++ b/src/lib/sockets.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * The sockets layer to communicate between daemons * @@ -21,6 +21,7 @@ */ #include <ouroboros/errno.h> +#include <ouroboros/pthread.h> #include <ouroboros/sockets.h> #include <ouroboros/utils.h> @@ -29,9 +30,7 @@ #include <string.h> #include <stdio.h> #include <stdlib.h> -#include <pthread.h> #include <stdbool.h> -#include <sys/time.h> /* Apple doesn't support SEQPACKET. */ #ifdef __APPLE__ @@ -57,8 +56,7 @@ int client_socket_open(char * file_name) serv_addr.sun_family = AF_UNIX; sprintf(serv_addr.sun_path, "%s", file_name); - if (connect(sockfd, - (struct sockaddr *) &serv_addr, + if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr))) { close(sockfd); return -1; @@ -102,10 +100,9 @@ int server_socket_open(char * file_name) irm_msg_t * send_recv_irm_msg(irm_msg_t * msg) { - int sockfd; - uint8_t buf[SOCK_BUF_SIZE]; - ssize_t len; - irm_msg_t * recv_msg = NULL; + int sockfd; + uint8_t buf[SOCK_BUF_SIZE]; + ssize_t len; sockfd = client_socket_open(IRM_SOCK_PATH); if (sockfd < 0) @@ -117,27 +114,53 @@ irm_msg_t * send_recv_irm_msg(irm_msg_t * msg) return NULL; } - pthread_cleanup_push(__cleanup_close_ptr, &sockfd); - irm_msg__pack(msg, buf); - if (write(sockfd, buf, len) != -1) + pthread_cleanup_push(__cleanup_close_ptr, &sockfd); + + len = write(sockfd, buf, len); + if (len >= 0) len = read(sockfd, buf, SOCK_BUF_SIZE); - if (len > 0) - recv_msg = irm_msg__unpack(NULL, len, buf); + pthread_cleanup_pop(true); + + if (len < 0) + goto fail; + + return irm_msg__unpack(NULL, len, buf); + fail: + return NULL; +} + +int send_recv_msg(buffer_t * msg) +{ + int sockfd; + ssize_t len = 0; + + sockfd = client_socket_open(IRM_SOCK_PATH); + if (sockfd < 0) + return -1; + + pthread_cleanup_push(__cleanup_close_ptr, &sockfd); + + len = write(sockfd, msg->data, msg->len); + if (len >= 0) + len = read(sockfd, msg->data, SOCK_BUF_SIZE); pthread_cleanup_pop(true); - return recv_msg; + msg->len = (size_t) len; + + return len < 0 ? -1 : 0; } -char * ipcp_sock_path(pid_t pid) +static char * __sock_path(pid_t pid, + const char * prefix, + const char * suffix) { char * full_name = NULL; char * pid_string = NULL; size_t len = 0; - char * delim = "_"; len = n_digits(pid); pid_string = malloc(len + 1); @@ -146,9 +169,9 @@ char * ipcp_sock_path(pid_t pid) sprintf(pid_string, "%d", pid); - len += strlen(IPCP_SOCK_PATH_PREFIX); - len += strlen(delim); - len += strlen(SOCK_PATH_SUFFIX); + len += strlen(prefix); + len += strlen(pid_string); + len += strlen(suffix); full_name = malloc(len + 1); if (full_name == NULL) { @@ -156,49 +179,17 @@ char * ipcp_sock_path(pid_t pid) return NULL; } - strcpy(full_name, IPCP_SOCK_PATH_PREFIX); - strcat(full_name, delim); + strcpy(full_name, prefix); strcat(full_name, pid_string); - strcat(full_name, SOCK_PATH_SUFFIX); + strcat(full_name, suffix); free(pid_string); return full_name; } -qosspec_msg_t spec_to_msg(const qosspec_t * qs) +char * sock_path(pid_t pid, + const char * prefix) { - qosspec_t spec; - qosspec_msg_t msg = QOSSPEC_MSG__INIT; - - spec = (qs == NULL ? qos_raw : *qs); - - msg.delay = spec.delay; - msg.bandwidth = spec.bandwidth; - msg.availability = spec.availability; - msg.loss = spec.loss; - msg.ber = spec.ber; - msg.in_order = spec.in_order; - msg.max_gap = spec.max_gap; - msg.cypher_s = spec.cypher_s; - - return msg; -} - -qosspec_t msg_to_spec(const qosspec_msg_t * msg) -{ - qosspec_t spec; - - assert(msg); - - spec.delay = msg->delay; - spec.bandwidth = msg->bandwidth; - spec.availability = msg->availability; - spec.loss = msg->loss; - spec.ber = msg->ber; - spec.in_order = msg->in_order; - spec.max_gap = msg->max_gap; - spec.cypher_s = msg->cypher_s; - - return spec; + return __sock_path(pid, prefix, SOCK_PATH_SUFFIX); } diff --git a/src/lib/shm_flow_set.c b/src/lib/ssm/flow_set.c index 5a9bee6c..ab24d357 100644 --- a/src/lib/shm_flow_set.c +++ b/src/lib/ssm/flow_set.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Management of flow_sets for fqueue * @@ -23,22 +23,23 @@ #define _POSIX_C_SOURCE 200809L #include "config.h" +#include "ssm.h" -#include <ouroboros/lockfile.h> -#include <ouroboros/time_utils.h> -#include <ouroboros/shm_flow_set.h> #include <ouroboros/errno.h> +#include <ouroboros/lockfile.h> #include <ouroboros/pthread.h> +#include <ouroboros/ssm_flow_set.h> +#include <ouroboros/time.h> -#include <sys/mman.h> +#include <assert.h> #include <fcntl.h> +#include <signal.h> #include <stdlib.h> #include <stdio.h> +#include <string.h> #include <unistd.h> -#include <signal.h> +#include <sys/mman.h> #include <sys/stat.h> -#include <string.h> -#include <assert.h> /* * pthread_cond_timedwait has a WONTFIX bug as of glibc 2.25 where it @@ -52,88 +53,78 @@ #endif #define FN_MAX_CHARS 255 +#define FS_PROT (PROT_READ | PROT_WRITE) -#define QUEUESIZE ((SHM_BUFFER_SIZE) * sizeof(struct portevent)) +#define QUEUESIZE ((SSM_RBUFF_SIZE) * sizeof(struct flowevent)) -#define SHM_FLOW_SET_FILE_SIZE (SYS_MAX_FLOWS * sizeof(ssize_t) \ - + PROG_MAX_FQUEUES * sizeof(size_t) \ - + PROG_MAX_FQUEUES * sizeof(pthread_cond_t) \ - + PROG_MAX_FQUEUES * QUEUESIZE \ - + sizeof(pthread_mutex_t)) +#define SSM_FSET_FILE_SIZE (SYS_MAX_FLOWS * sizeof(ssize_t) \ + + PROG_MAX_FQUEUES * sizeof(size_t) \ + + PROG_MAX_FQUEUES * sizeof(pthread_cond_t) \ + + PROG_MAX_FQUEUES * QUEUESIZE \ + + sizeof(pthread_mutex_t)) -#define fqueue_ptr(fs, idx) (fs->fqueues + (SHM_BUFFER_SIZE) * idx) +#define fqueue_ptr(fs, idx) (fs->fqueues + (SSM_RBUFF_SIZE) * idx) -struct portevent { - int flow_id; - int event; -}; - -struct shm_flow_set { +struct ssm_flow_set { ssize_t * mtable; size_t * heads; pthread_cond_t * conds; - struct portevent * fqueues; + struct flowevent * fqueues; pthread_mutex_t * lock; pid_t pid; }; -static struct shm_flow_set * flow_set_create(pid_t pid, - int flags) +static struct ssm_flow_set * flow_set_create(pid_t pid, + int oflags) { - struct shm_flow_set * set; + struct ssm_flow_set * set; ssize_t * shm_base; char fn[FN_MAX_CHARS]; - int shm_fd; + int fd; - sprintf(fn, SHM_FLOW_SET_PREFIX "%d", pid); + sprintf(fn, SSM_FLOW_SET_PREFIX "%d", pid); set = malloc(sizeof(*set)); if (set == NULL) goto fail_malloc; - shm_fd = shm_open(fn, flags, 0666); - if (shm_fd == -1) + fd = shm_open(fn, oflags, 0666); + if (fd == -1) goto fail_shm_open; - if (ftruncate(shm_fd, SHM_FLOW_SET_FILE_SIZE - 1) < 0) { - close(shm_fd); - goto fail_shm_open; - } - - shm_base = mmap(NULL, - SHM_FLOW_SET_FILE_SIZE, - PROT_READ | PROT_WRITE, - MAP_SHARED, - shm_fd, - 0); - - close(shm_fd); + if ((oflags & O_CREAT) && ftruncate(fd, SSM_FSET_FILE_SIZE) < 0) + goto fail_truncate; + shm_base = mmap(NULL, SSM_FSET_FILE_SIZE, FS_PROT, MAP_SHARED, fd, 0); if (shm_base == MAP_FAILED) goto fail_mmap; + close(fd); + set->mtable = shm_base; set->heads = (size_t *) (set->mtable + SYS_MAX_FLOWS); set->conds = (pthread_cond_t *)(set->heads + PROG_MAX_FQUEUES); - set->fqueues = (struct portevent *) (set->conds + PROG_MAX_FQUEUES); + set->fqueues = (struct flowevent *) (set->conds + PROG_MAX_FQUEUES); set->lock = (pthread_mutex_t *) - (set->fqueues + PROG_MAX_FQUEUES * (SHM_BUFFER_SIZE)); + (set->fqueues + PROG_MAX_FQUEUES * (SSM_RBUFF_SIZE)); return set; fail_mmap: - if (flags & O_CREAT) + if (oflags & O_CREAT) shm_unlink(fn); + fail_truncate: + close(fd); fail_shm_open: free(set); fail_malloc: return NULL; } -struct shm_flow_set * shm_flow_set_create(pid_t pid) +struct ssm_flow_set * ssm_flow_set_create(pid_t pid) { - struct shm_flow_set * set; + struct ssm_flow_set * set; pthread_mutexattr_t mattr; pthread_condattr_t cattr; mode_t mask; @@ -194,38 +185,38 @@ struct shm_flow_set * shm_flow_set_create(pid_t pid) fail_mattr_set: pthread_mutexattr_destroy(&mattr); fail_mutexattr_init: - shm_flow_set_destroy(set); + ssm_flow_set_destroy(set); fail_set: return NULL; } -struct shm_flow_set * shm_flow_set_open(pid_t pid) +struct ssm_flow_set * ssm_flow_set_open(pid_t pid) { return flow_set_create(pid, O_RDWR); } -void shm_flow_set_destroy(struct shm_flow_set * set) +void ssm_flow_set_destroy(struct ssm_flow_set * set) { - char fn[25]; + char fn[FN_MAX_CHARS]; assert(set); - sprintf(fn, SHM_FLOW_SET_PREFIX "%d", set->pid); + sprintf(fn, SSM_FLOW_SET_PREFIX "%d", set->pid); - shm_flow_set_close(set); + ssm_flow_set_close(set); shm_unlink(fn); } -void shm_flow_set_close(struct shm_flow_set * set) +void ssm_flow_set_close(struct ssm_flow_set * set) { assert(set); - munmap(set->mtable, SHM_FLOW_SET_FILE_SIZE); + munmap(set->mtable, SSM_FSET_FILE_SIZE); free(set); } -void shm_flow_set_zero(struct shm_flow_set * set, +void ssm_flow_set_zero(struct ssm_flow_set * set, size_t idx) { ssize_t i = 0; @@ -245,7 +236,7 @@ void shm_flow_set_zero(struct shm_flow_set * set, } -int shm_flow_set_add(struct shm_flow_set * set, +int ssm_flow_set_add(struct ssm_flow_set * set, size_t idx, int flow_id) { @@ -267,7 +258,7 @@ int shm_flow_set_add(struct shm_flow_set * set, return 0; } -void shm_flow_set_del(struct shm_flow_set * set, +void ssm_flow_set_del(struct ssm_flow_set * set, size_t idx, int flow_id) { @@ -283,7 +274,7 @@ void shm_flow_set_del(struct shm_flow_set * set, pthread_mutex_unlock(set->lock); } -int shm_flow_set_has(struct shm_flow_set * set, +int ssm_flow_set_has(struct ssm_flow_set * set, size_t idx, int flow_id) { @@ -303,10 +294,12 @@ int shm_flow_set_has(struct shm_flow_set * set, return ret; } -void shm_flow_set_notify(struct shm_flow_set * set, +void ssm_flow_set_notify(struct ssm_flow_set * set, int flow_id, int event) { + struct flowevent * e; + assert(set); assert(!(flow_id < 0) && flow_id < SYS_MAX_FLOWS); @@ -317,10 +310,13 @@ void shm_flow_set_notify(struct shm_flow_set * set, return; } - (fqueue_ptr(set, set->mtable[flow_id]) + - (set->heads[set->mtable[flow_id]]))->flow_id = flow_id; - (fqueue_ptr(set, set->mtable[flow_id]) + - (set->heads[set->mtable[flow_id]])++)->event = event; + e = fqueue_ptr(set, set->mtable[flow_id]) + + set->heads[set->mtable[flow_id]]; + + e->flow_id = flow_id; + e->event = event; + + ++set->heads[set->mtable[flow_id]]; pthread_cond_signal(&set->conds[set->mtable[flow_id]]); @@ -328,9 +324,9 @@ void shm_flow_set_notify(struct shm_flow_set * set, } -ssize_t shm_flow_set_wait(const struct shm_flow_set * set, +ssize_t ssm_flow_set_wait(const struct ssm_flow_set * set, size_t idx, - int * fqueue, + struct flowevent * fqueue, const struct timespec * abstime) { ssize_t ret = 0; @@ -349,18 +345,11 @@ ssize_t shm_flow_set_wait(const struct shm_flow_set * set, pthread_cleanup_push(__cleanup_mutex_unlock, set->lock); while (set->heads[idx] == 0 && ret != -ETIMEDOUT) { - if (abstime != NULL) { - ret = -pthread_cond_timedwait(set->conds + idx, - set->lock, - abstime); + ret = -__timedwait(set->conds + idx, set->lock, abstime); #ifdef HAVE_CANCEL_BUG - if (ret == -ETIMEDOUT) - pthread_testcancel(); + if (ret == -ETIMEDOUT) + pthread_testcancel(); #endif - } else { - ret = -pthread_cond_wait(set->conds + idx, - set->lock); - } #ifdef HAVE_ROBUST_MUTEX if (ret == -EOWNERDEAD) pthread_mutex_consistent(set->lock); @@ -370,7 +359,7 @@ ssize_t shm_flow_set_wait(const struct shm_flow_set * set, if (ret != -ETIMEDOUT) { memcpy(fqueue, fqueue_ptr(set, idx), - set->heads[idx] * sizeof(struct portevent)); + set->heads[idx] * sizeof(*fqueue)); ret = set->heads[idx]; set->heads[idx] = 0; } diff --git a/src/lib/ssm/pool.c b/src/lib/ssm/pool.c new file mode 100644 index 00000000..e938f644 --- /dev/null +++ b/src/lib/ssm/pool.c @@ -0,0 +1,953 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * Secure Shared Memory Infrastructure (SSMI) Packet Buffer + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#define _POSIX_C_SOURCE 200809L + +#include "config.h" + +#include <ouroboros/errno.h> +#include <ouroboros/pthread.h> +#include <ouroboros/ssm_pool.h> + +#include "ssm.h" + +#include <assert.h> +#include <fcntl.h> +#include <signal.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/mman.h> +#include <sys/stat.h> + +/* Global Shared Packet Pool (GSPP) configuration */ +static const struct ssm_size_class_cfg ssm_gspp_cfg[SSM_POOL_MAX_CLASSES] = { + { (1 << 8), SSM_GSPP_256_BLOCKS }, + { (1 << 9), SSM_GSPP_512_BLOCKS }, + { (1 << 10), SSM_GSPP_1K_BLOCKS }, + { (1 << 11), SSM_GSPP_2K_BLOCKS }, + { (1 << 12), SSM_GSPP_4K_BLOCKS }, + { (1 << 14), SSM_GSPP_16K_BLOCKS }, + { (1 << 16), SSM_GSPP_64K_BLOCKS }, + { (1 << 18), SSM_GSPP_256K_BLOCKS }, + { (1 << 20), SSM_GSPP_1M_BLOCKS }, +}; + +/* Per-User Pool (PUP) configuration */ +static const struct ssm_size_class_cfg ssm_pup_cfg[SSM_POOL_MAX_CLASSES] = { + { (1 << 8), SSM_PUP_256_BLOCKS }, + { (1 << 9), SSM_PUP_512_BLOCKS }, + { (1 << 10), SSM_PUP_1K_BLOCKS }, + { (1 << 11), SSM_PUP_2K_BLOCKS }, + { (1 << 12), SSM_PUP_4K_BLOCKS }, + { (1 << 14), SSM_PUP_16K_BLOCKS }, + { (1 << 16), SSM_PUP_64K_BLOCKS }, + { (1 << 18), SSM_PUP_256K_BLOCKS }, + { (1 << 20), SSM_PUP_1M_BLOCKS }, +}; + +#define PTR_TO_OFFSET(pool_base, ptr) \ + ((uintptr_t)(ptr) - (uintptr_t)(pool_base)) + +#define OFFSET_TO_PTR(pool_base, offset) \ + ((offset == 0) ? NULL : (void *)((uintptr_t)(pool_base) + offset)) + +#define GET_SHARD_FOR_PID(pid) ((int)((pid) % SSM_POOL_SHARDS)) + +#define LOAD_RELAXED(ptr) \ + (__atomic_load_n(ptr, __ATOMIC_RELAXED)) + +#define LOAD_ACQUIRE(ptr) \ + (__atomic_load_n(ptr, __ATOMIC_ACQUIRE)) + +#define STORE_RELEASE(ptr, val) \ + (__atomic_store_n(ptr, val, __ATOMIC_RELEASE)) + +#define LOAD(ptr) \ + (__atomic_load_n(ptr, __ATOMIC_SEQ_CST)) + +#define STORE(ptr, val) \ + (__atomic_store_n(ptr, val, __ATOMIC_SEQ_CST)) + +#define FETCH_ADD(ptr, val) \ + (__atomic_fetch_add(ptr, val, __ATOMIC_SEQ_CST)) + +#define FETCH_SUB(ptr, val) \ + (__atomic_fetch_sub(ptr, val, __ATOMIC_SEQ_CST)) + +#define SSM_FILE_SIZE (SSM_POOL_TOTAL_SIZE + sizeof(struct _ssm_pool_hdr)) +#define SSM_GSPP_FILE_SIZE (SSM_GSPP_TOTAL_SIZE + sizeof(struct _ssm_pool_hdr)) +#define SSM_PUP_FILE_SIZE (SSM_PUP_TOTAL_SIZE + sizeof(struct _ssm_pool_hdr)) + +#define IS_GSPP(uid) ((uid) == SSM_GSPP_UID) +#define GET_POOL_TOTAL_SIZE(uid) (IS_GSPP(uid) ? SSM_GSPP_TOTAL_SIZE \ + : SSM_PUP_TOTAL_SIZE) +#define GET_POOL_FILE_SIZE(uid) (IS_GSPP(uid) ? SSM_GSPP_FILE_SIZE \ + : SSM_PUP_FILE_SIZE) +#define GET_POOL_CFG(uid) (IS_GSPP(uid) ? ssm_gspp_cfg : ssm_pup_cfg) + +struct ssm_pool { + uint8_t * shm_base; /* start of blocks */ + struct _ssm_pool_hdr * hdr; /* shared memory header */ + void * pool_base; /* base of the memory pool */ + uid_t uid; /* user owner (0 = GSPP) */ + size_t total_size; /* total data size */ +}; + +static __inline__ +struct ssm_pk_buff * list_remove_head(struct _ssm_list_head * head, + void * base) +{ + uint32_t off; + uint32_t next_off; + struct ssm_pk_buff * blk; + + assert(head != NULL); + assert(base != NULL); + + off = LOAD(&head->head_offset); + if (off == 0) + return NULL; + + /* Validate offset is within pool bounds */ + if (off >= SSM_POOL_TOTAL_SIZE) + return NULL; + + blk = OFFSET_TO_PTR(base, off); + next_off = LOAD(&blk->next_offset); + + + + STORE(&head->head_offset, next_off); + STORE(&head->count, LOAD(&head->count) - 1); + + return blk; +} +static __inline__ void list_add_head(struct _ssm_list_head * head, + struct ssm_pk_buff * blk, + void * base) +{ + uint32_t off; + uint32_t old; + + assert(head != NULL); + assert(blk != NULL); + assert(base != NULL); + + off = (uint32_t) PTR_TO_OFFSET(base, blk); + old = LOAD(&head->head_offset); + + STORE(&blk->next_offset, old); + STORE(&head->head_offset, off); + STORE(&head->count, LOAD(&head->count) + 1); +} + +static __inline__ int select_size_class(struct ssm_pool * pool, + size_t len) +{ + size_t sz; + int i; + + assert(pool != NULL); + + /* Total space needed: header + headspace + data + tailspace */ + sz = sizeof(struct ssm_pk_buff) + SSM_PK_BUFF_HEADSPACE + len + + SSM_PK_BUFF_TAILSPACE; + + for (i = 0; i < SSM_POOL_MAX_CLASSES; i++) { + struct _ssm_size_class * sc; + + sc = &pool->hdr->size_classes[i]; + if (sc->object_size > 0 && sz <= sc->object_size) + return i; + } + + return -1; +} + +static __inline__ int find_size_class_for_offset(struct ssm_pool * pool, + size_t offset) +{ + int c; + + assert(pool != NULL); + + for (c = 0; c < SSM_POOL_MAX_CLASSES; c++) { + struct _ssm_size_class * sc = &pool->hdr->size_classes[c]; + + if (sc->object_size == 0) + continue; + + if (offset >= sc->pool_start && + offset < sc->pool_start + sc->pool_size) + return c; + } + + return -1; +} + +static void init_size_classes(struct ssm_pool * pool) +{ + const struct ssm_size_class_cfg * cfg; + struct _ssm_size_class * sc; + struct _ssm_shard * shard; + pthread_mutexattr_t mattr; + pthread_condattr_t cattr; + uint8_t * region; + size_t offset; + int c; /* class iterator */ + int s; /* shard iterator */ + size_t i; + + assert(pool != NULL); + + /* Check if already initialized */ + if (LOAD(&pool->hdr->initialized) != 0) + return; + + cfg = GET_POOL_CFG(pool->uid); + + pthread_mutexattr_init(&mattr); + pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED); +#ifdef HAVE_ROBUST_MUTEX + pthread_mutexattr_setrobust(&mattr, PTHREAD_MUTEX_ROBUST); +#endif + pthread_mutexattr_setprotocol(&mattr, PTHREAD_PRIO_INHERIT); + + pthread_condattr_init(&cattr); + pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED); +#ifndef __APPLE__ + pthread_condattr_setclock(&cattr, PTHREAD_COND_CLOCK); +#endif + offset = 0; + + for (c = 0; c < SSM_POOL_MAX_CLASSES; c++) { + if (cfg[c].blocks == 0) + continue; + + sc = &pool->hdr->size_classes[c]; + + sc->object_size = cfg[c].size; + sc->pool_start = offset; + sc->pool_size = cfg[c].size * cfg[c].blocks; + sc->object_count = cfg[c].blocks; + + /* Initialize all shards */ + for (s = 0; s < SSM_POOL_SHARDS; s++) { + shard = &sc->shards[s]; + + STORE(&shard->free_list.head_offset, 0); + STORE(&shard->free_list.count, 0); + STORE(&shard->free_count, 0); + + pthread_mutex_init(&shard->mtx, &mattr); + pthread_cond_init(&shard->cond, &cattr); + } + + /* Lazy distribution: put all blocks in shard 0 initially */ + region = pool->shm_base + offset; + + for (i = 0; i < sc->object_count; ++i) { + struct ssm_pk_buff * blk; + + blk = (struct ssm_pk_buff *) + (region + i * sc->object_size); + + STORE(&blk->refcount, 0); + blk->allocator_pid = 0; + STORE(&blk->next_offset, 0); + + list_add_head(&sc->shards[0].free_list, blk, + pool->pool_base); + FETCH_ADD(&sc->shards[0].free_count, 1); + } + + offset += sc->pool_size; + } + + /* Mark as initialized - acts as memory barrier */ + STORE(&pool->hdr->initialized, 1); + + pthread_mutexattr_destroy(&mattr); + pthread_condattr_destroy(&cattr); +} + +/* + * Reclaim all blocks allocated by a specific pid in a size class. + * Called with shard mutex held. + */ +static size_t reclaim_pid_from_sc(struct _ssm_size_class * sc, + struct _ssm_shard * shard, + void * pool_base, + pid_t pid) +{ + uint8_t * region; + size_t i; + size_t recovered = 0; + struct ssm_pk_buff * blk; + + region = (uint8_t *) pool_base + sc->pool_start; + + for (i = 0; i < sc->object_count; ++i) { + blk = (struct ssm_pk_buff *)(region + i * sc->object_size); + + if (blk->allocator_pid == pid && LOAD(&blk->refcount) > 0) { + STORE(&blk->refcount, 0); + blk->allocator_pid = 0; + list_add_head(&shard->free_list, blk, pool_base); + FETCH_ADD(&shard->free_count, 1); + recovered++; + } + } + + return recovered; +} + +void ssm_pool_reclaim_orphans(struct ssm_pool * pool, + pid_t pid) +{ + size_t sc_idx; + + if (pool == NULL || pid <= 0) + return; + + for (sc_idx = 0; sc_idx < SSM_POOL_MAX_CLASSES; sc_idx++) { + struct _ssm_size_class * sc; + struct _ssm_shard * shard; + + sc = &pool->hdr->size_classes[sc_idx]; + if (sc->object_count == 0) + continue; + + /* Reclaim to shard 0 for simplicity */ + shard = &sc->shards[0]; + robust_mutex_lock(&shard->mtx); + reclaim_pid_from_sc(sc, shard, pool->pool_base, pid); + pthread_mutex_unlock(&shard->mtx); + } +} + +static __inline__ +struct ssm_pk_buff * try_alloc_from_shard(struct _ssm_shard * shard, + void * base) +{ + struct ssm_pk_buff * blk; + + robust_mutex_lock(&shard->mtx); + + if (LOAD(&shard->free_count) > 0) { + blk = list_remove_head(&shard->free_list, base); + if (blk != NULL) { + FETCH_SUB(&shard->free_count, 1); + return blk; /* Caller must unlock */ + } + FETCH_SUB(&shard->free_count, 1); + } + + pthread_mutex_unlock(&shard->mtx); + return NULL; +} + +static __inline__ ssize_t init_block(struct ssm_pool * pool, + struct _ssm_size_class * sc, + struct _ssm_shard * shard, + struct ssm_pk_buff * blk, + size_t len, + uint8_t ** ptr, + struct ssm_pk_buff ** spb) +{ + STORE(&blk->refcount, 1); + blk->allocator_pid = getpid(); + blk->size = (uint32_t) (sc->object_size - + sizeof(struct ssm_pk_buff)); + blk->pk_head = SSM_PK_BUFF_HEADSPACE; + blk->pk_tail = blk->pk_head + (uint32_t) len; + blk->off = (uint32_t) PTR_TO_OFFSET(pool->pool_base, blk); + + pthread_mutex_unlock(&shard->mtx); + + *spb = blk; + if (ptr != NULL) + *ptr = blk->data + blk->pk_head; + + return blk->off; +} + +/* Non-blocking allocation from size class */ +static ssize_t alloc_from_sc(struct ssm_pool * pool, + int idx, + size_t len, + uint8_t ** ptr, + struct ssm_pk_buff ** spb) +{ + struct _ssm_size_class * sc; + struct ssm_pk_buff * blk; + int local; + int s; + + assert(pool != NULL); + assert(idx >= 0 && idx < SSM_POOL_MAX_CLASSES); + assert(spb != NULL); + + sc = &pool->hdr->size_classes[idx]; + local = GET_SHARD_FOR_PID(getpid()); + + for (s = 0; s < SSM_POOL_SHARDS; s++) { + struct _ssm_shard * shard; + int idx; + + idx = (local + s) % SSM_POOL_SHARDS; + shard = &sc->shards[idx]; + + blk = try_alloc_from_shard(shard, pool->pool_base); + if (blk != NULL) + return init_block(pool, sc, shard, blk, len, ptr, spb); + } + + return -EAGAIN; +} + +/* Blocking allocation from size class */ +static ssize_t alloc_from_sc_b(struct ssm_pool * pool, + int idx, + size_t len, + uint8_t ** ptr, + struct ssm_pk_buff ** spb, + const struct timespec * abstime) +{ + struct _ssm_size_class * sc; + struct _ssm_shard * shard; + struct ssm_pk_buff * blk = NULL; + int local; + int s; + int ret = 0; + + assert(pool != NULL); + assert(idx >= 0 && idx < SSM_POOL_MAX_CLASSES); + assert(spb != NULL); + + sc = &pool->hdr->size_classes[idx]; + local = GET_SHARD_FOR_PID(getpid()); + + while (blk == NULL && ret != ETIMEDOUT) { + /* Try non-blocking allocation from any shard */ + for (s = 0; s < SSM_POOL_SHARDS && blk == NULL; s++) { + shard = &sc->shards[(local + s) % SSM_POOL_SHARDS]; + blk = try_alloc_from_shard(shard, pool->pool_base); + } + + if (blk != NULL) + break; + + /* Nothing available, wait for signal */ + shard = &sc->shards[local]; + robust_mutex_lock(&shard->mtx); + ret = robust_wait(&shard->cond, &shard->mtx, abstime); + pthread_mutex_unlock(&shard->mtx); + } + + if (ret == ETIMEDOUT) + return -ETIMEDOUT; + + return init_block(pool, sc, shard, blk, len, ptr, spb); +} + +/* Generate pool filename: uid=0 for GSPP, uid>0 for PUP */ +static char * pool_filename(uid_t uid) +{ + char * str; + char * test_suffix; + char base[64]; + + if (IS_GSPP(uid)) + snprintf(base, sizeof(base), "%s", SSM_GSPP_NAME); + else + snprintf(base, sizeof(base), SSM_PUP_NAME_FMT, (int) uid); + + test_suffix = getenv("OUROBOROS_TEST_POOL_SUFFIX"); + if (test_suffix != NULL) { + str = malloc(strlen(base) + strlen(test_suffix) + 1); + if (str == NULL) + return NULL; + sprintf(str, "%s%s", base, test_suffix); + } else { + str = strdup(base); + } + + return str; +} + +void ssm_pool_close(struct ssm_pool * pool) +{ + size_t file_size; + + assert(pool != NULL); + + file_size = GET_POOL_FILE_SIZE(pool->uid); + + munmap(pool->shm_base, file_size); + free(pool); +} + +void ssm_pool_destroy(struct ssm_pool * pool) +{ + char * fn; + + assert(pool != NULL); + + if (getpid() != pool->hdr->pid && kill(pool->hdr->pid, 0) == 0) { + ssm_pool_close(pool); + free(pool); + return; + } + + fn = pool_filename(pool->uid); + if (fn == NULL) { + ssm_pool_close(pool); + free(pool); + return; + } + + ssm_pool_close(pool); + + shm_unlink(fn); + free(fn); +} + +#define MM_FLAGS (PROT_READ | PROT_WRITE) + +static struct ssm_pool * __pool_create(const char * name, + int flags, + uid_t uid, + mode_t mode) +{ + struct ssm_pool * pool; + int fd; + uint8_t * shm_base; + size_t file_size; + size_t total_size; + + file_size = GET_POOL_FILE_SIZE(uid); + total_size = GET_POOL_TOTAL_SIZE(uid); + + pool = malloc(sizeof(*pool)); + if (pool == NULL) + goto fail_pool; + + fd = shm_open(name, flags, mode); + if (fd == -1) + goto fail_open; + + if ((flags & O_CREAT) && ftruncate(fd, (off_t) file_size) < 0) + goto fail_truncate; + + shm_base = mmap(NULL, file_size, MM_FLAGS, MAP_SHARED, fd, 0); + if (shm_base == MAP_FAILED) + goto fail_truncate; + + pool->shm_base = shm_base; + pool->pool_base = shm_base; + pool->hdr = (struct _ssm_pool_hdr *) (shm_base + total_size); + pool->uid = uid; + pool->total_size = total_size; + + if (flags & O_CREAT) + pool->hdr->mapped_addr = shm_base; + + close(fd); + + return pool; + + fail_truncate: + close(fd); + if (flags & O_CREAT) + shm_unlink(name); + fail_open: + free(pool); + fail_pool: + return NULL; +} + +struct ssm_pool * ssm_pool_create(uid_t uid, + gid_t gid) +{ + struct ssm_pool * pool; + char * fn; + mode_t mask; + mode_t mode; + pthread_mutexattr_t mattr; + pthread_condattr_t cattr; + int fd; + + fn = pool_filename(uid); + if (fn == NULL) + goto fail_fn; + + mode = IS_GSPP(uid) ? 0660 : 0600; + mask = umask(0); + + pool = __pool_create(fn, O_CREAT | O_EXCL | O_RDWR, uid, mode); + + umask(mask); + + if (pool == NULL) + goto fail_pool; + + fd = shm_open(fn, O_RDWR, 0); + if (fd >= 0) { + fchown(fd, uid, gid); + fchmod(fd, mode); + close(fd); + } + + if (pthread_mutexattr_init(&mattr)) + goto fail_mattr; + + pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED); +#ifdef HAVE_ROBUST_MUTEX + pthread_mutexattr_setrobust(&mattr, PTHREAD_MUTEX_ROBUST); +#endif + if (pthread_mutex_init(&pool->hdr->mtx, &mattr)) + goto fail_mutex; + + if (pthread_condattr_init(&cattr)) + goto fail_cattr; + + pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED); +#ifndef __APPLE__ + pthread_condattr_setclock(&cattr, PTHREAD_COND_CLOCK); +#endif + if (pthread_cond_init(&pool->hdr->healthy, &cattr)) + goto fail_healthy; + + pool->hdr->pid = getpid(); + STORE(&pool->hdr->initialized, 0); + + init_size_classes(pool); + + pthread_mutexattr_destroy(&mattr); + pthread_condattr_destroy(&cattr); + free(fn); + + return pool; + + fail_healthy: + pthread_condattr_destroy(&cattr); + fail_cattr: + pthread_mutex_destroy(&pool->hdr->mtx); + fail_mutex: + pthread_mutexattr_destroy(&mattr); + fail_mattr: + ssm_pool_close(pool); + shm_unlink(fn); + fail_pool: + free(fn); + fail_fn: + return NULL; +} + +struct ssm_pool * ssm_pool_open(uid_t uid) +{ + struct ssm_pool * pool; + char * fn; + + fn = pool_filename(uid); + if (fn == NULL) + return NULL; + + pool = __pool_create(fn, O_RDWR, uid, 0); + if (pool != NULL) + init_size_classes(pool); + + free(fn); + + return pool; +} + +void ssm_pool_gspp_purge(void) +{ + char * fn; + + fn = pool_filename(SSM_GSPP_UID); + if (fn == NULL) + return; + + shm_unlink(fn); + free(fn); +} + +int ssm_pool_mlock(struct ssm_pool * pool) +{ + size_t file_size; + + assert(pool != NULL); + + file_size = GET_POOL_FILE_SIZE(pool->uid); + + return mlock(pool->shm_base, file_size); +} + +ssize_t ssm_pool_alloc(struct ssm_pool * pool, + size_t count, + uint8_t ** ptr, + struct ssm_pk_buff ** spb) +{ + int idx; + + assert(pool != NULL); + assert(spb != NULL); + + idx = select_size_class(pool, count); + if (idx >= 0) + return alloc_from_sc(pool, idx, count, ptr, spb); + + return -EMSGSIZE; +} + +ssize_t ssm_pool_alloc_b(struct ssm_pool * pool, + size_t count, + uint8_t ** ptr, + struct ssm_pk_buff ** spb, + const struct timespec * abstime) +{ + int idx; + + assert(pool != NULL); + assert(spb != NULL); + + idx = select_size_class(pool, count); + if (idx >= 0) + return alloc_from_sc_b(pool, idx, count, ptr, spb, abstime); + + return -EMSGSIZE; +} + +ssize_t ssm_pool_read(uint8_t ** dst, + struct ssm_pool * pool, + size_t off) +{ + struct ssm_pk_buff * blk; + + assert(dst != NULL); + assert(pool != NULL); + + blk = OFFSET_TO_PTR(pool->pool_base, off); + if (blk == NULL) + return -EINVAL; + + *dst = blk->data + blk->pk_head; + + return (ssize_t) (blk->pk_tail - blk->pk_head); +} + +struct ssm_pk_buff * ssm_pool_get(struct ssm_pool * pool, + size_t off) +{ + struct ssm_pk_buff * blk; + + assert(pool != NULL); + + if (off == 0 || off >= pool->total_size) + return NULL; + + blk = OFFSET_TO_PTR(pool->pool_base, off); + if (blk == NULL) + return NULL; + + if (LOAD(&blk->refcount) == 0) + return NULL; + + return blk; +} + +int ssm_pool_remove(struct ssm_pool * pool, + size_t off) +{ + struct ssm_pk_buff * blk; + struct _ssm_size_class * sc; + struct _ssm_shard * shard; + int sc_idx; + int shard_idx; + uint16_t old_ref; + + assert(pool != NULL); + + if (off == 0 || off >= pool->total_size) + return -EINVAL; + + blk = OFFSET_TO_PTR(pool->pool_base, off); + if (blk == NULL) + return -EINVAL; + + sc_idx = find_size_class_for_offset(pool, off); + if (sc_idx < 0) + return -EINVAL; + + sc = &pool->hdr->size_classes[sc_idx]; + + /* Free to allocator's shard (lazy distribution in action) */ + shard_idx = GET_SHARD_FOR_PID(blk->allocator_pid); + shard = &sc->shards[shard_idx]; + + robust_mutex_lock(&shard->mtx); + + old_ref = FETCH_SUB(&blk->refcount, 1); + if (old_ref > 1) { + /* Still referenced */ + pthread_mutex_unlock(&shard->mtx); + return 0; + } + + blk->allocator_pid = 0; +#ifdef CONFIG_OUROBOROS_DEBUG + if (old_ref == 0) { + /* Underflow - double free attempt */ + pthread_mutex_unlock(&shard->mtx); + abort(); + } + + /* Poison fields to detect use-after-free */ + blk->pk_head = 0xDEAD; + blk->pk_tail = 0xBEEF; +#endif + list_add_head(&shard->free_list, blk, pool->pool_base); + FETCH_ADD(&shard->free_count, 1); + + pthread_cond_signal(&shard->cond); + + pthread_mutex_unlock(&shard->mtx); + + return 0; +} + +size_t ssm_pk_buff_get_idx(struct ssm_pk_buff * spb) +{ + assert(spb != NULL); + + return spb->off; +} + +uint8_t * ssm_pk_buff_head(struct ssm_pk_buff * spb) +{ + assert(spb != NULL); + + return spb->data + spb->pk_head; +} + +uint8_t * ssm_pk_buff_tail(struct ssm_pk_buff * spb) +{ + assert(spb != NULL); + + return spb->data + spb->pk_tail; +} + +size_t ssm_pk_buff_len(struct ssm_pk_buff * spb) +{ + assert(spb != NULL); + + return spb->pk_tail - spb->pk_head; +} + +uint8_t * ssm_pk_buff_head_alloc(struct ssm_pk_buff * spb, + size_t size) +{ + assert(spb != NULL); + + if (spb->pk_head < size) + return NULL; + + spb->pk_head -= size; + + return spb->data + spb->pk_head; +} + +uint8_t * ssm_pk_buff_tail_alloc(struct ssm_pk_buff * spb, + size_t size) +{ + uint8_t * buf; + + assert(spb != NULL); + + if (spb->pk_tail + size >= spb->size) + return NULL; + + buf = spb->data + spb->pk_tail; + + spb->pk_tail += size; + + return buf; +} + +uint8_t * ssm_pk_buff_head_release(struct ssm_pk_buff * spb, + size_t size) +{ + uint8_t * buf; + + assert(spb != NULL); + assert(!(size > spb->pk_tail - spb->pk_head)); + + buf = spb->data + spb->pk_head; + + spb->pk_head += size; + + return buf; +} + +uint8_t * ssm_pk_buff_tail_release(struct ssm_pk_buff * spb, + size_t size) +{ + assert(spb != NULL); + assert(!(size > spb->pk_tail - spb->pk_head)); + + spb->pk_tail -= size; + + return spb->data + spb->pk_tail; +} + +void ssm_pk_buff_truncate(struct ssm_pk_buff * spb, + size_t len) +{ + assert(spb != NULL); + assert(len <= spb->size); + + spb->pk_tail = spb->pk_head + len; +} + +int ssm_pk_buff_wait_ack(struct ssm_pk_buff * spb) +{ + assert(spb != NULL); + + FETCH_ADD(&spb->refcount, 1); + + return 0; +} + +int ssm_pk_buff_ack(struct ssm_pk_buff * spb) +{ + assert(spb != NULL); + + FETCH_SUB(&spb->refcount, 1); + + return 0; +} diff --git a/src/lib/ssm/rbuff.c b/src/lib/ssm/rbuff.c new file mode 100644 index 00000000..e18f8ba7 --- /dev/null +++ b/src/lib/ssm/rbuff.c @@ -0,0 +1,449 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * Ring buffer implementations for incoming packets + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#define _POSIX_C_SOURCE 200809L + +#include "config.h" +#include "ssm.h" + +#include <ouroboros/ssm_rbuff.h> +#include <ouroboros/lockfile.h> +#include <ouroboros/errno.h> +#include <ouroboros/fccntl.h> +#include <ouroboros/pthread.h> +#include <ouroboros/time.h> + +#include <assert.h> +#include <fcntl.h> +#include <signal.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/mman.h> +#include <sys/stat.h> + +#define FN_MAX_CHARS 255 + +#define SSM_RBUFF_FILESIZE ((SSM_RBUFF_SIZE) * sizeof(ssize_t) \ + + 3 * sizeof(size_t) \ + + sizeof(pthread_mutex_t) \ + + 2 * sizeof(pthread_cond_t)) + +#define MODB(x) ((x) & (SSM_RBUFF_SIZE - 1)) + +#define LOAD_RELAXED(ptr) (__atomic_load_n(ptr, __ATOMIC_RELAXED)) +#define LOAD_ACQUIRE(ptr) (__atomic_load_n(ptr, __ATOMIC_ACQUIRE)) +#define STORE_RELEASE(ptr, val) \ + (__atomic_store_n(ptr, val, __ATOMIC_RELEASE)) + +#define HEAD(rb) (rb->shm_base[LOAD_RELAXED(rb->head)]) +#define TAIL(rb) (rb->shm_base[LOAD_RELAXED(rb->tail)]) +#define HEAD_IDX(rb) (LOAD_ACQUIRE(rb->head)) +#define TAIL_IDX(rb) (LOAD_ACQUIRE(rb->tail)) +#define ADVANCE_HEAD(rb) \ + (STORE_RELEASE(rb->head, MODB(LOAD_RELAXED(rb->head) + 1))) +#define ADVANCE_TAIL(rb) \ + (STORE_RELEASE(rb->tail, MODB(LOAD_RELAXED(rb->tail) + 1))) +#define QUEUED(rb) (MODB(HEAD_IDX(rb) - TAIL_IDX(rb))) +#define IS_FULL(rb) (QUEUED(rb) == (SSM_RBUFF_SIZE - 1)) +#define IS_EMPTY(rb) (HEAD_IDX(rb) == TAIL_IDX(rb)) + +struct ssm_rbuff { + ssize_t * shm_base; /* start of shared memory */ + size_t * head; /* start of ringbuffer */ + size_t * tail; + size_t * acl; /* access control */ + pthread_mutex_t * mtx; /* lock for cond vars only */ + pthread_cond_t * add; /* signal when new data */ + pthread_cond_t * del; /* signal when data removed */ + pid_t pid; /* pid of the owner */ + int flow_id; /* flow_id of the flow */ +}; + +#define MM_FLAGS (PROT_READ | PROT_WRITE) + +static struct ssm_rbuff * rbuff_create(pid_t pid, + int flow_id, + int flags) +{ + struct ssm_rbuff * rb; + int fd; + ssize_t * shm_base; + char fn[FN_MAX_CHARS]; + + sprintf(fn, SSM_RBUFF_PREFIX "%d.%d", pid, flow_id); + + rb = malloc(sizeof(*rb)); + if (rb == NULL) + goto fail_malloc; + + fd = shm_open(fn, flags, 0666); + if (fd == -1) + goto fail_open; + + if ((flags & O_CREAT) && ftruncate(fd, SSM_RBUFF_FILESIZE) < 0) + goto fail_truncate; + + shm_base = mmap(NULL, SSM_RBUFF_FILESIZE, MM_FLAGS, MAP_SHARED, fd, 0); + + close(fd); + + rb->shm_base = shm_base; + rb->head = (size_t *) (rb->shm_base + (SSM_RBUFF_SIZE)); + rb->tail = (size_t *) (rb->head + 1); + rb->acl = (size_t *) (rb->tail + 1); + rb->mtx = (pthread_mutex_t *) (rb->acl + 1); + rb->add = (pthread_cond_t *) (rb->mtx + 1); + rb->del = rb->add + 1; + rb->pid = pid; + rb->flow_id = flow_id; + + return rb; + + fail_truncate: + close(fd); + if (flags & O_CREAT) + shm_unlink(fn); + fail_open: + free(rb); + fail_malloc: + return NULL; +} + +static void rbuff_destroy(struct ssm_rbuff * rb) +{ + munmap(rb->shm_base, SSM_RBUFF_FILESIZE); + + free(rb); +} + +struct ssm_rbuff * ssm_rbuff_create(pid_t pid, + int flow_id) +{ + struct ssm_rbuff * rb; + pthread_mutexattr_t mattr; + pthread_condattr_t cattr; + mode_t mask; + + mask = umask(0); + + rb = rbuff_create(pid, flow_id, O_CREAT | O_EXCL | O_RDWR); + + umask(mask); + + if (rb == NULL) + goto fail_rb; + + if (pthread_mutexattr_init(&mattr)) + goto fail_mattr; + + pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED); +#ifdef HAVE_ROBUST_MUTEX + pthread_mutexattr_setrobust(&mattr, PTHREAD_MUTEX_ROBUST); +#endif + if (pthread_mutex_init(rb->mtx, &mattr)) + goto fail_mutex; + + if (pthread_condattr_init(&cattr)) + goto fail_cattr; + + pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED); +#ifndef __APPLE__ + pthread_condattr_setclock(&cattr, PTHREAD_COND_CLOCK); +#endif + if (pthread_cond_init(rb->add, &cattr)) + goto fail_add; + + if (pthread_cond_init(rb->del, &cattr)) + goto fail_del; + + *rb->acl = ACL_RDWR; + *rb->head = 0; + *rb->tail = 0; + + rb->pid = pid; + rb->flow_id = flow_id; + + pthread_mutexattr_destroy(&mattr); + pthread_condattr_destroy(&cattr); + + return rb; + + fail_del: + pthread_cond_destroy(rb->add); + fail_add: + pthread_condattr_destroy(&cattr); + fail_cattr: + pthread_mutex_destroy(rb->mtx); + fail_mutex: + pthread_mutexattr_destroy(&mattr); + fail_mattr: + ssm_rbuff_destroy(rb); + fail_rb: + return NULL; +} + +void ssm_rbuff_destroy(struct ssm_rbuff * rb) +{ + char fn[FN_MAX_CHARS]; + + assert(rb != NULL); + + sprintf(fn, SSM_RBUFF_PREFIX "%d.%d", rb->pid, rb->flow_id); + + ssm_rbuff_close(rb); + + shm_unlink(fn); +} + +struct ssm_rbuff * ssm_rbuff_open(pid_t pid, + int flow_id) +{ + return rbuff_create(pid, flow_id, O_RDWR); +} + +void ssm_rbuff_close(struct ssm_rbuff * rb) +{ + assert(rb); + + rbuff_destroy(rb); +} + +int ssm_rbuff_write(struct ssm_rbuff * rb, + size_t idx) +{ + size_t acl; + bool was_empty; + int ret = 0; + + assert(rb != NULL); + + acl = __atomic_load_n(rb->acl, __ATOMIC_SEQ_CST); + if (acl != ACL_RDWR) { + if (acl & ACL_FLOWDOWN) { + ret = -EFLOWDOWN; + goto fail_acl; + } + if (acl & ACL_RDONLY) { + ret = -ENOTALLOC; + goto fail_acl; + } + } + + robust_mutex_lock(rb->mtx); + + if (IS_FULL(rb)) { + ret = -EAGAIN; + goto fail_mutex; + } + + was_empty = IS_EMPTY(rb); + + HEAD(rb) = (ssize_t) idx; + ADVANCE_HEAD(rb); + + if (was_empty) + pthread_cond_broadcast(rb->add); + + pthread_mutex_unlock(rb->mtx); + + return 0; + + fail_mutex: + pthread_mutex_unlock(rb->mtx); + fail_acl: + return ret; +} + +int ssm_rbuff_write_b(struct ssm_rbuff * rb, + size_t idx, + const struct timespec * abstime) +{ + size_t acl; + int ret = 0; + bool was_empty; + + assert(rb != NULL); + + acl = __atomic_load_n(rb->acl, __ATOMIC_SEQ_CST); + if (acl != ACL_RDWR) { + if (acl & ACL_FLOWDOWN) { + ret = -EFLOWDOWN; + goto fail_acl; + } + if (acl & ACL_RDONLY) { + ret = -ENOTALLOC; + goto fail_acl; + } + } + + robust_mutex_lock(rb->mtx); + + pthread_cleanup_push(__cleanup_mutex_unlock, rb->mtx); + + while (IS_FULL(rb) && ret != -ETIMEDOUT) { + acl = __atomic_load_n(rb->acl, __ATOMIC_SEQ_CST); + if (acl & ACL_FLOWDOWN) { + ret = -EFLOWDOWN; + break; + } + ret = -robust_wait(rb->del, rb->mtx, abstime); + } + + pthread_cleanup_pop(false); + + if (ret != -ETIMEDOUT && ret != -EFLOWDOWN) { + was_empty = IS_EMPTY(rb); + HEAD(rb) = (ssize_t) idx; + ADVANCE_HEAD(rb); + if (was_empty) + pthread_cond_broadcast(rb->add); + } + + pthread_mutex_unlock(rb->mtx); + + fail_acl: + return ret; +} + +static int check_rb_acl(struct ssm_rbuff * rb) +{ + size_t acl; + + assert(rb != NULL); + + acl = __atomic_load_n(rb->acl, __ATOMIC_SEQ_CST); + + if (acl & ACL_FLOWDOWN) + return -EFLOWDOWN; + + if (acl & ACL_FLOWPEER) + return -EFLOWPEER; + + return -EAGAIN; +} + +ssize_t ssm_rbuff_read(struct ssm_rbuff * rb) +{ + ssize_t ret; + + assert(rb != NULL); + + if (IS_EMPTY(rb)) + return check_rb_acl(rb); + + robust_mutex_lock(rb->mtx); + + ret = TAIL(rb); + ADVANCE_TAIL(rb); + + pthread_cond_broadcast(rb->del); + + pthread_mutex_unlock(rb->mtx); + + return ret; +} + +ssize_t ssm_rbuff_read_b(struct ssm_rbuff * rb, + const struct timespec * abstime) +{ + ssize_t idx = -1; + size_t acl; + + assert(rb != NULL); + + acl = __atomic_load_n(rb->acl, __ATOMIC_SEQ_CST); + if (IS_EMPTY(rb) && (acl & ACL_FLOWDOWN)) + return -EFLOWDOWN; + + robust_mutex_lock(rb->mtx); + + pthread_cleanup_push(__cleanup_mutex_unlock, rb->mtx); + + while (IS_EMPTY(rb) && + idx != -ETIMEDOUT && + check_rb_acl(rb) == -EAGAIN) { + idx = -robust_wait(rb->add, rb->mtx, abstime); + } + + pthread_cleanup_pop(false); + + if (!IS_EMPTY(rb)) { + idx = TAIL(rb); + ADVANCE_TAIL(rb); + pthread_cond_broadcast(rb->del); + } else if (idx != -ETIMEDOUT) { + idx = check_rb_acl(rb); + } + + pthread_mutex_unlock(rb->mtx); + + assert(idx != -EAGAIN); + + return idx; +} + +void ssm_rbuff_set_acl(struct ssm_rbuff * rb, + uint32_t flags) +{ + assert(rb != NULL); + + __atomic_store_n(rb->acl, (size_t) flags, __ATOMIC_SEQ_CST); +} + +uint32_t ssm_rbuff_get_acl(struct ssm_rbuff * rb) +{ + assert(rb != NULL); + + return (uint32_t) __atomic_load_n(rb->acl, __ATOMIC_SEQ_CST); +} + +void ssm_rbuff_fini(struct ssm_rbuff * rb) +{ + assert(rb != NULL); + + robust_mutex_lock(rb->mtx); + + pthread_cleanup_push(__cleanup_mutex_unlock, rb->mtx); + + while (!IS_EMPTY(rb)) + robust_wait(rb->del, rb->mtx, NULL); + + pthread_cleanup_pop(true); +} + +size_t ssm_rbuff_queued(struct ssm_rbuff * rb) +{ + assert(rb != NULL); + + return QUEUED(rb); +} + +int ssm_rbuff_mlock(struct ssm_rbuff * rb) +{ + assert(rb != NULL); + + return mlock(rb->shm_base, SSM_RBUFF_FILESIZE); +} diff --git a/src/lib/ssm/ssm.h.in b/src/lib/ssm/ssm.h.in new file mode 100644 index 00000000..b9246c8b --- /dev/null +++ b/src/lib/ssm/ssm.h.in @@ -0,0 +1,171 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Secure Shared Memory configuration + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * version 2.1 as published by the Free Software Foundation. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#ifndef OUROBOROS_LIB_SSM_H +#define OUROBOROS_LIB_SSM_H + +#include <stddef.h> +#include <stdint.h> +#include <stdatomic.h> +#include <sys/types.h> + +/* Pool naming configuration */ +#define SSM_PREFIX "@SSM_PREFIX@" +#define SSM_GSPP_NAME "@SSM_GSPP_NAME@" +#define SSM_PUP_NAME_FMT "@SSM_PUP_NAME_FMT@" +#define SSM_GSPP_UID 0 + +/* Legacy SSM constants */ +#define SSM_RBUFF_PREFIX "@SSM_RBUFF_PREFIX@" +#define SSM_FLOW_SET_PREFIX "@SSM_FLOW_SET_PREFIX@" +#define SSM_POOL_NAME "@SSM_POOL_NAME@" +#define SSM_POOL_BLOCKS @SSM_POOL_BLOCKS@ +#define SSM_RBUFF_SIZE @SSM_RBUFF_SIZE@ + +/* Packet buffer space reservation */ +#define SSM_PK_BUFF_HEADSPACE @SSM_PK_BUFF_HEADSPACE@ +#define SSM_PK_BUFF_TAILSPACE @SSM_PK_BUFF_TAILSPACE@ + +/* Global Shared Packet Pool (GSPP) - for privileged processes */ +#define SSM_GSPP_256_BLOCKS @SSM_GSPP_256_BLOCKS@ +#define SSM_GSPP_512_BLOCKS @SSM_GSPP_512_BLOCKS@ +#define SSM_GSPP_1K_BLOCKS @SSM_GSPP_1K_BLOCKS@ +#define SSM_GSPP_2K_BLOCKS @SSM_GSPP_2K_BLOCKS@ +#define SSM_GSPP_4K_BLOCKS @SSM_GSPP_4K_BLOCKS@ +#define SSM_GSPP_16K_BLOCKS @SSM_GSPP_16K_BLOCKS@ +#define SSM_GSPP_64K_BLOCKS @SSM_GSPP_64K_BLOCKS@ +#define SSM_GSPP_256K_BLOCKS @SSM_GSPP_256K_BLOCKS@ +#define SSM_GSPP_1M_BLOCKS @SSM_GSPP_1M_BLOCKS@ +#define SSM_GSPP_TOTAL_SIZE @SSM_GSPP_TOTAL_SIZE@ + +/* Per-User Pool (PUP) - for unprivileged applications */ +#define SSM_PUP_256_BLOCKS @SSM_PUP_256_BLOCKS@ +#define SSM_PUP_512_BLOCKS @SSM_PUP_512_BLOCKS@ +#define SSM_PUP_1K_BLOCKS @SSM_PUP_1K_BLOCKS@ +#define SSM_PUP_2K_BLOCKS @SSM_PUP_2K_BLOCKS@ +#define SSM_PUP_4K_BLOCKS @SSM_PUP_4K_BLOCKS@ +#define SSM_PUP_16K_BLOCKS @SSM_PUP_16K_BLOCKS@ +#define SSM_PUP_64K_BLOCKS @SSM_PUP_64K_BLOCKS@ +#define SSM_PUP_256K_BLOCKS @SSM_PUP_256K_BLOCKS@ +#define SSM_PUP_1M_BLOCKS @SSM_PUP_1M_BLOCKS@ +#define SSM_PUP_TOTAL_SIZE @SSM_PUP_TOTAL_SIZE@ + +/* Legacy pool blocks (same as GSPP for compatibility) */ +#define SSM_POOL_256_BLOCKS @SSM_POOL_256_BLOCKS@ +#define SSM_POOL_512_BLOCKS @SSM_POOL_512_BLOCKS@ +#define SSM_POOL_1K_BLOCKS @SSM_POOL_1K_BLOCKS@ +#define SSM_POOL_2K_BLOCKS @SSM_POOL_2K_BLOCKS@ +#define SSM_POOL_4K_BLOCKS @SSM_POOL_4K_BLOCKS@ +#define SSM_POOL_16K_BLOCKS @SSM_POOL_16K_BLOCKS@ +#define SSM_POOL_64K_BLOCKS @SSM_POOL_64K_BLOCKS@ +#define SSM_POOL_256K_BLOCKS @SSM_POOL_256K_BLOCKS@ +#define SSM_POOL_1M_BLOCKS @SSM_POOL_1M_BLOCKS@ +#define SSM_POOL_TOTAL_SIZE @SSM_POOL_TOTAL_SIZE@ + +/* Size class configuration */ +#define SSM_POOL_MAX_CLASSES 9 +#define SSM_POOL_SHARDS @SSM_POOL_SHARDS@ + +/* Internal structures - exposed for testing */ +#ifdef __cplusplus +extern "C" { +#endif + +#include <errno.h> +#include <pthread.h> + +#include <ouroboros/pthread.h> + +static __inline__ void robust_mutex_lock(pthread_mutex_t * mtx) +{ +#ifndef HAVE_ROBUST_MUTEX + pthread_mutex_lock(mtx); +#else + if (pthread_mutex_lock(mtx) == EOWNERDEAD) + pthread_mutex_consistent(mtx); +#endif +} + +static __inline__ int robust_wait(pthread_cond_t * cond, + pthread_mutex_t * mtx, + const struct timespec * abstime) +{ + int ret = __timedwait(cond, mtx, abstime); +#ifdef HAVE_ROBUST_MUTEX + if (ret == EOWNERDEAD) + pthread_mutex_consistent(mtx); +#endif + return ret; +} + +/* Packet buffer structure used by pool, rbuff, and tests */ +struct ssm_pk_buff { + uint32_t next_offset; /* List linkage (pool < 4GB) */ + uint16_t refcount; /* Reference count (app + rtx) */ + pid_t allocator_pid; /* For orphan detection */ + uint32_t size; /* Block size (max 1MB) */ + uint32_t pk_head; /* Head offset into data */ + uint32_t pk_tail; /* Tail offset into data */ + uint32_t off; /* Block offset in pool */ + uint8_t data[]; /* Packet data */ +}; + +/* Size class configuration table */ +struct ssm_size_class_cfg { + size_t size; + size_t blocks; +}; + +struct _ssm_list_head { + uint32_t head_offset; + uint32_t count; +}; + +struct _ssm_shard { + pthread_mutex_t mtx; + pthread_cond_t cond; + struct _ssm_list_head free_list; + size_t free_count; +}; + +struct _ssm_size_class { + struct _ssm_shard shards[SSM_POOL_SHARDS]; + size_t object_size; + size_t pool_start; + size_t pool_size; + size_t object_count; +}; + +struct _ssm_pool_hdr { + pthread_mutex_t mtx; + pthread_cond_t healthy; + pid_t pid; + uint32_t initialized; + void * mapped_addr; + struct _ssm_size_class size_classes[SSM_POOL_MAX_CLASSES]; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* OUROBOROS_LIB_SSM_H */ diff --git a/src/lib/ssm/tests/CMakeLists.txt b/src/lib/ssm/tests/CMakeLists.txt new file mode 100644 index 00000000..5cac70d1 --- /dev/null +++ b/src/lib/ssm/tests/CMakeLists.txt @@ -0,0 +1,22 @@ +get_filename_component(PARENT_PATH ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) +get_filename_component(PARENT_DIR ${PARENT_PATH} NAME) + +compute_test_prefix() + +create_test_sourcelist(${PARENT_DIR}_tests test_suite.c + # Add new tests here + pool_test.c + pool_sharding_test.c + rbuff_test.c + flow_set_test.c + ) + +add_executable(${PARENT_DIR}_test ${${PARENT_DIR}_tests}) + +disable_test_logging_for_target(${PARENT_DIR}_test) +target_link_libraries(${PARENT_DIR}_test ouroboros-common) + +add_dependencies(build_tests ${PARENT_DIR}_test) + +ouroboros_register_tests(TARGET ${PARENT_DIR}_test TESTS ${${PARENT_DIR}_tests} + ENVIRONMENT "OUROBOROS_TEST_POOL_SUFFIX=.test") diff --git a/src/lib/ssm/tests/flow_set_test.c b/src/lib/ssm/tests/flow_set_test.c new file mode 100644 index 00000000..f9084d3c --- /dev/null +++ b/src/lib/ssm/tests/flow_set_test.c @@ -0,0 +1,255 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * Test of the SSM flow set + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#if defined(__linux__) || defined(__CYGWIN__) +#define _DEFAULT_SOURCE +#else +#define _POSIX_C_SOURCE 200112L +#endif + +#include "config.h" +#include "ssm.h" + +#include <test/test.h> +#include <ouroboros/ssm_flow_set.h> +#include <ouroboros/errno.h> +#include <ouroboros/time.h> + +#include <stdio.h> +#include <unistd.h> +#include <pthread.h> + +static int test_ssm_flow_set_create_destroy(void) +{ + struct ssm_flow_set * set; + pid_t pid; + + TEST_START(); + + pid = getpid(); + + set = ssm_flow_set_create(pid); + if (set == NULL) { + printf("Failed to create flow set.\n"); + goto fail; + } + + ssm_flow_set_destroy(set); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; +fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_ssm_flow_set_add_del_has(void) +{ + struct ssm_flow_set * set; + pid_t pid; + size_t idx = 0; + int flow_id = 42; + + TEST_START(); + + pid = getpid(); + + set = ssm_flow_set_create(pid); + if (set == NULL) { + printf("Failed to create flow set.\n"); + goto fail; + } + + if (ssm_flow_set_has(set, idx, flow_id)) { + printf("Flow should not be in set initially.\n"); + goto fail_destroy; + } + + if (ssm_flow_set_add(set, idx, flow_id) < 0) { + printf("Failed to add flow to set.\n"); + goto fail_destroy; + } + + if (!ssm_flow_set_has(set, idx, flow_id)) { + printf("Flow should be in set after add.\n"); + goto fail_destroy; + } + + /* Adding same flow again should fail */ + if (ssm_flow_set_add(set, idx, flow_id) != -EPERM) { + printf("Should not be able to add flow twice.\n"); + goto fail_destroy; + } + + ssm_flow_set_del(set, idx, flow_id); + + if (ssm_flow_set_has(set, idx, flow_id)) { + printf("Flow should not be in set after delete.\n"); + goto fail_destroy; + } + + ssm_flow_set_destroy(set); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; +fail_destroy: + ssm_flow_set_destroy(set); +fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_ssm_flow_set_zero(void) +{ + struct ssm_flow_set * set; + pid_t pid; + size_t idx = 0; + int flow_id1 = 10; + int flow_id2 = 20; + + TEST_START(); + + pid = getpid(); + + set = ssm_flow_set_create(pid); + if (set == NULL) { + printf("Failed to create flow set.\n"); + goto fail; + } + + if (ssm_flow_set_add(set, idx, flow_id1) < 0) { + printf("Failed to add flow1 to set.\n"); + goto fail_destroy; + } + + if (ssm_flow_set_add(set, idx, flow_id2) < 0) { + printf("Failed to add flow2 to set.\n"); + goto fail_destroy; + } + + ssm_flow_set_zero(set, idx); + + if (ssm_flow_set_has(set, idx, flow_id1)) { + printf("Flow1 should not be in set after zero.\n"); + goto fail_destroy; + } + + if (ssm_flow_set_has(set, idx, flow_id2)) { + printf("Flow2 should not be in set after zero.\n"); + goto fail_destroy; + } + + ssm_flow_set_destroy(set); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; +fail_destroy: + ssm_flow_set_destroy(set); +fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_ssm_flow_set_notify_wait(void) +{ + struct ssm_flow_set * set; + pid_t pid; + size_t idx = 0; + int flow_id = 100; + struct flowevent events[SSM_RBUFF_SIZE]; + struct timespec timeout; + ssize_t ret; + + TEST_START(); + + pid = getpid(); + + set = ssm_flow_set_create(pid); + if (set == NULL) { + printf("Failed to create flow set.\n"); + goto fail; + } + + if (ssm_flow_set_add(set, idx, flow_id) < 0) { + printf("Failed to add flow to set.\n"); + goto fail_destroy; + } + + /* Test immediate timeout when no events */ + clock_gettime(PTHREAD_COND_CLOCK, &timeout); + ret = ssm_flow_set_wait(set, idx, events, &timeout); + if (ret != -ETIMEDOUT) { + printf("Wait should timeout immediately when no events.\n"); + goto fail_destroy; + } + + /* Notify an event */ + ssm_flow_set_notify(set, flow_id, FLOW_PKT); + + /* Should be able to read the event immediately */ + clock_gettime(PTHREAD_COND_CLOCK, &timeout); + ts_add(&timeout, &timeout, &((struct timespec) {1, 0})); + + ret = ssm_flow_set_wait(set, idx, events, &timeout); + if (ret != 1) { + printf("Wait should return 1 event, got %zd.\n", ret); + goto fail_destroy; + } + + if (events[0].flow_id != flow_id) { + printf("Event flow_id mismatch: expected %d, got %d.\n", + flow_id, events[0].flow_id); + goto fail_destroy; + } + + if (events[0].event != FLOW_PKT) { + printf("Event type mismatch: expected %d, got %d.\n", + FLOW_PKT, events[0].event); + goto fail_destroy; + } + + ssm_flow_set_destroy(set); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; +fail_destroy: + ssm_flow_set_destroy(set); +fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int flow_set_test(int argc, + char ** argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + + ret |= test_ssm_flow_set_create_destroy(); + ret |= test_ssm_flow_set_add_del_has(); + ret |= test_ssm_flow_set_zero(); + ret |= test_ssm_flow_set_notify_wait(); + + return ret; +} diff --git a/src/lib/ssm/tests/pool_sharding_test.c b/src/lib/ssm/tests/pool_sharding_test.c new file mode 100644 index 00000000..46eecd8d --- /dev/null +++ b/src/lib/ssm/tests/pool_sharding_test.c @@ -0,0 +1,496 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Test of the SSM pool sharding with fallback + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#if defined(__linux__) || defined(__CYGWIN__) +#define _DEFAULT_SOURCE +#else +#define _POSIX_C_SOURCE 200112L +#endif + +#include "config.h" +#include "ssm.h" + +#include <test/test.h> +#include <ouroboros/ssm_pool.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <stdbool.h> +#include <sys/wait.h> +#include <sys/types.h> +#include <signal.h> + +#define TEST_SIZE 256 + +/* Helper to get pool header for inspection */ +static struct _ssm_pool_hdr * get_pool_hdr(struct ssm_pool * pool) +{ + /* ssm_pool is opaque, but we know its layout: + * uint8_t * shm_base + * struct _ssm_pool_hdr * hdr + * void * pool_base + */ + struct _ssm_pool_hdr ** hdr_ptr = + (struct _ssm_pool_hdr **)((uint8_t *)pool + sizeof(void *)); + + return *hdr_ptr; +} + +static int test_lazy_distribution(void) +{ + struct ssm_pool * pool; + struct _ssm_pool_hdr * hdr; + struct _ssm_size_class * sc; + int i; + int sc_idx; + + TEST_START(); + + pool = ssm_pool_create(0, getgid()); + if (pool == NULL) { + printf("Failed to create pool.\n"); + goto fail; + } + + hdr = get_pool_hdr(pool); + if (hdr == NULL) { + printf("Failed to get pool header.\n"); + goto fail_pool; + } + + /* Find the first size class with blocks */ + sc_idx = -1; + for (i = 0; i < SSM_POOL_MAX_CLASSES; i++) { + if (hdr->size_classes[i].object_count > 0) { + sc_idx = i; + break; + } + } + + if (sc_idx < 0) { + printf("No size classes configured.\n"); + for (i = 0; i < SSM_POOL_MAX_CLASSES; i++) { + printf(" Class %d: count=%zu\n", i, + hdr->size_classes[i].object_count); + } + goto fail_pool; + } + + sc = &hdr->size_classes[sc_idx]; + + /* Verify all blocks start in shard 0 */ + if (sc->shards[0].free_count == 0) { + printf("Shard 0 should have all blocks initially.\n"); + goto fail_pool; + } + + /* Verify other shards are empty */ + for (i = 1; i < SSM_POOL_SHARDS; i++) { + if (sc->shards[i].free_count != 0) { + printf("Shard %d should be empty, has %zu.\n", + i, sc->shards[i].free_count); + goto fail_pool; + } + } + + ssm_pool_destroy(pool); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_pool: + ssm_pool_destroy(pool); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_shard_migration(void) +{ + struct ssm_pool * pool; + struct _ssm_pool_hdr * hdr; + struct _ssm_size_class * sc; + struct ssm_pk_buff * spb; + uint8_t * ptr; + ssize_t off; + int shard_idx; + int sc_idx; + int i; + + TEST_START(); + + pool = ssm_pool_create(0, getgid()); + if (pool == NULL) { + printf("Failed to create pool.\n"); + goto fail; + } + + hdr = get_pool_hdr(pool); + + /* Find the first size class with blocks */ + sc_idx = -1; + for (i = 0; i < SSM_POOL_MAX_CLASSES; i++) { + if (hdr->size_classes[i].object_count > 0) { + sc_idx = i; + break; + } + } + + if (sc_idx < 0) { + printf("No size classes configured.\n"); + goto fail; + } + + sc = &hdr->size_classes[sc_idx]; + + /* Allocate from this process */ + off = ssm_pool_alloc(pool, TEST_SIZE, &ptr, &spb); + if (off < 0) { + printf("Allocation failed: %zd.\n", off); + goto fail_pool; + } + + /* Free it - should go to this process's shard */ + shard_idx = getpid() % SSM_POOL_SHARDS; + if (ssm_pool_remove(pool, off) != 0) { + printf("Remove failed.\n"); + goto fail_pool; + } + + /* Verify block migrated away from shard 0 or in allocator's shard */ + if (sc->shards[shard_idx].free_count == 0 && + sc->shards[0].free_count == 0) { + printf("Block should have been freed to a shard.\n"); + goto fail_pool; + } + + ssm_pool_destroy(pool); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_pool: + ssm_pool_destroy(pool); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_fallback_stealing(void) +{ + struct ssm_pool * pool; + struct _ssm_pool_hdr * hdr; + struct _ssm_size_class * sc; + struct ssm_pk_buff ** spbs; + uint8_t ** ptrs; + size_t total_blocks; + size_t total_free; + size_t i; + int sc_idx; + int c; + + TEST_START(); + + pool = ssm_pool_create(0, getgid()); + if (pool == NULL) { + printf("Failed to create pool.\n"); + goto fail; + } + + hdr = get_pool_hdr(pool); + + /* Find the first size class with blocks */ + sc_idx = -1; + for (c = 0; c < SSM_POOL_MAX_CLASSES; c++) { + if (hdr->size_classes[c].object_count > 0) { + sc_idx = c; + break; + } + } + + if (sc_idx < 0) { + printf("No size classes configured.\n"); + goto fail; + } + + sc = &hdr->size_classes[sc_idx]; + total_blocks = sc->object_count; + + spbs = malloc(total_blocks * sizeof(struct ssm_pk_buff *)); + ptrs = malloc(total_blocks * sizeof(uint8_t *)); + if (spbs == NULL || ptrs == NULL) { + printf("Failed to allocate test arrays.\n"); + goto fail_pool; + } + + /* Allocate half the blocks from single process */ + for (i = 0; i < total_blocks / 2; i++) { + ssize_t off = ssm_pool_alloc(pool, TEST_SIZE, + &ptrs[i], &spbs[i]); + if (off < 0) { + printf("Allocation %zu failed: %zd.\n", i, off); + free(spbs); + free(ptrs); + goto fail_pool; + } + } + + /* Free them all - they go to local_shard */ + for (i = 0; i < total_blocks / 2; i++) { + size_t off = ssm_pk_buff_get_idx(spbs[i]); + if (ssm_pool_remove(pool, off) != 0) { + printf("Remove %zu failed.\n", i); + free(spbs); + free(ptrs); + goto fail_pool; + } + } + + /* Freed blocks should be in shards (all blocks free again) */ + total_free = 0; + for (i = 0; i < SSM_POOL_SHARDS; i++) { + total_free += sc->shards[i].free_count; + } + + if (total_free != total_blocks) { + printf("Expected %zu free blocks total, got %zu.\n", + total_blocks, total_free); + free(spbs); + free(ptrs); + goto fail_pool; + } + + /* Allocate again - should succeed by taking from shards */ + for (i = 0; i < total_blocks / 2; i++) { + ssize_t off = ssm_pool_alloc(pool, TEST_SIZE, + &ptrs[i], &spbs[i]); + if (off < 0) { + printf("Fallback alloc %zu failed: %zd.\n", i, off); + free(spbs); + free(ptrs); + goto fail_pool; + } + } + + /* Now all allocated blocks are in use again */ + /* Cleanup - free all allocated blocks */ + for (i = 0; i < total_blocks / 2; i++) { + size_t off = ssm_pk_buff_get_idx(spbs[i]); + ssm_pool_remove(pool, off); + } + + free(spbs); + free(ptrs); + ssm_pool_destroy(pool); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_pool: + ssm_pool_destroy(pool); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_multiprocess_sharding(void) +{ + struct ssm_pool * pool; + struct _ssm_pool_hdr * hdr; + struct _ssm_size_class * sc; + pid_t children[SSM_POOL_SHARDS]; + int i; + int status; + + TEST_START(); + + pool = ssm_pool_create(0, getgid()); + if (pool == NULL) { + printf("Failed to create pool.\n"); + goto fail; + } + + /* Fork processes to test different shards */ + for (i = 0; i < SSM_POOL_SHARDS; i++) { + children[i] = fork(); + if (children[i] == -1) { + printf("Fork %d failed.\n", i); + goto fail_children; + } + + if (children[i] == 0) { + /* Child process */ + struct ssm_pool * child_pool; + struct ssm_pk_buff * spb; + uint8_t * ptr; + ssize_t off; + int my_shard; + + child_pool = ssm_pool_open(0); + if (child_pool == NULL) + exit(EXIT_FAILURE); + + my_shard = getpid() % SSM_POOL_SHARDS; + (void) my_shard; /* Reserved for future use */ + + /* Each child allocates and frees a block */ + off = ssm_pool_alloc(child_pool, TEST_SIZE, + &ptr, &spb); + if (off < 0) { + ssm_pool_close(child_pool); + exit(EXIT_FAILURE); + } + + /* Small delay to ensure allocation visible */ + usleep(10000); + + if (ssm_pool_remove(child_pool, off) != 0) { + ssm_pool_close(child_pool); + exit(EXIT_FAILURE); + } + + ssm_pool_close(child_pool); + exit(EXIT_SUCCESS); + } + } + + /* Wait for all children */ + for (i = 0; i < SSM_POOL_SHARDS; i++) { + if (waitpid(children[i], &status, 0) == -1) { + printf("Waitpid %d failed.\n", i); + goto fail_children; + } + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + printf("Child %d failed.\n", i); + goto fail_pool; + } + } + + /* Verify blocks distributed across shards */ + hdr = get_pool_hdr(pool); + + /* Find the first size class with blocks */ + sc = NULL; + for (i = 0; i < SSM_POOL_MAX_CLASSES; i++) { + if (hdr->size_classes[i].object_count > 0) { + sc = &hdr->size_classes[i]; + break; + } + } + + if (sc == NULL) { + printf("No size classes configured.\n"); + goto fail_pool; + } + + /* After children allocate and free, blocks should be in shards + * (though exact distribution depends on PID values) + */ + for (i = 0; i < SSM_POOL_SHARDS; i++) { + /* At least some shards should have blocks */ + if (sc->shards[i].free_count > 0) { + break; + } + } + + ssm_pool_destroy(pool); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_children: + /* Kill any remaining children */ + for (i = 0; i < SSM_POOL_SHARDS; i++) { + if (children[i] > 0) + kill(children[i], SIGKILL); + } + fail_pool: + ssm_pool_destroy(pool); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_exhaustion_with_fallback(void) +{ + struct ssm_pool * pool; + struct ssm_pk_buff * spb; + uint8_t * ptr; + ssize_t off; + + TEST_START(); + + pool = ssm_pool_create(0, getgid()); + if (pool == NULL) { + printf("Failed to create pool.\n"); + goto fail; + } + + /* Allocate until exhausted across all shards */ + while (true) { + off = ssm_pool_alloc(pool, TEST_SIZE, &ptr, &spb); + if (off < 0) { + if (off == -EAGAIN) + break; + printf("Unexpected error: %zd.\n", off); + goto fail_pool; + } + } + + /* Should fail with -EAGAIN when truly exhausted */ + off = ssm_pool_alloc(pool, TEST_SIZE, &ptr, &spb); + if (off != -EAGAIN) { + printf("Expected -EAGAIN, got %zd.\n", off); + goto fail_pool; + } + + ssm_pool_destroy(pool); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_pool: + ssm_pool_destroy(pool); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int pool_sharding_test(int argc, + char ** argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + + ret |= test_lazy_distribution(); + ret |= test_shard_migration(); + ret |= test_fallback_stealing(); + ret |= test_multiprocess_sharding(); + ret |= test_exhaustion_with_fallback(); + + return ret; +} diff --git a/src/lib/ssm/tests/pool_test.c b/src/lib/ssm/tests/pool_test.c new file mode 100644 index 00000000..53f7f541 --- /dev/null +++ b/src/lib/ssm/tests/pool_test.c @@ -0,0 +1,1036 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * Test of the Secure Shared Memory (SSM) system + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#define _POSIX_C_SOURCE 200809L + +#include "config.h" +#include "ssm.h" + +#include <test/test.h> +#include <ouroboros/ssm_pool.h> +#include <ouroboros/ssm_rbuff.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <stdbool.h> +#include <stdatomic.h> +#include <sys/wait.h> +#include <sys/types.h> +#include <signal.h> +#include <time.h> + +#define POOL_256 256 +#define POOL_512 512 +#define POOL_1K 1024 +#define POOL_2K 2048 +#define POOL_4K 4096 +#define POOL_16K 16384 +#define POOL_64K 65536 +#define POOL_256K 262144 +#define POOL_1M 1048576 +#define POOL_2M (2 * 1024 * 1024) + +static int test_ssm_pool_basic_allocation(void) +{ + struct ssm_pool * pool; + uint8_t * ptr; + struct ssm_pk_buff * spb; + ssize_t ret; + + TEST_START(); + + pool = ssm_pool_create(0, getgid()); + if (pool == NULL) + goto fail_create; + + ret = ssm_pool_alloc(pool, POOL_256, &ptr, &spb); + if (ret < 0) { + printf("Alloc failed: %zd.\n", ret); + goto fail_alloc; + } + + if (spb == NULL) { + printf("Spb is NULL.\n"); + goto fail_alloc; + } + + if (ptr == NULL) { + printf("Ptr is NULL.\n"); + goto fail_alloc; + } + + if (ssm_pk_buff_len(spb) != POOL_256) { + printf("Bad length: %zu.\n", ssm_pk_buff_len(spb)); + goto fail_alloc; + } + + ret = ssm_pool_remove(pool, ret); + if (ret != 0) { + printf("Remove failed: %zd.\n", ret); + goto fail_alloc; + } + + ssm_pool_destroy(pool); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_alloc: + ssm_pool_destroy(pool); + fail_create: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_ssm_pool_multiple_allocations(void) +{ + struct ssm_pool * pool; + uint8_t * ptr1; + uint8_t * ptr2; + uint8_t * ptr3; + struct ssm_pk_buff * spb1; + struct ssm_pk_buff * spb2; + struct ssm_pk_buff * spb3; + ssize_t ret1; + ssize_t ret2; + ssize_t ret3; + + TEST_START(); + + pool = ssm_pool_create(0, getgid()); + if (pool == NULL) + goto fail_create; + + ret1 = ssm_pool_alloc(pool, POOL_256, &ptr1, &spb1); + ret2 = ssm_pool_alloc(pool, POOL_256, &ptr2, &spb2); + ret3 = ssm_pool_alloc(pool, POOL_256, &ptr3, &spb3); + if (ret1 < 0 || ret2 < 0 || ret3 < 0) { + printf("Allocs failed: %zd, %zd, %zd.\n", ret1, ret2, ret3); + goto fail_alloc; + } + + if (spb1 == NULL) { + printf("Spb1 is NULL.\n"); + goto fail_alloc; + } + + if (ptr1 == NULL) { + printf("Ptr1 is NULL.\n"); + goto fail_alloc; + } + + if (spb2 == NULL) { + printf("Spb2 is NULL.\n"); + goto fail_alloc; + } + + if (ptr2 == NULL) { + printf("Ptr2 is NULL.\n"); + goto fail_alloc; + } + + if (spb3 == NULL) { + printf("Spb3 is NULL.\n"); + goto fail_alloc; + } + + if (ptr3 == NULL) { + printf("Ptr3 is NULL.\n"); + goto fail_alloc; + } + + if (ssm_pk_buff_len(spb1) != POOL_256) { + printf("Bad length spb1: %zu.\n", ssm_pk_buff_len(spb1)); + goto fail_alloc; + } + + if (ssm_pk_buff_len(spb2) != POOL_256) { + printf("Bad length spb2: %zu.\n", ssm_pk_buff_len(spb2)); + goto fail_alloc; + } + + if (ssm_pk_buff_len(spb3) != POOL_256) { + printf("Bad length spb3: %zu.\n", ssm_pk_buff_len(spb3)); + goto fail_alloc; + } + + if (ssm_pool_remove(pool, ret2) != 0) { + printf("Remove ret2 failed.\n"); + goto fail_alloc; + } + + if (ssm_pool_remove(pool, ret1) != 0) { + printf("Remove ret1 failed.\n"); + goto fail_alloc; + } + + if (ssm_pool_remove(pool, ret3) != 0) { + printf("Remove ret3 failed.\n"); + goto fail_alloc; + } + + ssm_pool_destroy(pool); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_alloc: + ssm_pool_destroy(pool); + fail_create: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_ssm_pool_no_fallback_for_large(void) +{ + struct ssm_pool * pool; + uint8_t * ptr; + struct ssm_pk_buff * spb; + ssize_t ret; + + TEST_START(); + + pool = ssm_pool_create(0, getgid()); + if (pool == NULL) + goto fail_create; + + ret = ssm_pool_alloc(pool, POOL_2M, &ptr, &spb); + if (ret >= 0) { + printf("Oversized alloc succeeded: %zd.\n", ret); + goto fail_alloc; + } + + if (ret != -EMSGSIZE) { + printf("Wrong error: %zd.\n", ret); + goto fail_alloc; + } + + ssm_pool_destroy(pool); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_alloc: + ssm_pool_destroy(pool); + fail_create: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_ssm_pool_blocking_vs_nonblocking(void) +{ + struct ssm_pool * pool; + uint8_t * ptr; + struct ssm_pk_buff * spb; + ssize_t ret; + + TEST_START(); + + pool = ssm_pool_create(0, getgid()); + if (pool == NULL) + goto fail_create; + + ret = ssm_pool_alloc(pool, POOL_2M, &ptr, &spb); + if (ret != -EMSGSIZE) { + printf("Nonblocking oversized: %zd.\n", ret); + goto fail_alloc; + } + + ret = ssm_pool_alloc_b(pool, POOL_2M, &ptr, &spb, NULL); + if (ret != -EMSGSIZE) { + printf("Blocking oversized: %zd.\n", ret); + goto fail_alloc; + } + + ret = ssm_pool_alloc(pool, POOL_256, &ptr, &spb); + if (ret < 0) { + printf("Valid alloc failed: %zd.\n", ret); + goto fail_alloc; + } + + ssm_pool_remove(pool, ret); + ssm_pool_destroy(pool); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_alloc: + ssm_pool_destroy(pool); + fail_create: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_ssm_pool_stress_test(void) +{ + struct ssm_pool * pool; + uint8_t * ptr; + struct ssm_pk_buff * spb; + ssize_t * indices = NULL; + ssize_t ret; + size_t count = 0; + size_t i; + + TEST_START(); + + pool = ssm_pool_create(0, getgid()); + if (pool == NULL) + goto fail_create; + + indices = malloc(100 * sizeof(*indices)); + if (indices == NULL) { + printf("Malloc failed.\n"); + goto fail_alloc; + } + + for (i = 0; i < 100; i++) { + size_t j; + size_t num; + size_t size; + + num = (i % 100) + 1; + + for (j = 0; j < num && count < 100; j++) { + switch (i % 4) { + case 0: + /* FALLTHRU */ + case 1: + size = POOL_256; + break; + case 2: + /* FALLTHRU */ + case 3: + size = POOL_1K; + break; + default: + size = POOL_256; + break; + } + + ret = ssm_pool_alloc(pool, size, &ptr, &spb); + if (ret < 0) { + printf("Alloc at iter %zu: %zd.\n", i, ret); + goto fail_test; + } + indices[count++] = ret; + } + + for (j = 0; j < count / 2; j++) { + size_t idx = j * 2; + if (idx < count) { + ret = ssm_pool_remove(pool, indices[idx]); + if (ret != 0) { + printf("Remove at iter %zu: %zd.\n", + i, ret); + goto fail_test; + } + memmove(&indices[idx], &indices[idx + 1], + (count - idx - 1) * sizeof(*indices)); + count--; + } + } + + if (i % 10 == 0) { + ret = ssm_pool_alloc(pool, POOL_256, &ptr, &spb); + if (ret < 0) { + printf("Periodic alloc at %zu: %zd.\n", i, ret); + goto fail_test; + } + ssm_pool_remove(pool, ret); + } + } + + for (i = 0; i < count; i++) + ssm_pool_remove(pool, indices[i]); + + free(indices); + ssm_pool_destroy(pool); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_test: + for (i = 0; i < count; i++) + ssm_pool_remove(pool, indices[i]); + free(indices); + fail_alloc: + ssm_pool_destroy(pool); + fail_create: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_ssm_pool_open_initializes_ssm(void) +{ + struct ssm_pool * creator; + struct ssm_pool * opener; + uint8_t * ptr; + struct ssm_pk_buff * spb; + ssize_t ret; + + TEST_START(); + + creator = ssm_pool_create(0, getgid()); + if (creator == NULL) + goto fail_create; + + ret = ssm_pool_alloc(creator, POOL_256, &ptr, &spb); + if (ret < 0) { + printf("Creator alloc failed: %zd.\n", ret); + goto fail_creator; + } + ssm_pool_remove(creator, ret); + + opener = ssm_pool_open(0); + if (opener == NULL) { + printf("Open failed.\n"); + goto fail_creator; + } + + ret = ssm_pool_alloc(opener, POOL_256, &ptr, &spb); + if (ret < 0) { + printf("Opener alloc failed: %zd.\n", ret); + goto fail_opener; + } + + ssm_pool_remove(opener, ret); + ssm_pool_close(opener); + ssm_pool_destroy(creator); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_opener: + ssm_pool_close(opener); + fail_creator: + ssm_pool_destroy(creator); + fail_create: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_ssm_pool_bounds_checking(void) +{ + struct ssm_pool * pool; + struct ssm_pk_buff * spb; + ssize_t ret; + + TEST_START(); + + pool = ssm_pool_create(0, getgid()); + if (pool == NULL) + goto fail_create; + + ret = ssm_pool_alloc(pool, POOL_256, NULL, &spb); + if (ret < 0) { + printf("alloc failed: %zd.\n", ret); + goto fail_alloc; + } + + spb = ssm_pool_get(pool, 0); + if (spb != NULL) { + printf("Get at offset 0.\n"); + goto fail_alloc; + } + + spb = ssm_pool_get(pool, 100000000UL); + if (spb != NULL) { + printf("Get beyond pool.\n"); + goto fail_alloc; + } + + ret = ssm_pool_remove(pool, 0); + if (ret != -EINVAL) { + printf("Remove at offset 0: %zd.\n", ret); + goto fail_alloc; + } + + ret = ssm_pool_remove(pool, 100000000UL); + if (ret != -EINVAL) { + printf("Remove beyond pool: %zd.\n", ret); + goto fail_alloc; + } + + ssm_pool_destroy(pool); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_alloc: + ssm_pool_destroy(pool); + fail_create: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_ssm_pool_inter_process_communication(void) +{ + struct ssm_pool * pool; + struct ssm_rbuff * rb; + struct ssm_pk_buff * spb; + uint8_t * ptr; + uint8_t * data; + const char * msg = "inter-process test"; + size_t len; + ssize_t idx; + pid_t pid; + int status; + + TEST_START(); + + len = strlen(msg) + 1; + + pool = ssm_pool_create(0, getgid()); + if (pool == NULL) + goto fail_create; + + rb = ssm_rbuff_create(getpid(), 1); + if (rb == NULL) { + printf("Rbuff create failed.\n"); + goto fail_pool; + } + + pid = fork(); + if (pid < 0) { + printf("Fork failed.\n"); + goto fail_rbuff; + } + + if (pid == 0) { + idx = ssm_rbuff_read_b(rb, NULL); + if (idx < 0) { + printf("Child: rbuff read: %zd.\n", idx); + exit(1); + } + + spb = ssm_pool_get(pool, idx); + if (spb == NULL) { + printf("Child: pool get failed.\n"); + exit(1); + } + + data = ssm_pk_buff_head(spb); + if (data == NULL) { + printf("Child: data is NULL.\n"); + ssm_pool_remove(pool, idx); + exit(1); + } + + if (strcmp((char *)data, msg) != 0) { + printf("Child: data mismatch.\n"); + ssm_pool_remove(pool, idx); + exit(1); + } + + ssm_pool_remove(pool, idx); + exit(0); + } + + idx = ssm_pool_alloc(pool, len, &ptr, &spb); + if (idx < 0) { + printf("Parent: pool alloc: %zd.\n", idx); + goto fail_child; + } + + memcpy(ptr, msg, len); + + if (ssm_rbuff_write(rb, idx) < 0) { + printf("Parent: rbuff write failed.\n"); + ssm_pool_remove(pool, idx); + goto fail_child; + } + + if (waitpid(pid, &status, 0) < 0) { + printf("Parent: waitpid failed.\n"); + ssm_pool_remove(pool, idx); + goto fail_rbuff; + } + + if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + printf("Child failed.\n"); + ssm_pool_remove(pool, idx); + goto fail_rbuff; + } + + ssm_rbuff_destroy(rb); + ssm_pool_destroy(pool); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_child: + waitpid(pid, &status, 0); + fail_rbuff: + ssm_rbuff_destroy(rb); + fail_pool: + ssm_pool_destroy(pool); + fail_create: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_ssm_pool_read_operation(void) +{ + struct ssm_pool * pool; + struct ssm_pk_buff * spb; + uint8_t * wptr; + uint8_t * rptr; + const char * data = "ssm_pool_read test"; + size_t len; + ssize_t idx; + ssize_t ret; + + TEST_START(); + + len = strlen(data) + 1; + + pool = ssm_pool_create(0, getgid()); + if (pool == NULL) + goto fail_create; + + idx = ssm_pool_alloc(pool, len, &wptr, &spb); + if (idx < 0) { + printf("alloc failed: %zd.\n", idx); + goto fail_alloc; + } + + memcpy(wptr, data, len); + + ret = ssm_pool_read(&rptr, pool, idx); + if (ret < 0) { + printf("Read failed: %zd.\n", ret); + goto fail_read; + } + + if (rptr == NULL) { + printf("NULL pointer.\n"); + goto fail_read; + } + + if (strcmp((char *)rptr, data) != 0) { + printf("Data mismatch.\n"); + goto fail_read; + } + + ssm_pool_remove(pool, idx); + ssm_pool_destroy(pool); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_read: + ssm_pool_remove(pool, idx); + fail_alloc: + ssm_pool_destroy(pool); + fail_create: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_ssm_pool_mlock_operation(void) +{ + struct ssm_pool * pool; + int ret; + + TEST_START(); + + pool = ssm_pool_create(0, getgid()); + if (pool == NULL) + goto fail_create; + + ret = ssm_pool_mlock(pool); + if (ret < 0) + printf("Mlock failed: %d (may need privileges).\n", ret); + + ssm_pool_destroy(pool); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_create: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_ssm_pk_buff_operations(void) +{ + struct ssm_pool * pool; + struct ssm_pk_buff * spb; + uint8_t * ptr; + uint8_t * head; + uint8_t * tail; + const char * data = "packet buffer test"; + size_t dlen; + size_t len; + ssize_t idx; + + TEST_START(); + + dlen = strlen(data); + + pool = ssm_pool_create(0, getgid()); + if (pool == NULL) + goto fail_create; + + idx = ssm_pool_alloc(pool, POOL_256, &ptr, &spb); + if (idx < 0) { + printf("alloc failed: %zd.\n", idx); + goto fail_alloc; + } + + head = ssm_pk_buff_head(spb); + if (head != ptr) { + printf("Head mismatch.\n"); + goto fail_ops; + } + + len = ssm_pk_buff_len(spb); + if (len != POOL_256) { + printf("Bad length: %zu.\n", len); + goto fail_ops; + } + + tail = ssm_pk_buff_tail(spb); + if (tail != ptr + len) { + printf("Tail mismatch.\n"); + goto fail_ops; + } + + memcpy(head, data, dlen); + + tail = ssm_pk_buff_tail_alloc(spb, 32); + if (tail == NULL) { + printf("Tail_alloc failed.\n"); + goto fail_ops; + } + + if (ssm_pk_buff_len(spb) != POOL_256 + 32) { + printf("Length after tail_alloc: %zu.\n", + ssm_pk_buff_len(spb)); + goto fail_ops; + } + + if (memcmp(head, data, dlen) != 0) { + printf("Data corrupted.\n"); + goto fail_ops; + } + + tail = ssm_pk_buff_tail_release(spb, 32); + if (tail == NULL) { + printf("Tail_release failed.\n"); + goto fail_ops; + } + + if (ssm_pk_buff_len(spb) != POOL_256) { + printf("Length after tail_release: %zu.\n", + ssm_pk_buff_len(spb)); + goto fail_ops; + } + + ssm_pool_remove(pool, idx); + ssm_pool_destroy(pool); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_ops: + ssm_pool_remove(pool, idx); + fail_alloc: + ssm_pool_destroy(pool); + fail_create: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +#define OVERHEAD (offsetof(struct ssm_pk_buff, data) + \ + SSM_PK_BUFF_HEADSPACE + SSM_PK_BUFF_TAILSPACE) +static int test_ssm_pool_size_class_boundaries(void) +{ + struct ssm_pool * pool; + struct ssm_pk_buff * spb; + uint8_t * ptr; + size_t sizes[] = { + 1, + POOL_512 - OVERHEAD, + POOL_512 - OVERHEAD + 1, + POOL_1K - OVERHEAD, + POOL_1K - OVERHEAD + 1, + POOL_2K - OVERHEAD, + POOL_2K - OVERHEAD + 1, + POOL_4K - OVERHEAD, + POOL_4K - OVERHEAD + 1, + POOL_16K - OVERHEAD, + POOL_16K - OVERHEAD + 1, + POOL_64K - OVERHEAD, + POOL_64K - OVERHEAD + 1, + POOL_256K - OVERHEAD, + POOL_256K - OVERHEAD + 1, + POOL_1M - OVERHEAD, + }; + size_t expected_classes[] = { + 512, 512, 1024, 1024, 2048, 2048, 4096, 4096, 16384, + 16384, 65536, 65536, 262144, 262144, 1048576, 1048576 + }; + size_t i; + ssize_t idx; + + TEST_START(); + + pool = ssm_pool_create(0, getgid()); + if (pool == NULL) + goto fail_create; + + for (i = 0; i < sizeof(sizes) / sizeof(sizes[0]); i++) { + struct ssm_pk_buff * hdr; + size_t actual_class; + + idx = ssm_pool_alloc(pool, sizes[i], &ptr, &spb); + if (idx < 0) { + printf("Alloc at %zu failed: %zd.\n", sizes[i], idx); + goto fail_alloc; + } + + if (ssm_pk_buff_len(spb) != sizes[i]) { + printf("Length mismatch at %zu: %zu.\n", + sizes[i], ssm_pk_buff_len(spb)); + ssm_pool_remove(pool, idx); + goto fail_alloc; + } + + /* Verify correct size class was used + * hdr->size is the data array size (object_size - header) */ + hdr = spb; + actual_class = hdr->size + offsetof(struct ssm_pk_buff, data); + if (actual_class != expected_classes[i]) { + printf("Wrong class for len=%zu: want %zu, got %zu.\n", + sizes[i], expected_classes[i], actual_class); + ssm_pool_remove(pool, idx); + goto fail_alloc; + } + + memset(ptr, i & 0xFF, sizes[i]); + + ssm_pool_remove(pool, idx); + } + + ssm_pool_destroy(pool); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_alloc: + ssm_pool_destroy(pool); + fail_create: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_ssm_pool_exhaustion(void) +{ + struct ssm_pool * pool; + struct ssm_pk_buff * spb; + uint8_t * ptr; + ssize_t * indices; + size_t count = 0; + size_t i; + ssize_t ret; + + TEST_START(); + + pool = ssm_pool_create(0, getgid()); + if (pool == NULL) + goto fail_create; + + indices = malloc(2048 * sizeof(*indices)); + if (indices == NULL) { + printf("Malloc failed.\n"); + goto fail_alloc; + } + + for (i = 0; i < 2048; i++) { + ret = ssm_pool_alloc(pool, POOL_256, &ptr, &spb); + if (ret < 0) { + if (ret == -EAGAIN) + break; + printf("Alloc error: %zd.\n", ret); + goto fail_test; + } + indices[count++] = ret; + } + + if (count == 0) { + printf("No allocs succeeded.\n"); + goto fail_test; + } + + ret = ssm_pool_alloc(pool, POOL_256, &ptr, &spb); + if (ret >= 0) { + ssm_pool_remove(pool, ret); + } else if (ret != -EAGAIN) { + printf("Unexpected error: %zd.\n", ret); + goto fail_test; + } + + for (i = 0; i < count; i++) + ssm_pool_remove(pool, indices[i]); + + ret = ssm_pool_alloc(pool, POOL_256, &ptr, &spb); + if (ret < 0) { + printf("Alloc after free failed: %zd.\n", ret); + goto fail_test; + } + ssm_pool_remove(pool, ret); + + free(indices); + ssm_pool_destroy(pool); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_test: + for (i = 0; i < count; i++) + ssm_pool_remove(pool, indices[i]); + free(indices); + fail_alloc: + ssm_pool_destroy(pool); + fail_create: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_ssm_pool_reclaim_orphans(void) +{ + struct ssm_pool * pool; + uint8_t * ptr1; + uint8_t * ptr2; + uint8_t * ptr3; + struct ssm_pk_buff * spb1; + struct ssm_pk_buff * spb2; + struct ssm_pk_buff * spb3; + ssize_t ret1; + ssize_t ret2; + ssize_t ret3; + pid_t my_pid; + pid_t fake_pid = 99999; + + TEST_START(); + + pool = ssm_pool_create(0, getgid()); + if (pool == NULL) + goto fail_create; + + my_pid = getpid(); + + /* Allocate some blocks */ + ret1 = ssm_pool_alloc(pool, POOL_256, &ptr1, &spb1); + ret2 = ssm_pool_alloc(pool, POOL_512, &ptr2, &spb2); + ret3 = ssm_pool_alloc(pool, POOL_1K, &ptr3, &spb3); + if (ret1 < 0 || ret2 < 0 || ret3 < 0) { + printf("Allocs failed: %zd, %zd, %zd.\n", ret1, ret2, ret3); + goto fail_alloc; + } + + /* Simulate blocks from another process by changing allocator_pid */ + spb1->allocator_pid = fake_pid; + spb2->allocator_pid = fake_pid; + /* Keep spb3 with our pid */ + + /* Reclaim orphans from fake_pid */ + ssm_pool_reclaim_orphans(pool, fake_pid); + + /* Verify spb1 and spb2 have refcount 0 (reclaimed) */ + if (spb1->refcount != 0) { + printf("spb1 refcount should be 0, got %u.\n", spb1->refcount); + goto fail_test; + } + + if (spb2->refcount != 0) { + printf("spb2 refcount should be 0, got %u.\n", spb2->refcount); + goto fail_test; + } + + /* Verify spb3 still has refcount 1 (not reclaimed) */ + if (spb3->refcount != 1) { + printf("spb3 refcount should be 1, got %u.\n", spb3->refcount); + goto fail_test; + } + + /* Clean up */ + ssm_pool_remove(pool, ret3); + + /* Try allocating again - should get blocks from reclaimed pool */ + ret1 = ssm_pool_alloc(pool, POOL_256, &ptr1, &spb1); + if (ret1 < 0) { + printf("Alloc after reclaim failed: %zd.\n", ret1); + goto fail_test; + } + + /* Verify new allocation has our pid */ + if (spb1->allocator_pid != my_pid) { + printf("New block has wrong pid: %d vs %d.\n", + spb1->allocator_pid, my_pid); + goto fail_test; + } + + ssm_pool_remove(pool, ret1); + ssm_pool_destroy(pool); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_test: + ssm_pool_remove(pool, ret3); + fail_alloc: + ssm_pool_destroy(pool); + fail_create: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int pool_test(int argc, + char ** argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + + ret |= test_ssm_pool_basic_allocation(); + ret |= test_ssm_pool_multiple_allocations(); + ret |= test_ssm_pool_no_fallback_for_large(); + ret |= test_ssm_pool_blocking_vs_nonblocking(); + ret |= test_ssm_pool_stress_test(); + ret |= test_ssm_pool_open_initializes_ssm(); + ret |= test_ssm_pool_bounds_checking(); + ret |= test_ssm_pool_inter_process_communication(); + ret |= test_ssm_pool_read_operation(); + ret |= test_ssm_pool_mlock_operation(); + ret |= test_ssm_pk_buff_operations(); + ret |= test_ssm_pool_size_class_boundaries(); + ret |= test_ssm_pool_exhaustion(); + ret |= test_ssm_pool_reclaim_orphans(); + + return ret; +} diff --git a/src/lib/ssm/tests/rbuff_test.c b/src/lib/ssm/tests/rbuff_test.c new file mode 100644 index 00000000..6e1cb5ec --- /dev/null +++ b/src/lib/ssm/tests/rbuff_test.c @@ -0,0 +1,675 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * Test of the SSM notification ring buffer + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#if defined(__linux__) || defined(__CYGWIN__) +#define _DEFAULT_SOURCE +#else +#define _POSIX_C_SOURCE 200112L +#endif + +#include "config.h" +#include "ssm.h" + +#include <test/test.h> +#include <ouroboros/ssm_rbuff.h> +#include <ouroboros/errno.h> +#include <ouroboros/time.h> + +#include <errno.h> +#include <stdio.h> +#include <unistd.h> +#include <pthread.h> + +static int test_ssm_rbuff_create_destroy(void) +{ + struct ssm_rbuff * rb; + + TEST_START(); + + rb = ssm_rbuff_create(getpid(), 1); + if (rb == NULL) { + printf("Failed to create rbuff.\n"); + goto fail; + } + + ssm_rbuff_destroy(rb); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_ssm_rbuff_write_read(void) +{ + struct ssm_rbuff * rb; + ssize_t idx; + + TEST_START(); + + rb = ssm_rbuff_create(getpid(), 2); + if (rb == NULL) { + printf("Failed to create rbuff.\n"); + goto fail; + } + + if (ssm_rbuff_write(rb, 42) < 0) { + printf("Failed to write value.\n"); + goto fail_rb; + } + + if (ssm_rbuff_queued(rb) != 1) { + printf("Queue length should be 1, got %zu.\n", + ssm_rbuff_queued(rb)); + goto fail_rb; + } + + idx = ssm_rbuff_read(rb); + if (idx != 42) { + printf("Expected 42, got %zd.\n", idx); + goto fail_rb; + } + + if (ssm_rbuff_queued(rb) != 0) { + printf("Queue should be empty, got %zu.\n", + ssm_rbuff_queued(rb)); + goto fail_rb; + } + + ssm_rbuff_destroy(rb); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_rb: + ssm_rbuff_destroy(rb); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_ssm_rbuff_read_empty(void) +{ + struct ssm_rbuff * rb; + ssize_t ret; + + TEST_START(); + + rb = ssm_rbuff_create(getpid(), 3); + if (rb == NULL) { + printf("Failed to create rbuff.\n"); + goto fail; + } + + ret = ssm_rbuff_read(rb); + if (ret != -EAGAIN) { + printf("Expected -EAGAIN, got %zd.\n", ret); + goto fail_rb; + } + + ssm_rbuff_destroy(rb); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_rb: + ssm_rbuff_destroy(rb); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_ssm_rbuff_fill_drain(void) +{ + struct ssm_rbuff * rb; + size_t i; + ssize_t ret; + + TEST_START(); + + rb = ssm_rbuff_create(getpid(), 4); + if (rb == NULL) { + printf("Failed to create rbuff.\n"); + goto fail; + } + + for (i = 0; i < SSM_RBUFF_SIZE - 1; ++i) { + if (ssm_rbuff_queued(rb) != i) { + printf("Expected %zu queued, got %zu.\n", + i, ssm_rbuff_queued(rb)); + goto fail_rb; + } + if (ssm_rbuff_write(rb, i) < 0) { + printf("Failed to write at index %zu.\n", i); + goto fail_rb; + } + } + + if (ssm_rbuff_queued(rb) != SSM_RBUFF_SIZE - 1) { + printf("Expected %d queued, got %zu.\n", + SSM_RBUFF_SIZE - 1, ssm_rbuff_queued(rb)); + goto fail_rb; + } + + ret = ssm_rbuff_write(rb, 999); + if (ret != -EAGAIN) { + printf("Expected -EAGAIN on full buffer, got %zd.\n", ret); + goto fail_rb; + } + + for (i = 0; i < SSM_RBUFF_SIZE - 1; ++i) { + ret = ssm_rbuff_read(rb); + if (ret != (ssize_t) i) { + printf("Expected %zu, got %zd.\n", i, ret); + goto fail_rb; + } + } + + if (ssm_rbuff_queued(rb) != 0) { + printf("Expected empty queue, got %zu.\n", + ssm_rbuff_queued(rb)); + goto fail_rb; + } + + ssm_rbuff_destroy(rb); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_rb: + while (ssm_rbuff_read(rb) >= 0) + ; + ssm_rbuff_destroy(rb); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_ssm_rbuff_acl(void) +{ + struct ssm_rbuff * rb; + uint32_t acl; + + TEST_START(); + + rb = ssm_rbuff_create(getpid(), 5); + if (rb == NULL) { + printf("Failed to create rbuff.\n"); + goto fail; + } + + acl = ssm_rbuff_get_acl(rb); + if (acl != ACL_RDWR) { + printf("Expected ACL_RDWR, got %u.\n", acl); + goto fail_rb; + } + + ssm_rbuff_set_acl(rb, ACL_RDONLY); + acl = ssm_rbuff_get_acl(rb); + if (acl != ACL_RDONLY) { + printf("Expected ACL_RDONLY, got %u.\n", acl); + goto fail_rb; + } + + if (ssm_rbuff_write(rb, 1) != -ENOTALLOC) { + printf("Expected -ENOTALLOC on RDONLY.\n"); + goto fail_rb; + } + + ssm_rbuff_set_acl(rb, ACL_FLOWDOWN); + if (ssm_rbuff_write(rb, 1) != -EFLOWDOWN) { + printf("Expected -EFLOWDOWN on FLOWDOWN.\n"); + goto fail_rb; + } + + if (ssm_rbuff_read(rb) != -EFLOWDOWN) { + printf("Expected -EFLOWDOWN on read with FLOWDOWN.\n"); + goto fail_rb; + } + + ssm_rbuff_destroy(rb); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_rb: + ssm_rbuff_destroy(rb); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_ssm_rbuff_open_close(void) +{ + struct ssm_rbuff * rb1; + struct ssm_rbuff * rb2; + pid_t pid; + + TEST_START(); + + pid = getpid(); + + rb1 = ssm_rbuff_create(pid, 6); + if (rb1 == NULL) { + printf("Failed to create rbuff.\n"); + goto fail; + } + + if (ssm_rbuff_write(rb1, 123) < 0) { + printf("Failed to write value.\n"); + goto fail_rb1; + } + + rb2 = ssm_rbuff_open(pid, 6); + if (rb2 == NULL) { + printf("Failed to open existing rbuff.\n"); + goto fail_rb1; + } + + if (ssm_rbuff_queued(rb2) != 1) { + printf("Expected 1 queued in opened rbuff, got %zu.\n", + ssm_rbuff_queued(rb2)); + goto fail_rb2; + } + + if (ssm_rbuff_read(rb2) != 123) { + printf("Failed to read from opened rbuff.\n"); + goto fail_rb2; + } + + ssm_rbuff_close(rb2); + ssm_rbuff_destroy(rb1); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_rb2: + ssm_rbuff_close(rb2); + fail_rb1: + ssm_rbuff_destroy(rb1); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +struct thread_args { + struct ssm_rbuff * rb; + int iterations; + int delay_us; +}; + +static void * writer_thread(void * arg) +{ + struct thread_args * args = (struct thread_args *) arg; + struct timespec delay = {0, 0}; + int i; + + delay.tv_nsec = args->delay_us * 1000L; + + for (i = 0; i < args->iterations; ++i) { + while (ssm_rbuff_write(args->rb, i) < 0) + nanosleep(&delay, NULL); + } + + return NULL; +} + +static void * reader_thread(void * arg) +{ + struct thread_args * args = (struct thread_args *) arg; + struct timespec delay = {0, 0}; + int i; + ssize_t val; + + delay.tv_nsec = args->delay_us * 1000L; + + for (i = 0; i < args->iterations; ++i) { + val = ssm_rbuff_read(args->rb); + while (val < 0) { + nanosleep(&delay, NULL); + val = ssm_rbuff_read(args->rb); + } + if (val != i) { + printf("Expected %d, got %zd.\n", i, val); + return (void *) -1; + } + } + + return NULL; +} + +static void * blocking_writer_thread(void * arg) +{ + struct thread_args * args = (struct thread_args *) arg; + int i; + + for (i = 0; i < args->iterations; ++i) { + if (ssm_rbuff_write_b(args->rb, i, NULL) < 0) + return (void *) -1; + } + + return NULL; +} + +static void * blocking_reader_thread(void * arg) +{ + struct thread_args * args = (struct thread_args *) arg; + int i; + ssize_t val; + + for (i = 0; i < args->iterations; ++i) { + val = ssm_rbuff_read_b(args->rb, NULL); + if (val < 0 || val != i) { + printf("Expected %d, got %zd.\n", i, val); + return (void *) -1; + } + } + + return NULL; +} + +static int test_ssm_rbuff_blocking(void) +{ + struct ssm_rbuff * rb; + pthread_t wthread; + pthread_t rthread; + struct thread_args args; + struct timespec delay = {0, 10 * MILLION}; + void * ret_w; + void * ret_r; + + TEST_START(); + + rb = ssm_rbuff_create(getpid(), 8); + if (rb == NULL) { + printf("Failed to create rbuff.\n"); + goto fail; + } + + args.rb = rb; + args.iterations = 50; + args.delay_us = 0; + + if (pthread_create(&rthread, NULL, blocking_reader_thread, &args)) { + printf("Failed to create reader thread.\n"); + goto fail_rthread; + } + + nanosleep(&delay, NULL); + + if (pthread_create(&wthread, NULL, blocking_writer_thread, &args)) { + printf("Failed to create writer thread.\n"); + pthread_cancel(rthread); + goto fail_wthread; + } + + pthread_join(wthread, &ret_w); + pthread_join(rthread, &ret_r); + + if (ret_w != NULL || ret_r != NULL) { + printf("Thread returned error.\n"); + goto fail_ret; + } + + ssm_rbuff_destroy(rb); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_ret: + fail_wthread: + pthread_join(rthread, NULL); + fail_rthread: + ssm_rbuff_destroy(rb); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_ssm_rbuff_blocking_timeout(void) +{ + struct ssm_rbuff * rb; + struct timespec abs_timeout; + struct timespec interval = {0, 100 * MILLION}; + struct timespec start; + struct timespec end; + ssize_t ret; + long elapsed_ms; + size_t i; + + TEST_START(); + + rb = ssm_rbuff_create(getpid(), 9); + if (rb == NULL) { + printf("Failed to create rbuff.\n"); + goto fail; + } + + clock_gettime(PTHREAD_COND_CLOCK, &start); + ts_add(&start, &interval, &abs_timeout); + + ret = ssm_rbuff_read_b(rb, &abs_timeout); + + clock_gettime(PTHREAD_COND_CLOCK, &end); + + if (ret != -ETIMEDOUT) { + printf("Expected -ETIMEDOUT, got %zd.\n", ret); + goto fail_rb; + } + + elapsed_ms = (end.tv_sec - start.tv_sec) * 1000L + + (end.tv_nsec - start.tv_nsec) / 1000000L; + + if (elapsed_ms < 90 || elapsed_ms > 200) { + printf("Timeout took %ld ms, expected ~100 ms.\n", + elapsed_ms); + goto fail_rb; + } + + for (i = 0; i < SSM_RBUFF_SIZE - 1; ++i) { + if (ssm_rbuff_write(rb, i) < 0) { + printf("Failed to fill buffer.\n"); + goto fail_rb; + } + } + + clock_gettime(PTHREAD_COND_CLOCK, &start); + ts_add(&start, &interval, &abs_timeout); + + ret = ssm_rbuff_write_b(rb, 999, &abs_timeout); + + clock_gettime(PTHREAD_COND_CLOCK, &end); + + if (ret != -ETIMEDOUT) { + printf("Expected -ETIMEDOUT on full buffer, got %zd.\n", + ret); + goto fail_rb; + } + + elapsed_ms = (end.tv_sec - start.tv_sec) * 1000L + + (end.tv_nsec - start.tv_nsec) / 1000000L; + + if (elapsed_ms < 90 || elapsed_ms > 200) { + printf("Write timeout took %ld ms, expected ~100 ms.\n", + elapsed_ms); + goto fail_rb; + } + + while (ssm_rbuff_read(rb) >= 0) + ; + + ssm_rbuff_destroy(rb); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_rb: + while (ssm_rbuff_read(rb) >= 0) + ; + ssm_rbuff_destroy(rb); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_ssm_rbuff_blocking_flowdown(void) +{ + struct ssm_rbuff * rb; + struct timespec abs_timeout; + struct timespec now; + struct timespec interval = {5, 0}; + ssize_t ret; + size_t i; + + TEST_START(); + + rb = ssm_rbuff_create(getpid(), 10); + if (rb == NULL) { + printf("Failed to create rbuff.\n"); + goto fail; + } + + clock_gettime(PTHREAD_COND_CLOCK, &now); + ts_add(&now, &interval, &abs_timeout); + + ssm_rbuff_set_acl(rb, ACL_FLOWDOWN); + + ret = ssm_rbuff_read_b(rb, &abs_timeout); + if (ret != -EFLOWDOWN) { + printf("Expected -EFLOWDOWN, got %zd.\n", ret); + goto fail_rb; + } + + ssm_rbuff_set_acl(rb, ACL_RDWR); + + for (i = 0; i < SSM_RBUFF_SIZE - 1; ++i) { + if (ssm_rbuff_write(rb, i) < 0) { + printf("Failed to fill buffer.\n"); + goto fail_rb; + } + } + + clock_gettime(PTHREAD_COND_CLOCK, &now); + ts_add(&now, &interval, &abs_timeout); + + ssm_rbuff_set_acl(rb, ACL_FLOWDOWN); + + ret = ssm_rbuff_write_b(rb, 999, &abs_timeout); + if (ret != -EFLOWDOWN) { + printf("Expected -EFLOWDOWN on write, got %zd.\n", ret); + goto fail_rb; + } + + ssm_rbuff_set_acl(rb, ACL_RDWR); + while (ssm_rbuff_read(rb) >= 0) + ; + + ssm_rbuff_destroy(rb); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_rb: + while (ssm_rbuff_read(rb) >= 0) + ; + ssm_rbuff_destroy(rb); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_ssm_rbuff_threaded(void) +{ + struct ssm_rbuff * rb; + pthread_t wthread; + pthread_t rthread; + struct thread_args args; + void * ret_w; + void * ret_r; + + TEST_START(); + + rb = ssm_rbuff_create(getpid(), 7); + if (rb == NULL) { + printf("Failed to create rbuff.\n"); + goto fail; + } + + args.rb = rb; + args.iterations = 100; + args.delay_us = 100; + + if (pthread_create(&wthread, NULL, writer_thread, &args)) { + printf("Failed to create writer thread.\n"); + goto fail_rb; + } + + if (pthread_create(&rthread, NULL, reader_thread, &args)) { + printf("Failed to create reader thread.\n"); + pthread_cancel(wthread); + pthread_join(wthread, NULL); + goto fail_rb; + } + + pthread_join(wthread, &ret_w); + pthread_join(rthread, &ret_r); + + if (ret_w != NULL || ret_r != NULL) { + printf("Thread returned error.\n"); + goto fail_rb; + } + + ssm_rbuff_destroy(rb); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + + fail_rb: + ssm_rbuff_destroy(rb); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int rbuff_test(int argc, + char ** argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + + ret |= test_ssm_rbuff_create_destroy(); + ret |= test_ssm_rbuff_write_read(); + ret |= test_ssm_rbuff_read_empty(); + ret |= test_ssm_rbuff_fill_drain(); + ret |= test_ssm_rbuff_acl(); + ret |= test_ssm_rbuff_open_close(); + ret |= test_ssm_rbuff_threaded(); + ret |= test_ssm_rbuff_blocking(); + ret |= test_ssm_rbuff_blocking_timeout(); + ret |= test_ssm_rbuff_blocking_flowdown(); + + return ret; +} diff --git a/src/lib/tests/CMakeLists.txt b/src/lib/tests/CMakeLists.txt index 9e23b0ee..23d01f9b 100644 --- a/src/lib/tests/CMakeLists.txt +++ b/src/lib/tests/CMakeLists.txt @@ -1,27 +1,31 @@ get_filename_component(PARENT_PATH ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) get_filename_component(PARENT_DIR ${PARENT_PATH} NAME) +compute_test_prefix() + create_test_sourcelist(${PARENT_DIR}_tests test_suite.c # Add new tests here + auth_test.c + auth_test_pqc.c bitmap_test.c btree_test.c crc32_test.c + crypt_test.c + hash_test.c + kex_test.c + kex_test_pqc.c md5_test.c sha3_test.c - shm_rbuff_test.c - time_utils_test.c + sockets_test.c + time_test.c + tpm_test.c ) -add_executable(${PARENT_DIR}_test EXCLUDE_FROM_ALL ${${PARENT_DIR}_tests}) +add_executable(${PARENT_DIR}_test ${${PARENT_DIR}_tests}) +disable_test_logging_for_target(${PARENT_DIR}_test) target_link_libraries(${PARENT_DIR}_test ouroboros-common) -add_dependencies(check ${PARENT_DIR}_test) - -set(tests_to_run ${${PARENT_DIR}_tests}) -remove(tests_to_run test_suite.c) +add_dependencies(build_tests ${PARENT_DIR}_test) -foreach (test ${tests_to_run}) - get_filename_component(test_name ${test} NAME_WE) - add_test(${test_name} ${C_TEST_PATH}/${PARENT_DIR}_test ${test_name}) -endforeach (test) +ouroboros_register_tests(TARGET ${PARENT_DIR}_test TESTS ${${PARENT_DIR}_tests}) diff --git a/src/lib/tests/auth_test.c b/src/lib/tests/auth_test.c new file mode 100644 index 00000000..b3f09277 --- /dev/null +++ b/src/lib/tests/auth_test.c @@ -0,0 +1,548 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * Test of the authentication functions + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#include "config.h" + +#include <test/test.h> +#include <ouroboros/crypt.h> +#include <ouroboros/random.h> +#include <ouroboros/utils.h> + +#include <test/certs.h> + +#define TEST_MSG_SIZE 1500 + +static int test_auth_create_destroy_ctx(void) +{ + struct auth_ctx * ctx; + + TEST_START(); + + ctx = auth_create_ctx(); + if (ctx == NULL) { + printf("Failed to create auth context.\n"); + goto fail_create; + } + + auth_destroy_ctx(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_create: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_load_free_crt(void) +{ + void * crt; + + TEST_START(); + + if (crypt_load_crt_str(root_ca_crt_ec, &crt) < 0) { + printf("Failed to load certificate string.\n"); + goto fail_load; + } + + crypt_free_crt(crt); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_load: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_crypt_get_pubkey_crt(void) +{ + void * pk; + void * crt; + + TEST_START(); + + if (crypt_load_crt_str(signed_server_crt_ec, &crt) < 0) { + printf("Failed to load server certificate from string.\n"); + goto fail_load; + } + + if (crypt_get_pubkey_crt(crt, &pk) < 0) { + printf("Failed to get public key from certificate.\n"); + goto fail_get_pubkey; + } + + crypt_free_key(pk); + crypt_free_crt(crt); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_get_pubkey: + crypt_free_crt(crt); + fail_load: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_check_crt_name(void) +{ + void * crt; + + TEST_START(); + + if (crypt_load_crt_str(signed_server_crt_ec, &crt) < 0) { + printf("Failed to load certificate from string.\n"); + goto fail_load; + } + + if (crypt_check_crt_name(crt, "test-1.unittest.o7s") < 0) { + printf("Failed to verify correct name.\n"); + goto fail_check; + } + + if (crypt_check_crt_name(crt, "bogus.name") == 0) { + printf("Failed to detect incorrect name.\n"); + goto fail_check; + } + + crypt_free_crt(crt); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_check: + crypt_free_crt(crt); + fail_load: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_load_free_privkey(void) +{ + void * key; + + TEST_START(); + + if (crypt_load_privkey_str(server_pkp_ec, &key) < 0) { + printf("Failed to load server key pair from string.\n"); + goto fail_load; + } + + crypt_free_key(key); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_load: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_load_free_pubkey(void) +{ + void * key; + + TEST_START(); + + if (crypt_load_pubkey_str(server_pk_ec, &key) < 0) { + printf("Failed to load server public key from string.\n"); + goto fail_load; + } + + crypt_free_key(key); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_load: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_crypt_check_pubkey_crt(void) +{ + void * pk; + void * crt_pk; + void * crt; + + TEST_START(); + + if (crypt_load_crt_str(signed_server_crt_ec, &crt) < 0) { + printf("Failed to load public certificate from string.\n"); + goto fail_crt; + } + + if (crypt_load_pubkey_str(server_pk_ec, &pk) < 0) { + printf("Failed to load public key from string.\n"); + goto fail_pubkey; + } + + if (crypt_get_pubkey_crt(crt, &crt_pk) < 0) { + printf("Failed to get public key from certificate.\n"); + goto fail_get_pubkey; + } + + if (crypt_cmp_key(pk, crt_pk) != 0) { + printf("Public keys do not match .\n"); + goto fail_check; + } + + + crypt_free_key(crt_pk); + crypt_free_key(pk); + crypt_free_crt(crt); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_check: + crypt_free_key(crt_pk); + fail_get_pubkey: + crypt_free_key(pk); + fail_pubkey: + crypt_free_crt(crt); + fail_crt: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_store_add(void) +{ + struct auth_ctx * ctx; + void * _root_ca_crt; + + TEST_START(); + + ctx = auth_create_ctx(); + if (ctx == NULL) { + printf("Failed to create auth context.\n"); + goto fail_create; + } + + if (crypt_load_crt_str(root_ca_crt_ec, &_root_ca_crt) < 0) { + printf("Failed to load root crt from string.\n"); + goto fail_load; + } + + if (auth_add_crt_to_store(ctx, _root_ca_crt) < 0) { + printf("Failed to add root crt to auth store.\n"); + goto fail_add; + } + + crypt_free_crt(_root_ca_crt); + auth_destroy_ctx(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_add: + crypt_free_crt(_root_ca_crt); + fail_load: + crypt_free_crt(_root_ca_crt); + fail_create: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_verify_crt(void) +{ + struct auth_ctx * auth; + void * _server_crt; + void * _signed_server_crt; + void * _root_ca_crt; + void * _im_ca_crt; + + TEST_START(); + + auth = auth_create_ctx(); + if (auth == NULL) { + printf("Failed to create auth context.\n"); + goto fail_create_ctx; + } + + if (crypt_load_crt_str(server_crt_ec, &_server_crt) < 0) { + printf("Failed to load self-signed crt from string.\n"); + goto fail_load_server_crt; + } + + if (crypt_load_crt_str(signed_server_crt_ec, &_signed_server_crt) < 0) { + printf("Failed to load signed crt from string.\n"); + goto fail_load_signed_server_crt; + } + + if (crypt_load_crt_str(root_ca_crt_ec, &_root_ca_crt) < 0) { + printf("Failed to load root crt from string.\n"); + goto fail_load_root_ca_crt; + } + + if (crypt_load_crt_str(im_ca_crt_ec, &_im_ca_crt) < 0) { + printf("Failed to load intermediate crt from string.\n"); + goto fail_load_im_ca_crt; + } + + if (auth_add_crt_to_store(auth, _root_ca_crt) < 0) { + printf("Failed to add root ca crt to auth store.\n"); + goto fail_verify; + } + + if (auth_add_crt_to_store(auth, _im_ca_crt) < 0) { + printf("Failed to add intermediate ca crt to auth store.\n"); + goto fail_verify; + } + + if (auth_verify_crt(auth, _signed_server_crt) < 0) { + printf("Failed to verify signed crt with ca crt.\n"); + goto fail_verify; + } + + if (auth_verify_crt(auth, _server_crt) == 0) { + printf("Failed to detect untrusted crt.\n"); + goto fail_verify; + } + + crypt_free_crt(_im_ca_crt); + crypt_free_crt(_root_ca_crt); + crypt_free_crt(_signed_server_crt); + crypt_free_crt(_server_crt); + + auth_destroy_ctx(auth); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_verify: + crypt_free_crt(_im_ca_crt); + fail_load_im_ca_crt: + crypt_free_crt(_root_ca_crt); + fail_load_root_ca_crt: + crypt_free_crt(_signed_server_crt); + fail_load_signed_server_crt: + crypt_free_crt(_server_crt); + fail_load_server_crt: + auth_destroy_ctx(auth); + fail_create_ctx: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int test_auth_sign(void) +{ + uint8_t buf[TEST_MSG_SIZE]; + void * pkp; + void * pk; + buffer_t msg; + buffer_t sig; + + TEST_START(); + + msg.data = buf; + msg.len = sizeof(buf); + + if (random_buffer(msg.data, msg.len) < 0) { + printf("Failed to generate random message.\n"); + goto fail_init; + } + + if (crypt_load_privkey_str(server_pkp_ec, &pkp) < 0) { + printf("Failed to load server key pair from string.\n"); + goto fail_init; + } + + if (crypt_load_pubkey_str(server_pk_ec, &pk) < 0) { + printf("Failed to load public key.\n"); + goto fail_pubkey; + } + + if (auth_sign(pkp, 0, msg, &sig) < 0) { + printf("Failed to sign message.\n"); + goto fail_sign; + } + + if (auth_verify_sig(pk, 0, msg, sig) < 0) { + printf("Failed to verify signature.\n"); + goto fail_verify; + } + + freebuf(sig); + + crypt_free_key(pk); + crypt_free_key(pkp); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_verify: + freebuf(sig); + fail_sign: + crypt_free_key(pk); + fail_pubkey: + crypt_free_key(pkp); + fail_init: + return TEST_RC_FAIL; +} + +int test_auth_bad_signature(void) +{ + uint8_t buf[TEST_MSG_SIZE]; + void * pkp; + void * pk; + buffer_t msg; + buffer_t sig; + buffer_t fake_sig; + + TEST_START(); + + msg.data = buf; + msg.len = sizeof(buf); + + if (random_buffer(msg.data, msg.len) < 0) { + printf("Failed to generate random message.\n"); + goto fail_init; + } + + if (crypt_load_privkey_str(server_pkp_ec, &pkp) < 0) { + printf("Failed to load server key pair from string.\n"); + goto fail_init; + } + + if (crypt_load_pubkey_str(server_pk_ec, &pk) < 0) { + printf("Failed to load public key.\n"); + goto fail_pubkey; + } + + if (auth_sign(pkp, 0, msg, &sig) < 0) { + printf("Failed to sign message.\n"); + goto fail_sign; + } + + fake_sig.data = malloc(sig.len); + if (fake_sig.data == NULL) { + printf("Failed to allocate memory for fake signature.\n"); + goto fail_malloc; + } + + fake_sig.len = sig.len; + if (random_buffer(fake_sig.data, fake_sig.len) < 0) { + printf("Failed to generate random fake signature.\n"); + goto fail_malloc; + } + + if (auth_verify_sig(pk, 0, msg, fake_sig) == 0) { + printf("Failed to detect bad signature.\n"); + goto fail_verify; + } + + freebuf(fake_sig); + freebuf(sig); + + crypt_free_key(pk); + crypt_free_key(pkp); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_verify: + freebuf(fake_sig); + fail_malloc: + freebuf(sig); + fail_sign: + crypt_free_key(pk); + fail_pubkey: + crypt_free_key(pkp); + fail_init: + return TEST_RC_FAIL; +} + +#define SSC_BUF_SIZE 4096 /* OpenSSL version my return different lengths */ +int test_crt_str(void) +{ + char str[SSC_BUF_SIZE]; + void * crt; + + TEST_START(); + + if (crypt_load_crt_str(signed_server_crt_ec, &crt) < 0) { + printf("Failed to load certificate from string.\n"); + goto fail_load; + } + + if (crypt_crt_str(crt, str) < 0) { + printf("Failed to convert certificate to string.\n"); + goto fail_to_str; + } + + printf("Certificate string:\n%s\n", str); + + crypt_free_crt(crt); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_to_str: + crypt_free_crt(crt); + fail_load: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int auth_test(int argc, + char ** argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + + ret |= test_auth_create_destroy_ctx(); +#ifdef HAVE_OPENSSL + ret |= test_load_free_crt(); + ret |= test_check_crt_name(); + ret |= test_crypt_get_pubkey_crt(); + ret |= test_load_free_privkey(); + ret |= test_load_free_pubkey(); + ret |= test_crypt_check_pubkey_crt(); + ret |= test_store_add(); + ret |= test_verify_crt(); + ret |= test_auth_sign(); + ret |= test_auth_bad_signature(); + ret |= test_crt_str(); +#else + (void) test_load_free_crt; + (void) test_check_crt_name; + (void) test_crypt_get_pubkey_crt; + (void) test_load_free_privkey; + (void) test_load_free_pubkey; + (void) test_crypt_check_pubkey_crt; + (void) test_store_add; + (void) test_verify_crt; + (void) test_auth_sign; + (void) test_auth_bad_signature; + (void) test_crt_str; + + ret = TEST_RC_SKIP; +#endif + return ret; +} diff --git a/src/lib/tests/auth_test_pqc.c b/src/lib/tests/auth_test_pqc.c new file mode 100644 index 00000000..349636d2 --- /dev/null +++ b/src/lib/tests/auth_test_pqc.c @@ -0,0 +1,356 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Test of the PQC authentication functions (ML-DSA-65) + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#include "config.h" + +#include <test/test.h> +#include <ouroboros/crypt.h> +#include <ouroboros/random.h> +#include <ouroboros/utils.h> + +#include <test/certs_pqc.h> + +#define TEST_MSG_SIZE 1500 + +static int test_auth_create_destroy_ctx(void) +{ + struct auth_ctx * ctx; + + TEST_START(); + + ctx = auth_create_ctx(); + if (ctx == NULL) { + printf("Failed to create auth context.\n"); + goto fail_create; + } + + auth_destroy_ctx(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_create: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_load_free_crt(void) +{ + void * crt; + + TEST_START(); + + if (crypt_load_crt_str(root_ca_crt_ml, &crt) < 0) { + printf("Failed to load root crt from string.\n"); + goto fail_load; + } + + crypt_free_crt(crt); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_load: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_load_free_privkey(void) +{ + void * key; + + TEST_START(); + + if (crypt_load_privkey_str(server_pkp_ml, &key) < 0) { + printf("Failed to load server key pair from string.\n"); + goto fail_load; + } + + crypt_free_key(key); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_load: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_load_free_pubkey(void) +{ + void * key; + + TEST_START(); + + if (crypt_load_pubkey_str(server_pk_ml, &key) < 0) { + printf("Failed to load server public key from string.\n"); + goto fail_load; + } + + crypt_free_key(key); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_load: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_verify_crt(void) +{ + struct auth_ctx * auth; + void * _server_crt; + void * _signed_server_crt; + void * _root_ca_crt; + void * _im_ca_crt; + + TEST_START(); + + auth = auth_create_ctx(); + if (auth == NULL) { + printf("Failed to create auth context.\n"); + goto fail_create_ctx; + } + + if (crypt_load_crt_str(server_crt_ml, &_server_crt) < 0) { + printf("Failed to load self-signed crt from string.\n"); + goto fail_load_server_crt; + } + + if (crypt_load_crt_str(signed_server_crt_ml, &_signed_server_crt) < 0) { + printf("Failed to load signed crt from string.\n"); + goto fail_load_signed_server_crt; + } + + if (crypt_load_crt_str(root_ca_crt_ml, &_root_ca_crt) < 0) { + printf("Failed to load root crt from string.\n"); + goto fail_load_root_ca_crt; + } + + if (crypt_load_crt_str(im_ca_crt_ml, &_im_ca_crt) < 0) { + printf("Failed to load intermediate crt from string.\n"); + goto fail_load_im_ca_crt; + } + + if (auth_add_crt_to_store(auth, _root_ca_crt) < 0) { + printf("Failed to add root ca crt to auth store.\n"); + goto fail_verify; + } + + if (auth_add_crt_to_store(auth, _im_ca_crt) < 0) { + printf("Failed to add intermediate ca crt to auth store.\n"); + goto fail_verify; + } + + if (auth_verify_crt(auth, _signed_server_crt) < 0) { + printf("Failed to verify signed crt with ca crt.\n"); + goto fail_verify; + } + + if (auth_verify_crt(auth, _server_crt) == 0) { + printf("Failed to detect untrusted crt.\n"); + goto fail_verify; + } + + crypt_free_crt(_im_ca_crt); + crypt_free_crt(_root_ca_crt); + crypt_free_crt(_signed_server_crt); + crypt_free_crt(_server_crt); + + auth_destroy_ctx(auth); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_verify: + crypt_free_crt(_im_ca_crt); + fail_load_im_ca_crt: + crypt_free_crt(_root_ca_crt); + fail_load_root_ca_crt: + crypt_free_crt(_signed_server_crt); + fail_load_signed_server_crt: + crypt_free_crt(_server_crt); + fail_load_server_crt: + auth_destroy_ctx(auth); + fail_create_ctx: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_auth_sign(void) +{ + uint8_t buf[TEST_MSG_SIZE]; + void * pkp; + void * pk; + buffer_t msg; + buffer_t sig; + + TEST_START(); + + msg.data = buf; + msg.len = sizeof(buf); + + if (random_buffer(msg.data, msg.len) < 0) { + printf("Failed to generate random message.\n"); + goto fail_init; + } + + if (crypt_load_privkey_str(server_pkp_ml, &pkp) < 0) { + printf("Failed to load server key pair from string.\n"); + goto fail_init; + } + + if (crypt_load_pubkey_str(server_pk_ml, &pk) < 0) { + printf("Failed to load public key from string.\n"); + goto fail_pubkey; + } + + if (auth_sign(pkp, 0, msg, &sig) < 0) { + printf("Failed to sign message.\n"); + goto fail_sign; + } + + if (auth_verify_sig(pk, 0, msg, sig) < 0) { + printf("Failed to verify signature.\n"); + goto fail_verify; + } + + freebuf(sig); + + crypt_free_key(pk); + crypt_free_key(pkp); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_verify: + freebuf(sig); + fail_sign: + crypt_free_key(pk); + fail_pubkey: + crypt_free_key(pkp); + fail_init: + return TEST_RC_FAIL; +} + +static int test_auth_bad_signature(void) +{ + uint8_t buf[TEST_MSG_SIZE]; + void * pkp; + void * pk; + buffer_t msg; + buffer_t sig; + buffer_t fake_sig; + + TEST_START(); + + msg.data = buf; + msg.len = sizeof(buf); + + if (random_buffer(msg.data, msg.len) < 0) { + printf("Failed to generate random message.\n"); + goto fail_init; + } + + if (crypt_load_privkey_str(server_pkp_ml, &pkp) < 0) { + printf("Failed to load server key pair from string.\n"); + goto fail_init; + } + + if (crypt_load_pubkey_str(server_pk_ml, &pk) < 0) { + printf("Failed to load public key from string.\n"); + goto fail_pubkey; + } + + if (auth_sign(pkp, 0, msg, &sig) < 0) { + printf("Failed to sign message.\n"); + goto fail_sign; + } + + fake_sig.data = malloc(sig.len); + if (fake_sig.data == NULL) { + printf("Failed to allocate memory for fake signature.\n"); + goto fail_malloc; + } + + fake_sig.len = sig.len; + if (random_buffer(fake_sig.data, fake_sig.len) < 0) { + printf("Failed to generate random fake signature.\n"); + goto fail_malloc; + } + + if (auth_verify_sig(pk, 0, msg, fake_sig) == 0) { + printf("Failed to detect bad ML-DSA-65 signature.\n"); + goto fail_verify; + } + + freebuf(fake_sig); + freebuf(sig); + + crypt_free_key(pk); + crypt_free_key(pkp); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_verify: + freebuf(fake_sig); + fail_malloc: + freebuf(sig); + fail_sign: + crypt_free_key(pk); + fail_pubkey: + crypt_free_key(pkp); + fail_init: + return TEST_RC_FAIL; +} + +int auth_test_pqc(int argc, + char ** argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + +#ifdef HAVE_OPENSSL_PQC + ret |= test_auth_create_destroy_ctx(); + ret |= test_load_free_crt(); + ret |= test_load_free_privkey(); + ret |= test_load_free_pubkey(); + ret |= test_verify_crt(); + ret |= test_auth_sign(); + ret |= test_auth_bad_signature(); +#else + (void) test_auth_create_destroy_ctx; + (void) test_load_free_crt; + (void) test_load_free_privkey; + (void) test_load_free_pubkey; + (void) test_verify_crt; + (void) test_auth_sign; + (void) test_auth_bad_signature; + + ret = TEST_RC_SKIP; +#endif + return ret; +} diff --git a/src/lib/tests/bitmap_test.c b/src/lib/tests/bitmap_test.c index 0815ecff..4dbd6653 100644 --- a/src/lib/tests/bitmap_test.c +++ b/src/lib/tests/bitmap_test.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Test of the bitmap * @@ -27,7 +27,8 @@ #define BITMAP_SIZE 200 -int bitmap_test(int argc, char ** argv) +int bitmap_test(int argc, + char ** argv) { struct bmp * bmp; ssize_t bits = BITMAP_SIZE; @@ -60,27 +61,23 @@ int bitmap_test(int argc, char ** argv) if (!bmp_is_id_valid(bmp, id)) { if (i < BITMAP_SIZE + offset) { printf("Failed valid ID %d (%zd).\n", i, id); - bmp_destroy(bmp); - return -1; + goto fail; } if (id >= offset && id < bits + offset) { printf("Valid ID %zd returned invalid.\n", id); - bmp_destroy(bmp); - return -1; + goto fail; } continue; } if (!bmp_is_id_used(bmp, id)) { printf("ID not marked in use.\n"); - bmp_destroy(bmp); - return -1; + goto fail; } if (id != i) { printf("Wrong ID returned.\n"); - bmp_destroy(bmp); - return -1; + goto fail; } } @@ -89,20 +86,24 @@ int bitmap_test(int argc, char ** argv) if (bmp_release(bmp, r)) { printf("Failed to release ID.\n"); - return -1; + goto fail; } id = bmp_allocate(bmp); if (!bmp_is_id_valid(bmp, id)) continue; + if (id != r) { printf("Wrong prev ID returned.\n"); - bmp_destroy(bmp); - return -1; + goto fail; } } bmp_destroy(bmp); return 0; + + fail: + bmp_destroy(bmp); + return -1; } diff --git a/src/lib/tests/btree_test.c b/src/lib/tests/btree_test.c index 9dc59d32..8bd30370 100644 --- a/src/lib/tests/btree_test.c +++ b/src/lib/tests/btree_test.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Test of the B-tree implementation * diff --git a/src/lib/tests/crc32_test.c b/src/lib/tests/crc32_test.c index a0f70423..a26c8220 100644 --- a/src/lib/tests/crc32_test.c +++ b/src/lib/tests/crc32_test.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Test of the CRC32 function * diff --git a/src/lib/tests/crypt_test.c b/src/lib/tests/crypt_test.c new file mode 100644 index 00000000..e250ad2a --- /dev/null +++ b/src/lib/tests/crypt_test.c @@ -0,0 +1,459 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * Test of the cryptography functions + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#include "config.h" + +#include <test/test.h> +#include <ouroboros/random.h> +#include <ouroboros/crypt.h> +#include <ouroboros/utils.h> + +#include <stdio.h> + +#define TEST_PACKET_SIZE 1500 + +extern const uint16_t crypt_supported_nids[]; +extern const uint16_t md_supported_nids[]; + +static int test_crypt_create_destroy(void) +{ + struct crypt_ctx * ctx; + uint8_t key[SYMMKEYSZ]; + struct crypt_sk sk = { + .nid = NID_aes_256_gcm, + .key = key, + .rot_bit = KEY_ROTATION_BIT + }; + + TEST_START(); + + memset(key, 0, sizeof(key)); + + ctx = crypt_create_ctx(&sk); + if (ctx == NULL) { + printf("Failed to initialize cryptography.\n"); + goto fail; + } + + crypt_destroy_ctx(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_crypt_encrypt_decrypt(int nid) +{ + uint8_t pkt[TEST_PACKET_SIZE]; + struct crypt_ctx * ctx; + uint8_t key[SYMMKEYSZ]; + struct crypt_sk sk = { + .nid = NID_aes_256_gcm, + .key = key, + .rot_bit = KEY_ROTATION_BIT + }; + buffer_t in; + buffer_t out; + buffer_t out2; + const char * cipher; + + cipher = crypt_nid_to_str(nid); + TEST_START("(%s)", cipher); + + if (random_buffer(key, sizeof(key)) < 0) { + printf("Failed to generate random key.\n"); + goto fail_init; + } + + if (random_buffer(pkt, sizeof(pkt)) < 0) { + printf("Failed to generate random data.\n"); + goto fail_init; + } + + ctx = crypt_create_ctx(&sk); + if (ctx == NULL) { + printf("Failed to initialize cryptography.\n"); + goto fail_init; + } + + in.len = sizeof(pkt); + in.data = pkt; + + if (crypt_encrypt(ctx, in, &out) < 0) { + printf("Encryption failed.\n"); + goto fail_encrypt; + } + + if (out.len < in.len) { + printf("Encryption returned too little data.\n"); + goto fail_encrypt; + } + + if (crypt_decrypt(ctx, out, &out2) < 0) { + printf("Decryption failed.\n"); + goto fail_decrypt; + } + + if (out2.len != in.len) { + printf("Decrypted data length does not match original.\n"); + goto fail_chk; + } + + if (memcmp(in.data, out2.data, in.len) != 0) { + printf("Decrypted data does not match original.\n"); + goto fail_chk; + } + + crypt_destroy_ctx(ctx); + freebuf(out2); + freebuf(out); + + TEST_SUCCESS("(%s)", cipher); + + return TEST_RC_SUCCESS; + fail_chk: + freebuf(out2); + fail_decrypt: + freebuf(out); + fail_encrypt: + crypt_destroy_ctx(ctx); + fail_init: + TEST_FAIL("(%s)", cipher); + return TEST_RC_FAIL; +} + +static int test_encrypt_decrypt_all(void) +{ + int ret = 0; + int i; + + for (i = 0; crypt_supported_nids[i] != NID_undef; i++) + ret |= test_crypt_encrypt_decrypt(crypt_supported_nids[i]); + + return ret; +} + +#ifdef HAVE_OPENSSL +#include <openssl/evp.h> +#include <openssl/obj_mac.h> + +static int test_cipher_nid_values(void) +{ + int i; + + TEST_START(); + + /* Loop over all supported ciphers and verify NIDs match OpenSSL's */ + for (i = 0; crypt_supported_nids[i] != NID_undef; i++) { + uint16_t our_nid = crypt_supported_nids[i]; + const char * str = crypt_nid_to_str(our_nid); + const EVP_CIPHER * cipher; + int openssl_nid; + + if (str == NULL) { + printf("crypt_nid_to_str failed for NID %u\n", our_nid); + goto fail; + } + + cipher = EVP_get_cipherbyname(str); + if (cipher == NULL) { + printf("OpenSSL doesn't recognize cipher '%s'\n", str); + goto fail; + } + + openssl_nid = EVP_CIPHER_nid(cipher); + + if (our_nid != openssl_nid) { + printf("NID mismatch for '%s': ours=%u, OpenSSL=%d\n", + str, our_nid, openssl_nid); + goto fail; + } + + /* Test reverse conversion */ + if (crypt_str_to_nid(str) != our_nid) { + printf("crypt_str_to_nid failed for '%s'\n", str); + goto fail; + } + } + + /* Test error cases */ + if (crypt_str_to_nid("invalid") != NID_undef) { + printf("crypt_str_to_nid: no NID_undef for invalid.\n"); + goto fail; + } + + if (crypt_nid_to_str(9999) != NULL) { + printf("crypt_nid_to_str should return NULL for invalid NID\n"); + goto fail; + } + + if (crypt_str_to_nid(NULL) != NID_undef) { + printf("crypt_str_to_nid should return NID_undef for NULL\n"); + goto fail; + } + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_md_nid_values(void) +{ + int i; + + TEST_START(); + + for (i = 0; md_supported_nids[i] != NID_undef; i++) { + uint16_t our_nid = md_supported_nids[i]; + const EVP_MD * md; + int openssl_nid; + + md = EVP_get_digestbynid(our_nid); + if (md == NULL) { + printf("OpenSSL doesn't recognize NID %u\n", our_nid); + goto fail; + } + + openssl_nid = EVP_MD_nid(md); + if (our_nid != openssl_nid) { + printf("NID mismatch: ours=%u, OpenSSL=%d\n", + our_nid, openssl_nid); + goto fail; + } + } + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} +#endif + +static int test_key_rotation(void) +{ + uint8_t pkt[TEST_PACKET_SIZE]; + struct crypt_ctx * tx_ctx; + struct crypt_ctx * rx_ctx; + uint8_t key[SYMMKEYSZ]; + struct crypt_sk sk = { + .nid = NID_aes_256_gcm, + .key = key, + .rot_bit = 7 + }; + buffer_t in; + buffer_t enc; + buffer_t dec; + uint32_t i; + uint32_t threshold; + + TEST_START(); + + if (random_buffer(key, sizeof(key)) < 0) { + printf("Failed to generate random key.\n"); + goto fail; + } + + if (random_buffer(pkt, sizeof(pkt)) < 0) { + printf("Failed to generate random data.\n"); + goto fail; + } + + tx_ctx = crypt_create_ctx(&sk); + if (tx_ctx == NULL) { + printf("Failed to create TX context.\n"); + goto fail; + } + + rx_ctx = crypt_create_ctx(&sk); + if (rx_ctx == NULL) { + printf("Failed to create RX context.\n"); + goto fail_tx; + } + + in.len = sizeof(pkt); + in.data = pkt; + + threshold = (1U << sk.rot_bit); + + /* Encrypt and decrypt across multiple rotations */ + for (i = 0; i < threshold * 3; i++) { + if (crypt_encrypt(tx_ctx, in, &enc) < 0) { + printf("Encryption failed at packet %u.\n", i); + goto fail_rx; + } + + if (crypt_decrypt(rx_ctx, enc, &dec) < 0) { + printf("Decryption failed at packet %u.\n", i); + freebuf(enc); + goto fail_rx; + } + + if (dec.len != in.len || + memcmp(in.data, dec.data, in.len) != 0) { + printf("Data mismatch at packet %u.\n", i); + freebuf(dec); + freebuf(enc); + goto fail_rx; + } + + freebuf(dec); + freebuf(enc); + } + + crypt_destroy_ctx(rx_ctx); + crypt_destroy_ctx(tx_ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_rx: + crypt_destroy_ctx(rx_ctx); + fail_tx: + crypt_destroy_ctx(tx_ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_key_phase_bit(void) +{ + uint8_t pkt[TEST_PACKET_SIZE]; + struct crypt_ctx * ctx; + uint8_t key[SYMMKEYSZ]; + struct crypt_sk sk = { + .nid = NID_aes_256_gcm, + .key = key, + .rot_bit = 7 + }; + buffer_t in; + buffer_t out; + uint32_t count; + uint32_t threshold; + uint8_t phase_before; + uint8_t phase_after; + int ivsz; + + TEST_START(); + + if (random_buffer(key, sizeof(key)) < 0) { + printf("Failed to generate random key.\n"); + goto fail; + } + + if (random_buffer(pkt, sizeof(pkt)) < 0) { + printf("Failed to generate random data.\n"); + goto fail; + } + + ctx = crypt_create_ctx(&sk); + if (ctx == NULL) { + printf("Failed to initialize cryptography.\n"); + goto fail; + } + + ivsz = crypt_get_ivsz(ctx); + if (ivsz <= 0) { + printf("Invalid IV size.\n"); + goto fail_ctx; + } + + in.len = sizeof(pkt); + in.data = pkt; + + /* Encrypt packets up to just before rotation threshold */ + threshold = (1U << sk.rot_bit); + + /* Encrypt threshold - 1 packets (indices 0 to threshold-2) */ + for (count = 0; count < threshold - 1; count++) { + if (crypt_encrypt(ctx, in, &out) < 0) { + printf("Encryption failed at count %u.\n", count); + goto fail_ctx; + } + freebuf(out); + } + + /* Packet at index threshold-1: phase should still be initial */ + if (crypt_encrypt(ctx, in, &out) < 0) { + printf("Encryption failed before rotation.\n"); + goto fail_ctx; + } + phase_before = (out.data[0] & 0x80) ? 1 : 0; + freebuf(out); + + /* Packet at index threshold: phase should have toggled */ + if (crypt_encrypt(ctx, in, &out) < 0) { + printf("Encryption failed at rotation threshold.\n"); + goto fail_ctx; + } + phase_after = (out.data[0] & 0x80) ? 1 : 0; + freebuf(out); + + /* Phase bit should have toggled */ + if (phase_before == phase_after) { + printf("Phase bit did not toggle: before=%u, after=%u.\n", + phase_before, phase_after); + goto fail_ctx; + } + + crypt_destroy_ctx(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + crypt_destroy_ctx(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int crypt_test(int argc, + char ** argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + + ret |= test_crypt_create_destroy(); + ret |= test_encrypt_decrypt_all(); +#ifdef HAVE_OPENSSL + ret |= test_cipher_nid_values(); + ret |= test_md_nid_values(); + ret |= test_key_rotation(); + ret |= test_key_phase_bit(); +#else + (void) test_key_rotation; + (void) test_key_phase_bit; + + return TEST_RC_SKIP; +#endif + return ret; +} diff --git a/src/lib/tests/hash_test.c b/src/lib/tests/hash_test.c new file mode 100644 index 00000000..fb428b47 --- /dev/null +++ b/src/lib/tests/hash_test.c @@ -0,0 +1,202 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * Test of the hashing functions + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#include <ouroboros/hash.h> +#include <test/test.h> + +#include <stdlib.h> +#include <stdint.h> +#include <assert.h> +#include <string.h> +#include <stdio.h> + +/* + * Test vectors calculated at + * https://www.lammertbies.nl/comm/info/crc-calculation.html + */ + +struct vec_entry { + char * in; + char * out; +}; + +static int test_crc32(void) +{ + int ret = 0; + + struct vec_entry vec [] = { + { "0", "f4dbdf21" }, + { "123456789", "cbf43926" }, + { "987654321", "015f0201" }, + { NULL, NULL } + }; + + struct vec_entry * cur = vec; + + TEST_START(); + + while (cur->in != NULL) { + uint8_t crc[4]; + char res[9]; + + str_hash(HASH_CRC32, crc, cur->in); + + sprintf(res, HASH_FMT32, HASH_VAL32(crc)); + if (strcmp(res, cur->out) != 0) { + printf("Hash failed %s != %s.\n", res, cur->out); + ret |= -1; + } + + ++cur; + } + + TEST_END(ret); + + return ret; +} + +static int test_md5(void) +{ + int ret = 0; + + struct vec_entry vec [] = {{ + "abc", + "900150983cd24fb0d6963f7d28e17f72" + }, { + "The quick brown fox jumps over the lazy dog", + "9e107d9d372bb6826bd81d3542a419d6" + }, { + "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + "8215ef0796a20bcaaae116d3876c664a" + }, { + NULL, + NULL + }}; + + struct vec_entry * cur = vec; + + TEST_START(); + + + while (cur->in != NULL) { + uint8_t md5[16]; + char res[33]; + + str_hash(HASH_MD5, md5, cur->in); + + sprintf(res, HASH_FMT128, HASH_VAL128(md5)); + if (strcmp(res, cur->out) != 0) { + printf("Hash failed %s != %s.\n", res, cur->out); + ret |= -1; + } + + ++cur; + } + + TEST_END(ret); + + return ret; +} + +static int test_sha3(void) +{ + int ret = 0; + + uint8_t sha3[64]; + char res[129]; + + char * in = "abc"; + + char * out = + "e642824c3f8cf24ad09234ee7d3c766f" + "c9a3a5168d0c94ad73b46fdf"; + + TEST_START(); + + str_hash(HASH_SHA3_224, sha3, in); + + sprintf(res, HASH_FMT224, HASH_VAL224(sha3)); + if (strcmp(res, out) != 0) { + printf("SHA3-224 failed %s != %s", res, out); + ret |= -1; + } + + out = + "3a985da74fe225b2045c172d6bd390bd" + "855f086e3e9d525b46bfe24511431532"; + + str_hash(HASH_SHA3_256, sha3, in); + + sprintf(res, HASH_FMT256, HASH_VAL256(sha3)); + if (strcmp(res, out) != 0) { + printf("SHA3-256 failed %s != %s.\n", res, out); + ret |= -1; + } + + out = + "ec01498288516fc926459f58e2c6ad8d" + "f9b473cb0fc08c2596da7cf0e49be4b2" + "98d88cea927ac7f539f1edf228376d25"; + + str_hash(HASH_SHA3_384, sha3, in); + + sprintf(res, HASH_FMT384, HASH_VAL384(sha3)); + if (strcmp(res, out) != 0) { + printf("SHA3-384failed %s != %s.'n", res, out); + ret |= -1; + } + + out = + "b751850b1a57168a5693cd924b6b096e" + "08f621827444f70d884f5d0240d2712e" + "10e116e9192af3c91a7ec57647e39340" + "57340b4cf408d5a56592f8274eec53f0"; + + str_hash(HASH_SHA3_512, sha3, in); + + sprintf(res, HASH_FMT512, HASH_VAL512(sha3)); + if (strcmp(res, out) != 0) { + printf("SHA3-512 failed %s != %s.\n", res, out); + ret |= -1; + } + + TEST_END(ret); + + return ret; +} + +int hash_test(int argc, + char ** argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + + ret |= test_crc32(); + + ret |= test_md5(); + + ret |= test_sha3(); + + return ret; +} diff --git a/src/lib/tests/kex_test.c b/src/lib/tests/kex_test.c new file mode 100644 index 00000000..0a588550 --- /dev/null +++ b/src/lib/tests/kex_test.c @@ -0,0 +1,844 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * Test of the key exchange functions + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#define _POSIX_C_SOURCE 200809L + +#include "config.h" + +#include <test/test.h> +#include <ouroboros/utils.h> +#include <ouroboros/crypt.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#ifdef HAVE_OPENSSL +#include <openssl/evp.h> +#include <openssl/x509.h> +#endif + +/* Test configuration strings */ +#define KEX_CONFIG_CUSTOM \ + "kex=X25519\n" + +#define KEX_CONFIG_NONE \ + "none\n" + +#define KEX_CONFIG_WHITESPACE \ + "# Comment line\n" \ + "kex = X448" \ + "\n" \ + "# Another comment\n" + +#define KEX_CONFIG_CIPHER \ + "kex=X25519\n" \ + "cipher=chacha20-poly1305\n" + +#define KEX_CONFIG_DIGEST \ + "kex=X25519\n" \ + "digest=sha384\n" + +/* Test key material for key loading tests */ +#define X25519_PRIVKEY_PEM \ + "-----BEGIN PRIVATE KEY-----\n" \ + "MC4CAQAwBQYDK2VuBCIEIJDd3+/0k2IZlaH5sZ9Z2e5J8dV2U0nsXaSUm70ZaMhL\n" \ + "-----END PRIVATE KEY-----\n" + +#define X25519_PUBKEY_PEM \ + "-----BEGIN PUBLIC KEY-----\n" \ + "MCowBQYDK2VuAyEAKYLIycSZtLFlwAX07YWWgBAYhEnRxHfgK1TVw9+mtBs=\n" \ + "-----END PUBLIC KEY-----\n" + +/* Helper macro to open string constant as FILE stream */ +#define FMEMOPEN_STR(str) fmemopen((void *) (str), strlen(str), "r") + +extern const uint16_t kex_supported_nids[]; + +int parse_sec_config(struct sec_config * cfg, + FILE * fp); + +static int test_kex_create_destroy(void) +{ + struct sec_config cfg; + + TEST_START(); + + memset(&cfg, 0, sizeof(cfg)); + cfg.x.nid = NID_X9_62_prime256v1; + cfg.x.str = kex_nid_to_str(cfg.x.nid); + cfg.c.nid = NID_aes_256_gcm; + cfg.c.str = crypt_nid_to_str(cfg.c.nid); + + if (cfg.x.nid == NID_undef || cfg.c.nid == NID_undef) { + printf("Failed to initialize kex config.\n"); + goto fail; + } + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_kex_dh_pkp_create_destroy(void) +{ + struct sec_config kex; + void * pkp; + uint8_t buf[MSGBUFSZ]; + + TEST_START(); + + memset(&kex, 0, sizeof(kex)); + SET_KEX_ALGO(&kex, "prime256v1"); + + if (kex_pkp_create(&kex, &pkp, buf) < 0) { + printf("Failed to create DH PKP.\n"); + goto fail; + } + + kex_pkp_destroy(pkp); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_kex_get_algo_from_pk(const char * algo) +{ + struct sec_config kex; + void * pkp; + buffer_t pk; + ssize_t len; + uint8_t buf[MSGBUFSZ]; + char extracted_algo[256]; + + TEST_START("(%s)", algo); + + memset(&kex, 0, sizeof(kex)); + SET_KEX_ALGO(&kex, algo); + + len = kex_pkp_create(&kex, &pkp, buf); + if (len < 0) { + printf("Failed to create key pair.\n"); + goto fail; + } + + pk.len = (size_t) len; + pk.data = buf; + + /* Use raw decode for hybrid KEMs, DER for others */ + if (IS_HYBRID_KEM(algo)) { + if (kex_get_algo_from_pk_raw(pk, extracted_algo) < 0) { + printf("Failed to extract algo from pk.\n"); + goto fail_pkp; + } + } else { + if (kex_get_algo_from_pk_der(pk, extracted_algo) < 0) { + printf("Failed to extract algo from pk.\n"); + goto fail_pkp; + } + } + + /* All algorithms should now return the specific group name */ + if (strcmp(extracted_algo, algo) != 0) { + printf("Algo mismatch: expected %s, got %s.\n", + algo, extracted_algo); + goto fail_pkp; + } + + kex_pkp_destroy(pkp); + + TEST_SUCCESS("(%s)", algo); + + return TEST_RC_SUCCESS; + fail_pkp: + kex_pkp_destroy(pkp); + fail: + TEST_FAIL("(%s)", algo); + return TEST_RC_FAIL; +} + +static int test_kex_get_algo_from_pk_all(void) +{ + int ret = 0; + int i; + + for (i = 0; kex_supported_nids[i] != NID_undef; i++) { + const char * algo = kex_nid_to_str(kex_supported_nids[i]); + ret |= test_kex_get_algo_from_pk(algo); + } + + return ret; +} + +static int test_kex_dhe_derive(const char * algo) +{ + struct sec_config kex; + void * pkp1; + void * pkp2; + buffer_t pk1; + buffer_t pk2; + ssize_t len; + uint8_t buf1[MSGBUFSZ]; + uint8_t buf2[MSGBUFSZ]; + uint8_t s1[SYMMKEYSZ]; + uint8_t s2[SYMMKEYSZ]; + + TEST_START("(%s)", algo); + + memset(&kex, 0, sizeof(kex)); + SET_KEX_ALGO(&kex, algo); + + len = kex_pkp_create(&kex, &pkp1, buf1); + if (len < 0) { + printf("Failed to create first key pair for %s.\n", algo); + goto fail; + } + + pk1.len = (size_t) len; + pk1.data = buf1; + + len = kex_pkp_create(&kex, &pkp2, buf2); + if (len < 0) { + printf("Failed to create second key pair for %s.\n", algo); + goto fail_pkp1; + } + + pk2.len = (size_t) len; + pk2.data = buf2; + + if (kex_dhe_derive(&kex, pkp1, pk2, s1) < 0) { + printf("Failed to derive first key for %s.\n", algo); + goto fail_pkp2; + } + + if (kex_dhe_derive(&kex, pkp2, pk1, s2) < 0) { + printf("Failed to derive second key for %s.\n", algo); + goto fail_pkp2; + } + + if (memcmp(s1, s2, SYMMKEYSZ) != 0) { + printf("Derived keys do not match for %s.\n", algo); + goto fail_pkp2; + } + + kex_pkp_destroy(pkp2); + kex_pkp_destroy(pkp1); + + TEST_SUCCESS("(%s)", algo); + + return TEST_RC_SUCCESS; + fail_pkp2: + kex_pkp_destroy(pkp2); + fail_pkp1: + kex_pkp_destroy(pkp1); + fail: + TEST_FAIL("(%s)", algo); + return TEST_RC_FAIL; +} + +static int test_kex_validate_algo(void) +{ + TEST_START(); + + if (kex_validate_algo("prime256v1") != 0) { + printf("prime256v1 should be valid.\n"); + goto fail; + } + + if (kex_validate_algo("X25519") != 0) { + printf("X25519 should be valid.\n"); + goto fail; + } + +#ifdef HAVE_OPENSSL_PQC + if (kex_validate_algo("ML-KEM-768") != 0) { + printf("ML-KEM-768 should be valid.\n"); + goto fail; + } +#endif + + if (kex_validate_algo("ffdhe2048") != 0) { + printf("ffdhe2048 should be valid.\n"); + goto fail; + } + + if (kex_validate_algo("invalid_algo") == 0) { + printf("invalid_algo should be rejected.\n"); + goto fail; + } + + if (kex_validate_algo("rsa2048") == 0) { + printf("rsa2048 should be rejected.\n"); + goto fail; + } + + if (kex_validate_algo(NULL) == 0) { + printf("NULL should be rejected.\n"); + goto fail; + } + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_kex_dhe_corrupted_pubkey(const char * algo) +{ + struct sec_config kex; + void * pkp; + buffer_t pk; + ssize_t len; + uint8_t buf[MSGBUFSZ]; + uint8_t s[SYMMKEYSZ]; + + TEST_START("(%s)", algo); + + memset(&kex, 0, sizeof(kex)); + SET_KEX_ALGO(&kex, algo); + + len = kex_pkp_create(&kex, &pkp, buf); + if (len < 0) { + printf("Failed to create key pair.\n"); + goto fail; + } + + pk.len = (size_t) len; + pk.data = buf; + + /* Corrupt the public key */ + buf[0] ^= 0xFF; + buf[len - 1] ^= 0xFF; + + if (kex_dhe_derive(&kex, pkp, pk, s) == 0) { + printf("Should fail with corrupted public key.\n"); + goto fail_pkp; + } + + kex_pkp_destroy(pkp); + + TEST_SUCCESS("(%s)", algo); + + return TEST_RC_SUCCESS; + fail_pkp: + kex_pkp_destroy(pkp); + fail: + TEST_FAIL("(%s)", algo); + return TEST_RC_FAIL; +} + +static int test_kex_dhe_wrong_algo(void) +{ + struct sec_config kex1; + struct sec_config kex2; + void * pkp1; + void * pkp2; + buffer_t pk2; + ssize_t len; + uint8_t buf1[MSGBUFSZ]; + uint8_t buf2[MSGBUFSZ]; + uint8_t s[SYMMKEYSZ]; + const char * algo1 = "X25519"; + const char * algo2 = "X448"; + + TEST_START("(%s vs %s)", algo1, algo2); + + memset(&kex1, 0, sizeof(kex1)); + memset(&kex2, 0, sizeof(kex2)); + SET_KEX_ALGO(&kex1, algo1); + SET_KEX_ALGO(&kex2, algo2); + + if (kex_pkp_create(&kex1, &pkp1, buf1) < 0) { + printf("Failed to create first key pair.\n"); + goto fail; + } + + len = kex_pkp_create(&kex2, &pkp2, buf2); + if (len < 0) { + printf("Failed to create second key pair.\n"); + goto fail_pkp1; + } + + pk2.len = (size_t) len; + pk2.data = buf2; + + /* Try to derive with mismatched algorithms */ + if (kex_dhe_derive(&kex1, pkp1, pk2, s) == 0) { + printf("Should fail with mismatched algorithms.\n"); + goto fail_pkp2; + } + + kex_pkp_destroy(pkp2); + kex_pkp_destroy(pkp1); + + TEST_SUCCESS("(%s vs %s)", algo1, algo2); + + return TEST_RC_SUCCESS; + fail_pkp2: + kex_pkp_destroy(pkp2); + fail_pkp1: + kex_pkp_destroy(pkp1); + fail: + TEST_FAIL("(%s vs %s)", algo1, algo2); + return TEST_RC_FAIL; +} + +static int test_kex_load_dhe_privkey(void) +{ + void * key; + + TEST_START(); + + if (crypt_load_privkey_str(X25519_PRIVKEY_PEM, &key) < 0) { + printf("Failed to load X25519 private key.\n"); + goto fail; + } + + crypt_free_key(key); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_kex_load_dhe_pubkey(void) +{ + void * key; + + TEST_START(); + + if (crypt_load_pubkey_str(X25519_PUBKEY_PEM, &key) < 0) { + printf("Failed to load X25519 public key.\n"); + goto fail; + } + + crypt_free_key(key); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +#ifdef HAVE_OPENSSL +#include <openssl/obj_mac.h> + +static int test_kex_nid_values(void) +{ + int i; + + TEST_START(); + + /* Verify all KEX algorithm NIDs match OpenSSL's */ + for (i = 0; kex_supported_nids[i] != NID_undef; i++) { + uint16_t our_nid = kex_supported_nids[i]; + const char * kex_name; + int openssl_nid; + + kex_name = kex_nid_to_str(our_nid); + if (kex_name == NULL) { + printf("kex_nid_to_str failed for NID %u\n", our_nid); + goto fail; + } + + /* Test reverse conversion */ + if (kex_str_to_nid(kex_name) != our_nid) { + printf("kex_str_to_nid failed for '%s'\n", kex_name); + goto fail; + } + + /* Get OpenSSL's NID for this name */ + openssl_nid = OBJ_txt2nid(kex_name); + if (openssl_nid != NID_undef) { + /* OpenSSL recognizes this algorithm */ + if (our_nid != openssl_nid) { + printf("NID mismatch for '%s': " + "ours=%d, OpenSSL=%d\n", + kex_name, our_nid, openssl_nid); + goto fail; + } + } else { + /* Verify no NID collision with different algorithm */ + const char * ossl_name = OBJ_nid2sn(our_nid); + if (ossl_name != NULL && + strcmp(ossl_name, kex_name) != 0) { + printf("NID collision for '%d': " + "ours=%s, OpenSSL=%s\n", + our_nid, kex_name, ossl_name); + goto fail; + } + } + } + + /* Test error cases */ + if (kex_str_to_nid("invalid") != NID_undef) { + printf("kex_str_to_nid should return NID_undef for invalid\n"); + goto fail; + } + + if (kex_nid_to_str(9999) != NULL) { + printf("kex_nid_to_str should return NULL for invalid NID\n"); + goto fail; + } + + if (kex_str_to_nid(NULL) != NID_undef) { + printf("kex_str_to_nid should return NID_undef for NULL\n"); + goto fail; + } + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} +#endif + +static int test_kex_all(void) +{ + int ret = 0; + int i; + + for (i = 0; kex_supported_nids[i] != NID_undef; i++) { + const char * algo = kex_nid_to_str(kex_supported_nids[i]); + + /* KEM tests are in kex_test_pqc.c */ + if (IS_KEM_ALGORITHM(algo)) + continue; + + ret |= test_kex_dhe_derive(algo); + } + + return ret; +} + +static int test_kex_dhe_corrupted_pubkey_all(void) +{ + int ret = 0; + int i; + + /* Test corruption for all DHE algorithms */ + /* KEM error injection tests are in kex_test_pqc.c */ + for (i = 0; kex_supported_nids[i] != NID_undef; i++) { + const char * algo = kex_nid_to_str(kex_supported_nids[i]); + + if (IS_KEM_ALGORITHM(algo)) + continue; + + ret |= test_kex_dhe_corrupted_pubkey(algo); + } + + return ret; +} + +static int test_kex_parse_config_empty(void) +{ + struct sec_config kex; + FILE * fp; + + TEST_START(); + + memset(&kex, 0, sizeof(kex)); + + fp = FMEMOPEN_STR("\n"); + if (fp == NULL) { + printf("Failed to open memory stream.\n"); + goto fail; + } + + if (parse_sec_config(&kex, fp) < 0) { + printf("Failed to parse empty config.\n"); + fclose(fp); + goto fail; + } + + if (strcmp(kex.x.str, "prime256v1") != 0) { + printf("Empty config should use prime256v1.\n"); + fclose(fp); + goto fail; + } + + fclose(fp); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_kex_parse_config_custom(void) +{ + struct sec_config kex; + FILE * fp; + + TEST_START(); + + memset(&kex, 0, sizeof(kex)); + + fp = FMEMOPEN_STR(KEX_CONFIG_CUSTOM); + if (fp == NULL) { + printf("Failed to open memory stream.\n"); + goto fail; + } + + if (parse_sec_config(&kex, fp) < 0) { + printf("Failed to parse custom config.\n"); + fclose(fp); + goto fail; + } + + if (strcmp(kex.x.str, "X25519") != 0) { + printf("Algorithm not set correctly.\n"); + fclose(fp); + goto fail; + } + + fclose(fp); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_kex_parse_config_none(void) +{ + struct sec_config kex; + FILE * fp; + + TEST_START(); + + memset(&kex, 0, sizeof(kex)); + + fp = FMEMOPEN_STR(KEX_CONFIG_NONE); + if (fp == NULL) { + printf("Failed to open memory stream.\n"); + goto fail; + } + + if (parse_sec_config(&kex, fp) < 0) { + printf("Failed to parse 'none' config.\n"); + fclose(fp); + goto fail; + } + + if (kex.x.nid != NID_undef) { + printf("'none' keyword should disable encryption.\n"); + fclose(fp); + goto fail; + } + + fclose(fp); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_kex_parse_config_whitespace(void) +{ + struct sec_config kex; + FILE * fp; + + TEST_START(); + + memset(&kex, 0, sizeof(kex)); + + fp = FMEMOPEN_STR(KEX_CONFIG_WHITESPACE); + if (fp == NULL) { + printf("Failed to open memory stream.\n"); + goto fail; + } + + if (parse_sec_config(&kex, fp) < 0) { + printf("Failed to parse config with comments.\n"); + fclose(fp); + goto fail; + } + + if (strcmp(kex.x.str, "X448") != 0) { + printf("Algorithm with whitespace not parsed correctly.\n"); + fclose(fp); + goto fail; + } + + fclose(fp); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_kex_parse_config_cipher(void) +{ + struct sec_config kex; + FILE * fp; + + TEST_START(); + + memset(&kex, 0, sizeof(kex)); + + fp = FMEMOPEN_STR(KEX_CONFIG_CIPHER); + if (fp == NULL) { + printf("Failed to open memory stream.\n"); + goto fail; + } + + if (parse_sec_config(&kex, fp) < 0) { + printf("Failed to parse cipher config.\n"); + fclose(fp); + goto fail; + } + + if (strcmp(kex.x.str, "X25519") != 0) { + printf("Algorithm not set correctly.\n"); + fclose(fp); + goto fail; + } + + if (kex.c.nid != NID_chacha20_poly1305) { + printf("Cipher not set correctly.\n"); + fclose(fp); + goto fail; + } + + fclose(fp); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_kex_parse_config_digest(void) +{ + struct sec_config kex; + FILE * fp; + + TEST_START(); + + memset(&kex, 0, sizeof(kex)); + + fp = FMEMOPEN_STR(KEX_CONFIG_DIGEST); + if (fp == NULL) { + printf("Failed to open memory stream.\n"); + goto fail; + } + + if (parse_sec_config(&kex, fp) < 0) { + printf("Failed to parse digest config.\n"); + fclose(fp); + goto fail; + } + + if (strcmp(kex.x.str, "X25519") != 0) { + printf("Algorithm not set correctly.\n"); + fclose(fp); + goto fail; + } + + if (kex.d.nid != NID_sha384) { + printf("Digest not set correctly.\n"); + fclose(fp); + goto fail; + } + + fclose(fp); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int kex_test(int argc, + char ** argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + + ret |= test_kex_create_destroy(); + ret |= test_kex_parse_config_empty(); + ret |= test_kex_parse_config_none(); +#ifdef HAVE_OPENSSL + ret |= test_kex_parse_config_custom(); + ret |= test_kex_parse_config_whitespace(); + ret |= test_kex_parse_config_cipher(); + ret |= test_kex_parse_config_digest(); + ret |= test_kex_nid_values(); + ret |= test_kex_dh_pkp_create_destroy(); + ret |= test_kex_all(); + ret |= test_kex_validate_algo(); + ret |= test_kex_get_algo_from_pk_all(); + ret |= test_kex_dhe_wrong_algo(); + ret |= test_kex_dhe_corrupted_pubkey_all(); + ret |= test_kex_load_dhe_privkey(); + ret |= test_kex_load_dhe_pubkey(); +#else + (void) test_kex_parse_config_custom; + (void) test_kex_parse_config_whitespace; + (void) test_kex_parse_config_cipher; + (void) test_kex_parse_config_digest; + (void) test_kex_dh_pkp_create_destroy; + (void) test_kex_all; + (void) test_kex_validate_algo; + (void) test_kex_get_algo_from_pk_all; + (void) test_kex_dhe_wrong_algo(); + (void) test_kex_dhe_corrupted_pubkey_all; + (void) test_kex_load_dhe_privkey; + (void) test_kex_load_dhe_pubkey; + + ret = TEST_RC_SKIP; +#endif + return ret; +} diff --git a/src/lib/tests/kex_test_pqc.c b/src/lib/tests/kex_test_pqc.c new file mode 100644 index 00000000..d4579eca --- /dev/null +++ b/src/lib/tests/kex_test_pqc.c @@ -0,0 +1,549 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Test of the post-quantum key exchange functions + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#define _POSIX_C_SOURCE 200809L + +#include "config.h" + +#include <test/test.h> +#include <ouroboros/utils.h> +#include <ouroboros/crypt.h> +#include <ouroboros/random.h> + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#ifdef HAVE_OPENSSL +#include <openssl/evp.h> +#include <openssl/x509.h> +#endif + +extern const uint16_t kex_supported_nids[]; +extern const uint16_t md_supported_nids[]; + +static int get_random_kdf(void) +{ + static int idx = 0; + int count; + + if (md_supported_nids[0] == NID_undef) + return NID_undef; + + for (count = 0; md_supported_nids[count] != NID_undef; count++) + ; + + return md_supported_nids[(idx++) % count]; +} + +/* ML-KEM-768 test key material */ + +#define MLKEM768_PRIVKEY_PEM \ + "-----BEGIN PRIVATE KEY-----\n" \ + "MIIJvgIBADALBglghkgBZQMEBAIEggmqMIIJpgRA+QIIiQLQkS5fl5RluSmgXRjZ\n" \ + "YU16W4TVt0dmnBP41rLTTRT3S8CRtkb+xmoFAcWTfEzbdr5pp3g2CBRx+APXTwSC\n" \ + "CWBll6AecTd1Kqdyix3zNQcthDBP0XnwdTHDqkKuFzMP58Y+0gc9Bo+W0xBOK2ZK\n" \ + "gcAmix3YLJuDS8Teep/Tdc7KIm5AaLNoI8BIMgKC/ASsW8kC+78BV4OIgqNWurS9\n" \ + "BrTiCmiag7c+6DsVDJHJ4kfcccwUDBKiW0v+LAkk1HXBcx6usrwuFC0H3ICli2sC\n" \ + "o5DfGL7g4kWHhobXjAZnxn298C8FGmLQK5kah4nZiJ+MuHqrirziCGTLKkY1a8vC\n" \ + "GFgzfHIcvB4dtyi9dxZmWpSXqDf2AVNgqrD2C7WQEULQOKxm/I8Mw31Yp8TC6SAP\n" \ + "RzM4cBAXF00W4Rce05O0am/ga5dStAhikMESyckCoEGlPFFXOmjy1HmOasI+AbGk\n" \ + "2BKp6cfbImbjd0ePdCSFEgIQwAQHm7+4UoZR2JmNwSI1AC2P4FMRAIaD2A69i6LC\n" \ + "kFniGcOog5m09nw5FqZmeEfNs6yyFGSX16D1YyjuooAFGlU0FFX7aKwsYM8t1gkS\n" \ + "YSUfMxIW9yzhSW4vZHuGyxlxBMr1y51RZrW8gnvW5p/Ip5yDBJRahY6KMWT15C14\n" \ + "C2rIe8U+d4Xi5IMI3D1JNpwFebYhKs3/ManxoU7Fwwa0GzQrgLYU5KhqO8/hopnl\n" \ + "8mQH+BPh+TR5lqYawS7HZXFJE8JzOnCtOSgB6Hz2U7oG9ik8h0FRqVD3ak20EmZU\n" \ + "c7gpGW8Odc51uaIBzDu4ej4dGgwo4awYaX4ugLOutHqGqRfCjIVb6XQ4m35p4KKi\n" \ + "qBVQ211aIhavUIgNECJ7WUETilXyyHLB9x3EFJdidEfSRUxLYJNAC5XM2WFCyhnE\n" \ + "pKmossSNq6ZOqBjPegE0J6zfNg65dR/OlIdGVDgrVTIpwYAUzBMW2nTnCa00EmPj\n" \ + "F7tRscHI8qb/QlnRVEUN+S+A2CtVIH1c666zOoRFRI9G4bmVoa8k2x0ANB51tCns\n" \ + "vAYqkMybIgMvWwbqoAxeW0G1O3qObGXtgs94BzhAEM3RbG/hy3GR1qUNSk/qyDKc\n" \ + "t1qpiaao0aLVsnpb28eBIk6+q0I82reGdV31OYvUpnVxRbRPFXEFs5PNS3s/7I8a\n" \ + "SlSLUGOh+mhrUzDPSJCzgEvOmrwrRxe3F52tS0nAt6Z5zKToASHphoISUi7lGX1F\n" \ + "Owx62qhSqqlI98bKqh7yQRZYrHXqE0bscAHCcIaZ8RVya42JHDCoQWyxqBuLOWEl\n" \ + "+Fz6vI5DqEnJkA7ke49EvBAOJ58lxAXQIV5remtzYGPKdyG2oamiFHiLVQDzGX/l\n" \ + "aFNMGXRWcK4/Y3mnkJvx9QGtq6KstQN/J4a51ZeX5YwNBcoY9UcFS6kHRW5rR3UM\n" \ + "tEZj5VN8BL9nyWM9h7hUSHQboaxO7M5qswfXB8f21xR16T40Ki4nawx/6zHGCQsc\n" \ + "uKr5SaCV88tghqJYHBorU5iKB5KsLDSHqYYrNo/Vy8W6kMA2jGAO24d4G32DSshR\n" \ + "sEF9W1nuAHK/5ste01G5KmX2KhdZBE37oGhM98HRQ6hU8qwuKrhdV7vZis5C8LXY\n" \ + "7MbDyDt1NnFqWFc6lYeVa6eRcmYzeAbXahrxwiiaLIdHXD95aZ/0S6+tKBGgQzwm\n" \ + "ZsbwdXhl+n+yqDNE6Sow2bwueqhDwZVWoMCv5SK+HAGPtcZ7UU9oWrqpiL085m7F\n" \ + "5G49KJUEZadVtj4Z9zrkeQkida+4I7v3Y3MzsWsGJww7YhTDJpsxxmSm85bHwx98\n" \ + "hZXSqckJTL4c2nBzgrBlukIT9Wl+qItMthVvABPzp4wGZhdgKrEIRl3yCnhhUgpL\n" \ + "lUxYegwWDMEjZxKlSbIyl5p9lCS8w2lsBzsQ2FJiAy/MWLa56aA+wFs3C8smZ6Cf\n" \ + "p5NWa8Rm+k898GWBxZivhF03CBOZ42du0YUZdCPoA5V1KC6bh4JyWFI49VFbQFMG\n" \ + "gwAqc0ErAH3iMammKC9746WWagnUIG3o8LygZrusuGeTohXJhVUTJDw2s0rzNhbw\n" \ + "5IyookkY5BWENKFKTIgdBxvYelOKwbGE8Z36FEW0ABlmx7SRCKWlNVjSEAIXmMiQ\n" \ + "VLdQF33QVYD9RR5chja254VuJH4plo+5JwiKWz8LlCIBm7CVkifZMLofmMk3s3L4\n" \ + "sXtE+Bhfm5Plk3RrgDdlHH+hK7gk61XGdynGjDY7aLtCKZ0SMsVskSLom1pbIR5M\n" \ + "KLYsQ1Pse4mhfDOFCkWFLI5TShGMuIoo1k7XeIE6g8QoUlV5EXyWHHhIVaE4yWGP\n" \ + "AVgEp0UswKFeeo3SoCAeADA3U88ymxpBJp73yDIqok5dM3SgkjfPWZDkgkAI8WHs\n" \ + "CKKeqrSOs1kkE3JXtE7kcTHT6XHo162TmgGkqMVwOQ3EmR6FRpYxJhZvuVbjJsSx\n" \ + "YjW3ScnR4Zivoi7q95ypco331pIlIZpqV0NydUpMyQaz1cnoPKYDh1xa6LhcqEKK\n" \ + "8a68iXjQgzgqQBDABonVybNDtlJ5lnTTuKhak8PBFAmmhj1JdrPqoIvQRCmLaark\n" \ + "J7/q9RLtk6kTOJ0qtLe2qqwCxJwyoMd2Q5F4+xTWZHu90ljRdcnYewarqcKzoL27\n" \ + "tcpTOmVz88I1hYVUJEV7aB36QMhTS1dquTqJZCD0hBPWAMToEoD4OFvKWmbFmzaW\n" \ + "xrMc4ECYeDAAKYs2YqoXSLfAixBmZjb6UDB61l2GA58pFJW0ZwN8S5tApA2NRi+7\n" \ + "oC/zgMgBGHft6E0+OUVb8It89pY1t7ybq5+fkBvEixDId3f1pK3gqcaYqG/YhoMJ\n" \ + "MJWkqYxCNGmdZ8gFo46V6K+4xZUblQWKypN6+RYO4kDh0koppWGEULjgBoCH+V8E\n" \ + "7GcoE8SRdQY1BIMoRVWb8Ur8ZYIVU8lqgaZPlWM3oRCiWk0kRxexFF0i5WlILIK9\n" \ + "GT8saX+bmRd9KSy3JrpPhQn59CpJBRxz8WKdJ3wwtqE/2TbxQhLooEWHYVrZEG5E\n" \ + "SkIoOkUAJUR+CzLLFDMdUE8w3CasE4ys+hco7AA5TAms24A1FXcxMgNb6VHA0bi5\n" \ + "c8rPCZvjubLXR4A0/A2Ualo4cy3UAr9k0rbZOJnjqk8eExkeaxbyh42cJpU75i4O\n" \ + "NLYsRZJkg9bkCpPgZKb707sPZO72CX3h/lQdXVgGkZ7Tqd1qzM+JOhSWvrYiBLa+\n" \ + "5IKSmFwT+5sw1InEesXwRN09000U90vAkbZG/sZqBQHFk3xM23a+aad4NggUcfgD\n" \ + "108=\n" \ + "-----END PRIVATE KEY-----\n" + +#define MLKEM768_PUBKEY_PEM \ + "-----BEGIN PUBLIC KEY-----\n" \ + "MIIEsjALBglghkgBZQMEBAIDggShAMPIO3U2cWpYVzqVh5Vrp5FyZjN4BtdqGvHC\n" \ + "KJosh0dcP3lpn/RLr60oEaBDPCZmxvB1eGX6f7KoM0TpKjDZvC56qEPBlVagwK/l\n" \ + "Ir4cAY+1xntRT2hauqmIvTzmbsXkbj0olQRlp1W2Phn3OuR5CSJ1r7gju/djczOx\n" \ + "awYnDDtiFMMmmzHGZKbzlsfDH3yFldKpyQlMvhzacHOCsGW6QhP1aX6oi0y2FW8A\n" \ + "E/OnjAZmF2AqsQhGXfIKeGFSCkuVTFh6DBYMwSNnEqVJsjKXmn2UJLzDaWwHOxDY\n" \ + "UmIDL8xYtrnpoD7AWzcLyyZnoJ+nk1ZrxGb6Tz3wZYHFmK+EXTcIE5njZ27RhRl0\n" \ + "I+gDlXUoLpuHgnJYUjj1UVtAUwaDACpzQSsAfeIxqaYoL3vjpZZqCdQgbejwvKBm\n" \ + "u6y4Z5OiFcmFVRMkPDazSvM2FvDkjKiiSRjkFYQ0oUpMiB0HG9h6U4rBsYTxnfoU\n" \ + "RbQAGWbHtJEIpaU1WNIQAheYyJBUt1AXfdBVgP1FHlyGNrbnhW4kfimWj7knCIpb\n" \ + "PwuUIgGbsJWSJ9kwuh+YyTezcvixe0T4GF+bk+WTdGuAN2Ucf6EruCTrVcZ3KcaM\n" \ + "Njtou0IpnRIyxWyRIuibWlshHkwotixDU+x7iaF8M4UKRYUsjlNKEYy4iijWTtd4\n" \ + "gTqDxChSVXkRfJYceEhVoTjJYY8BWASnRSzAoV56jdKgIB4AMDdTzzKbGkEmnvfI\n" \ + "MiqiTl0zdKCSN89ZkOSCQAjxYewIop6qtI6zWSQTcle0TuRxMdPpcejXrZOaAaSo\n" \ + "xXA5DcSZHoVGljEmFm+5VuMmxLFiNbdJydHhmK+iLur3nKlyjffWkiUhmmpXQ3J1\n" \ + "SkzJBrPVyeg8pgOHXFrouFyoQorxrryJeNCDOCpAEMAGidXJs0O2UnmWdNO4qFqT\n" \ + "w8EUCaaGPUl2s+qgi9BEKYtpquQnv+r1Eu2TqRM4nSq0t7aqrALEnDKgx3ZDkXj7\n" \ + "FNZke73SWNF1ydh7BqupwrOgvbu1ylM6ZXPzwjWFhVQkRXtoHfpAyFNLV2q5Oolk\n" \ + "IPSEE9YAxOgSgPg4W8paZsWbNpbGsxzgQJh4MAApizZiqhdIt8CLEGZmNvpQMHrW\n" \ + "XYYDnykUlbRnA3xLm0CkDY1GL7ugL/OAyAEYd+3oTT45RVvwi3z2ljW3vJurn5+Q\n" \ + "G8SLEMh3d/WkreCpxpiob9iGgwkwlaSpjEI0aZ1nyAWjjpXor7jFlRuVBYrKk3r5\n" \ + "Fg7iQOHSSimlYYRQuOAGgIf5XwTsZygTxJF1BjUEgyhFVZvxSvxlghVTyWqBpk+V\n" \ + "YzehEKJaTSRHF7EUXSLlaUgsgr0ZPyxpf5uZF30pLLcmuk+FCfn0KkkFHHPxYp0n\n" \ + "fDC2oT/ZNvFCEuigRYdhWtkQbkRKQig6RQAlRH4LMssUMx1QTzDcJqwTjKz6Fyjs\n" \ + "ADlMCazbgDUVdzEyA1vpUcDRuLlzys8Jm+O5stdHgDT8DZRqWjhzLdQCv2TSttk4\n" \ + "meOqTx4TGR5rFvKHjZwmlTvmLg40tixFkmSD1uQKk+BkpvvTuw9k7vYJfeH+VB1d\n" \ + "WAaRntOp\n" \ + "-----END PUBLIC KEY-----\n" + +/* Helper macro to open string constant as FILE stream */ +#define FMEMOPEN_STR(str) fmemopen((void *) (str), strlen(str), "r") + +static int test_kex_load_kem_privkey(void) +{ + void * key; + + TEST_START(); + + if (crypt_load_privkey_str(MLKEM768_PRIVKEY_PEM, &key) < 0) { + printf("Failed to load ML-KEM-768 private key.\n"); + goto fail; + } + + crypt_free_key(key); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_kex_load_kem_pubkey(void) +{ + void * key; + + TEST_START(); + + if (crypt_load_pubkey_str(MLKEM768_PUBKEY_PEM, &key) < 0) { + printf("Failed to load ML-KEM-768 public key.\n"); + goto fail; + } + + crypt_free_key(key); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_kex_kem(const char * algo) +{ + struct sec_config kex; + void * pkp; + buffer_t pk; + buffer_t ct; + ssize_t len; + ssize_t ct_len; + uint8_t buf1[MSGBUFSZ]; + uint8_t buf2[MSGBUFSZ]; + uint8_t s1[SYMMKEYSZ]; + uint8_t s2[SYMMKEYSZ]; + int kdf; + + TEST_START("(%s)", algo); + + kdf = get_random_kdf(); + + memset(&kex, 0, sizeof(kex)); + SET_KEX_ALGO(&kex, algo); + + len = kex_pkp_create(&kex, &pkp, buf1); + if (len < 0) { + printf("Failed to create key pair for %s.\n", algo); + goto fail; + } + + pk.len = (size_t) len; + pk.data = buf1; + + if (IS_HYBRID_KEM(algo)) + ct_len = kex_kem_encap_raw(pk, buf2, kdf, s1); + else + ct_len = kex_kem_encap(pk, buf2, kdf, s1); + + if (ct_len < 0) { + printf("Failed to encapsulate for %s.\n", algo); + goto fail_pkp; + } + + ct.len = (size_t) ct_len; + ct.data = buf2; + + if (kex_kem_decap(pkp, ct, kdf, s2) < 0) { + printf("Failed to decapsulate for %s.\n", algo); + goto fail_pkp; + } + + if (memcmp(s1, s2, SYMMKEYSZ) != 0) { + printf("Shared secrets don't match for %s.\n", algo); + goto fail_pkp; + } + + kex_pkp_destroy(pkp); + + TEST_SUCCESS("(%s)", algo); + + return TEST_RC_SUCCESS; + fail_pkp: + kex_pkp_destroy(pkp); + fail: + TEST_FAIL("(%s)", algo); + return TEST_RC_FAIL; +} + +static int test_kex_kem_corrupted_ciphertext(const char * algo) +{ + struct sec_config kex; + void * pkp; + buffer_t pk; + buffer_t ct; + ssize_t len; + ssize_t ct_len; + uint8_t buf1[MSGBUFSZ]; + uint8_t buf2[MSGBUFSZ]; + uint8_t s1[SYMMKEYSZ]; + uint8_t s2[SYMMKEYSZ]; + int kdf; + + TEST_START("(%s)", algo); + + kdf = get_random_kdf(); + + memset(&kex, 0, sizeof(kex)); + SET_KEX_ALGO(&kex, algo); + + len = kex_pkp_create(&kex, &pkp, buf1); + if (len < 0) { + printf("Failed to create key pair.\n"); + goto fail; + } + + pk.len = (size_t) len; + pk.data = buf1; + + if (IS_HYBRID_KEM(algo)) + ct_len = kex_kem_encap_raw(pk, buf2, kdf, s1); + else + ct_len = kex_kem_encap(pk, buf2, kdf, s1); + + if (ct_len < 0) { + printf("Failed to encapsulate.\n"); + goto fail_pkp; + } + + ct.len = (size_t) ct_len; + ct.data = buf2; + + /* Corrupt the ciphertext */ + buf2[0] ^= 0xFF; + buf2[ct_len - 1] ^= 0xFF; + + /* ML-KEM uses implicit rejection */ + if (kex_kem_decap(pkp, ct, kdf, s2) < 0) { + printf("Decapsulation failed unexpectedly.\n"); + goto fail_pkp; + } + + /* The shared secrets should NOT match with corrupted CT */ + if (memcmp(s1, s2, SYMMKEYSZ) == 0) { + printf("Corrupted ciphertext produced same secret.\n"); + goto fail_pkp; + } + + kex_pkp_destroy(pkp); + + TEST_SUCCESS("(%s)", algo); + + return TEST_RC_SUCCESS; + fail_pkp: + kex_pkp_destroy(pkp); + fail: + TEST_FAIL("(%s)", algo); + return TEST_RC_FAIL; +} + +static int test_kex_kem_wrong_keypair(const char * algo) +{ + struct sec_config kex; + void * pkp1; + void * pkp2; + buffer_t pk1; + buffer_t ct; + ssize_t len; + ssize_t ct_len; + uint8_t buf1[MSGBUFSZ]; + uint8_t buf2[MSGBUFSZ]; + uint8_t buf3[MSGBUFSZ]; + uint8_t s1[SYMMKEYSZ]; + uint8_t s2[SYMMKEYSZ]; + + TEST_START("(%s)", algo); + + memset(&kex, 0, sizeof(kex)); + SET_KEX_ALGO(&kex, algo); + + len = kex_pkp_create(&kex, &pkp1, buf1); + if (len < 0) { + printf("Failed to create first key pair.\n"); + goto fail; + } + + pk1.len = (size_t) len; + pk1.data = buf1; + + if (kex_pkp_create(&kex, &pkp2, buf2) < 0) { + printf("Failed to create second key pair.\n"); + goto fail_pkp1; + } + + if (IS_HYBRID_KEM(algo)) + ct_len = kex_kem_encap_raw(pk1, buf3, NID_sha256, s1); + else + ct_len = kex_kem_encap(pk1, buf3, NID_sha256, s1); + + if (ct_len < 0) { + printf("Failed to encapsulate.\n"); + goto fail_pkp2; + } + + ct.len = (size_t) ct_len; + ct.data = buf3; + + if (kex_kem_decap(pkp2, ct, NID_sha256, s2) == 0) { + if (memcmp(s1, s2, SYMMKEYSZ) == 0) { + printf("Wrong keypair produced same secret.\n"); + goto fail_pkp2; + } + } + + kex_pkp_destroy(pkp2); + kex_pkp_destroy(pkp1); + + TEST_SUCCESS("(%s)", algo); + + return TEST_RC_SUCCESS; + fail_pkp2: + kex_pkp_destroy(pkp2); + fail_pkp1: + kex_pkp_destroy(pkp1); + fail: + TEST_FAIL("(%s)", algo); + return TEST_RC_FAIL; +} + +static int test_kex_kem_truncated_ciphertext(const char * algo) +{ + struct sec_config kex; + void * pkp; + buffer_t pk; + buffer_t ct; + ssize_t len; + ssize_t ct_len; + uint8_t buf1[MSGBUFSZ]; + uint8_t buf2[MSGBUFSZ]; + uint8_t s1[SYMMKEYSZ]; + uint8_t s2[SYMMKEYSZ]; + + TEST_START("(%s)", algo); + + memset(&kex, 0, sizeof(kex)); + SET_KEX_ALGO(&kex, algo); + + len = kex_pkp_create(&kex, &pkp, buf1); + if (len < 0) { + printf("Failed to create key pair.\n"); + goto fail; + } + + pk.len = (size_t) len; + pk.data = buf1; + + if (IS_HYBRID_KEM(algo)) + ct_len = kex_kem_encap_raw(pk, buf2, NID_sha256, s1); + else + ct_len = kex_kem_encap(pk, buf2, NID_sha256, s1); + + if (ct_len < 0) { + printf("Failed to encapsulate.\n"); + goto fail_pkp; + } + + /* Truncate the ciphertext */ + ct.len = (size_t) ct_len / 2; + ct.data = buf2; + + if (kex_kem_decap(pkp, ct, NID_sha256, s2) == 0) { + printf("Should fail with truncated ciphertext.\n"); + goto fail_pkp; + } + + kex_pkp_destroy(pkp); + + TEST_SUCCESS("(%s)", algo); + + return TEST_RC_SUCCESS; + fail_pkp: + kex_pkp_destroy(pkp); + fail: + TEST_FAIL("(%s)", algo); + return TEST_RC_FAIL; +} + +static int test_kex_kem_all(void) +{ + int ret = 0; + int i; + + for (i = 0; kex_supported_nids[i] != NID_undef; i++) { + const char * algo = kex_nid_to_str(kex_supported_nids[i]); + + if (!IS_KEM_ALGORITHM(algo)) + continue; + + ret |= test_kex_kem(algo); + } + + return ret; +} + +static int test_kex_kem_corrupted_ciphertext_all(void) +{ + int ret = 0; + int i; + + for (i = 0; kex_supported_nids[i] != NID_undef; i++) { + const char * algo = kex_nid_to_str(kex_supported_nids[i]); + + if (!IS_KEM_ALGORITHM(algo)) + continue; + + ret |= test_kex_kem_corrupted_ciphertext(algo); + } + + return ret; +} + +static int test_kex_kem_wrong_keypair_all(void) +{ + int ret = 0; + int i; + + for (i = 0; kex_supported_nids[i] != NID_undef; i++) { + const char * algo = kex_nid_to_str(kex_supported_nids[i]); + + if (!IS_KEM_ALGORITHM(algo)) + continue; + + ret |= test_kex_kem_wrong_keypair(algo); + } + + return ret; +} + +static int test_kex_kem_truncated_ciphertext_all(void) +{ + int ret = 0; + int i; + + for (i = 0; kex_supported_nids[i] != NID_undef; i++) { + const char * algo = kex_nid_to_str(kex_supported_nids[i]); + + if (!IS_KEM_ALGORITHM(algo)) + continue; + + ret |= test_kex_kem_truncated_ciphertext(algo); + } + + return ret; +} + +int kex_test_pqc(int argc, + char ** argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + +#ifdef HAVE_OPENSSL_PQC + ret |= test_kex_load_kem_privkey(); + ret |= test_kex_load_kem_pubkey(); + ret |= test_kex_kem_all(); + ret |= test_kex_kem_corrupted_ciphertext_all(); + ret |= test_kex_kem_wrong_keypair_all(); + ret |= test_kex_kem_truncated_ciphertext_all(); +#else + (void) test_kex_load_kem_privkey; + (void) test_kex_load_kem_pubkey; + (void) test_kex_kem_all; + (void) test_kex_kem_corrupted_ciphertext_all; + (void) test_kex_kem_wrong_keypair_all; + (void) test_kex_kem_truncated_ciphertext_all; + + ret = TEST_RC_SKIP; +#endif + return ret; +} diff --git a/src/lib/tests/md5_test.c b/src/lib/tests/md5_test.c index b5ad127f..28e8f42f 100644 --- a/src/lib/tests/md5_test.c +++ b/src/lib/tests/md5_test.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Test of the MD5 function * diff --git a/src/lib/tests/sha3_test.c b/src/lib/tests/sha3_test.c index 4860cd9b..82b4ef0d 100644 --- a/src/lib/tests/sha3_test.c +++ b/src/lib/tests/sha3_test.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Test of the SHA3 function * diff --git a/src/lib/tests/shm_rbuff_test.c b/src/lib/tests/shm_rbuff_test.c deleted file mode 100644 index a3ed1449..00000000 --- a/src/lib/tests/shm_rbuff_test.c +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2021 - * - * Test of the shm_rbuff - * - * Dimitri Staessens <dimitri@ouroboros.rocks> - * Sander Vrijders <sander@ouroboros.rocks> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., http://www.fsf.org/about/contact/. - */ - -#define _POSIX_C_SOURCE 200112L - -#include "config.h" - -#include <ouroboros/shm_rbuff.h> - -#include <errno.h> -#include <stdio.h> -#include <unistd.h> - -int shm_rbuff_test(int argc, - char ** argv) -{ - struct shm_rbuff * rb; - size_t i; - - (void) argc; - (void) argv; - - printf("Test: create rbuff..."); - - rb = shm_rbuff_create(getpid(), 1); - if (rb == NULL) - goto err; - - printf("success.\n\n"); - printf("Test: write a value..."); - - if (shm_rbuff_write(rb, 1) < 0) - goto error; - - printf("success.\n\n"); - printf("Test: check queue length is 1..."); - - if (shm_rbuff_queued(rb) != 1) - goto error; - - printf("success.\n\n"); - printf("Test: read a value..."); - - if (shm_rbuff_read(rb) != 1) - goto error; - - printf("success.\n\n"); - printf("Test: check queue is empty..."); - - if (shm_rbuff_read(rb) != -EAGAIN) - goto error; - - printf("success.\n\n"); - printf("Test: fill the queue..."); - - for (i = 0; i < SHM_RBUFF_SIZE - 1; ++i) { - if (shm_rbuff_queued(rb) != i) - goto error; - if (shm_rbuff_write(rb, 1) < 0) - goto error; - } - - printf("success.\n\n"); - printf("Test: check queue is full..."); - - if (shm_rbuff_queued(rb) != SHM_RBUFF_SIZE - 1) - goto error; - - printf("success [%zd entries].\n\n", shm_rbuff_queued(rb)); - - printf("Test: check queue is full by writing value..."); - if (!(shm_rbuff_write(rb, 1) < 0)) - goto error; - - printf("success [%zd entries].\n\n", shm_rbuff_queued(rb)); - - /* empty the rbuff */ - while (shm_rbuff_read(rb) >= 0) - ; - - shm_rbuff_destroy(rb); - - return 0; - - error: - /* empty the rbuff */ - while (shm_rbuff_read(rb) >= 0) - ; - - shm_rbuff_destroy(rb); - err: - printf("failed.\n\n"); - return -1; -} diff --git a/src/lib/tests/sockets_test.c b/src/lib/tests/sockets_test.c new file mode 100644 index 00000000..ce9051b6 --- /dev/null +++ b/src/lib/tests/sockets_test.c @@ -0,0 +1,102 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * Tests for socket.c + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#if defined(__linux__) || defined(__CYGWIN__) +#define _DEFAULT_SOURCE +#else +#define _POSIX_C_SOURCE 200112L +#endif + +#include <ouroboros/sockets.h> +#include <test/test.h> + +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> + +#define TEST_PID 1234 +#define TEST_PID_STR "1234" +#define TEST_SERVER_PATH "/tmp/test.sock" +#define TEST_SERVER_PREFIX "/tmp/ouroboros/test." +#define TEST_SOCK_PATH_PREFIX "var/run/ouroboros/test." + +static int test_sock_path(void) +{ + char * path; + char * exp = TEST_SOCK_PATH_PREFIX TEST_PID_STR SOCK_PATH_SUFFIX; + + TEST_START(); + + path = sock_path(TEST_PID, TEST_SOCK_PATH_PREFIX); + if (path == NULL) { + printf("Path is NULL.\n"); + goto fail_path; + } + + if (strcmp(path, exp) != 0) { + printf("Expected path '%s', got '%s'.\n", exp, path); + goto fail_cmp; + } + + free(path); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + fail_cmp: + free(path); + fail_path: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_server_socket_open(void) +{ + int sockfd; + + TEST_START(); + + sockfd = server_socket_open(TEST_SERVER_PATH); + if (sockfd < 0) { + printf("Failed to open server socket.\n"); + goto fail_sock; + } + + close(sockfd); + + unlink(TEST_SERVER_PATH); + + TEST_SUCCESS(); + return TEST_RC_SUCCESS; + fail_sock: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int sockets_test(void) +{ + int ret = 0; + + ret |= test_sock_path(); + ret |= test_server_socket_open(); + + return ret; +} diff --git a/src/lib/tests/time_test.c b/src/lib/tests/time_test.c new file mode 100644 index 00000000..4685310b --- /dev/null +++ b/src/lib/tests/time_test.c @@ -0,0 +1,529 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * Test of the time utilities + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + +#define _POSIX_C_SOURCE 200809L + +#include <test/test.h> +#include <ouroboros/time.h> + +#include <stdio.h> + +static int ts_check(struct timespec * s, + time_t sec, + time_t nsec) +{ + return s->tv_sec == sec && s->tv_nsec == nsec; +} + +static int tv_check(struct timeval * v, + time_t sec, + time_t usec) +{ + return v->tv_sec == sec && v->tv_usec == usec; +} + + +static int test_time_ts_init(void) +{ + struct timespec s = TIMESPEC_INIT_S (100); + struct timespec ms = TIMESPEC_INIT_MS(100); + struct timespec us = TIMESPEC_INIT_US(100); + struct timespec ns = TIMESPEC_INIT_NS(100); + + TEST_START(); + + if (!ts_check(&s, 100, 0)) { + printf("timespec_init_s failed.\n"); + goto fail; + } + + if (!ts_check(&ms, 0, 100 * MILLION)) { + printf("timespec_init_ms failed.\n"); + goto fail; + } + + if (!ts_check(&us, 0, 100* 1000L)) { + printf("timespec_init_us failed.\n"); + goto fail; + } + + if (!ts_check(&ns, 0, 100)) { + printf("timespec_init_ns failed.\n"); + goto fail; + } + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_time_tv_init(void) +{ + struct timeval s = TIMEVAL_INIT_S (100); + struct timeval ms = TIMEVAL_INIT_MS(100); + struct timeval us = TIMEVAL_INIT_US(100); + + TEST_START(); + + if (!tv_check(&s, 100, 0)) { + printf("timeval_init_s failed.\n"); + goto fail; + } + + if (!tv_check(&ms, 0, 100 * 1000L)) { + printf("timeval_init_ms failed.\n"); + goto fail; + } + + if (!tv_check(&us, 0, 100)) { + printf("timeval_init_us failed.\n"); + goto fail; + } + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_ts_diff(void) +{ + struct timespec s0 = TIMESPEC_INIT_S (100); + struct timespec s1 = TIMESPEC_INIT_S (200); + struct timespec ms0 = TIMESPEC_INIT_MS(100); + struct timespec ms1 = TIMESPEC_INIT_MS(200); + struct timespec us0 = TIMESPEC_INIT_US(100); + struct timespec us1 = TIMESPEC_INIT_US(200); + struct timespec ns0 = TIMESPEC_INIT_NS(100); + struct timespec ns1 = TIMESPEC_INIT_NS(200); + struct timespec res; + + TEST_START(); + + ts_diff(&s0, &s1, &res); + if (!ts_check(&res, -100, 0)) { + printf("timespec_diff failed at s0 - s1.\n"); + goto fail; + } + + ts_diff(&s1, &s0, &res); + if (!ts_check(&res, 100, 0)) { + printf("timespec_diff failed at s1 - s0.\n"); + goto fail; + } + + ts_diff(&ms0, &ms1, &res); + if (!ts_check(&res, -1, 900 * MILLION)) { + printf("timespec_diff failed at ms0 - ms1.\n"); + goto fail; + } + + ts_diff(&ms1, &ms0, &res); + if (!ts_check(&res, 0, 100 * MILLION)) { + printf("timespec_diff failed at ms1 - ms0.\n"); + goto fail; + } + + ts_diff(&us0, &us1, &res); + if (!ts_check(&res, -1, 999900 * 1000L)) { + printf("timespec_diff failed at us0 - us1.\n"); + goto fail; + } + + ts_diff(&us1, &us0, &res); + if (!ts_check(&res, 0, 100 * 1000L)) { + printf("timespec_diff failed at us1 - us0.\n"); + goto fail; + } + + ts_diff(&ns0, &ns1, &res); + if (!ts_check(&res, -1, 999999900)) { + printf("timespec_diff failed at ns0 - ns1.\n"); + goto fail; + } + + ts_diff(&ns1, &ns0, &res); + if (!ts_check(&res, 0, 100)) { + printf("timespec_diff failed at ns1 - ns0.\n"); + goto fail; + } + + ts_diff(&s0, &ms0, &res); + if (!ts_check(&res, 99, 900 * MILLION)) { + printf("timespec_diff failed at s0 - ms0.\n"); + goto fail; + } + + ts_diff(&s0, &us0, &res); + if (!ts_check(&res, 99, 999900 * 1000L)) { + printf("timespec_diff failed at s0 - us0.\n"); + goto fail; + } + + ts_diff(&s0, &ns0, &res); + if (!ts_check(&res, 99, 999999900)) { + printf("timespec_diff failed at s0 - ns0.\n"); + goto fail; + } + + ts_diff(&ms0, &us0, &res); + if (!ts_check(&res, 0, 99900 * 1000L)) { + printf("timespec_diff failed at ms0 - us0.\n"); + goto fail; + } + + ts_diff(&ms0, &ns0, &res); + if (!ts_check(&res, 0, 99999900)) { + printf("timespec_diff failed at ms0 - ns0.\n"); + goto fail; + } + + ts_diff(&us0, &ns0, &res); + if (!ts_check(&res, 0, 99900)) { + printf("timespec_diff failed at us0 - ns0.\n"); + goto fail; + } + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_tv_diff(void) +{ + struct timeval s0 = TIMEVAL_INIT_S (100); + struct timeval s1 = TIMEVAL_INIT_S (200); + struct timeval ms0 = TIMEVAL_INIT_MS(100); + struct timeval ms1 = TIMEVAL_INIT_MS(200); + struct timeval us0 = TIMEVAL_INIT_US(100); + struct timeval us1 = TIMEVAL_INIT_US(200); + struct timeval res; + + TEST_START(); + + tv_diff(&s0, &s1, &res); + if (!tv_check(&res, -100, 0)) { + printf("timeval_diff failed at s0 - s1.\n"); + goto fail; + } + + tv_diff(&s1, &s0, &res); + if (!tv_check(&res, 100, 0)) { + printf("timeval_diff failed at s1 - s0.\n"); + goto fail; + } + + tv_diff(&ms0, &ms1, &res); + if (!tv_check(&res, -1, 900 * 1000L)) { + printf("timeval_diff failed at ms0 - ms1.\n"); + goto fail; + } + + tv_diff(&ms1, &ms0, &res); + if (!tv_check(&res, 0, 100 * 1000L)) { + printf("timeval_diff failed at ms1 - ms0.\n"); + goto fail; + } + + tv_diff(&us0, &us1, &res); + if (!tv_check(&res, -1, 999900)) { + printf("timeval_diff failed at us0 - us1.\n"); + goto fail; + } + + tv_diff(&us1, &us0, &res); + if (!tv_check(&res, 0, 100)) { + printf("timeval_diff failed at us1 - us0.\n"); + goto fail; + } + + tv_diff(&s0, &ms0, &res); + if (!tv_check(&res, 99, 900 * 1000L)) { + printf("timeval_diff failed at s0 - ms0.\n"); + goto fail; + } + + tv_diff(&s0, &us0, &res); + if (!tv_check(&res, 99, 999900)) { + printf("timeval_diff failed at s0 - us0.\n"); + goto fail; + } + + tv_diff(&ms0, &us0, &res); + if (!tv_check(&res, 0, 99900)) { + printf("timeval_diff failed at ms0 - us0.\n"); + goto fail; + } + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_ts_diff_time(void) +{ + struct timespec s0 = TIMESPEC_INIT_S (100); + struct timespec s1 = TIMESPEC_INIT_S (200); + struct timespec ms0 = TIMESPEC_INIT_MS(100); + struct timespec ms1 = TIMESPEC_INIT_MS(200); + struct timespec us0 = TIMESPEC_INIT_US(100); + struct timespec us1 = TIMESPEC_INIT_US(200); + struct timespec ns0 = TIMESPEC_INIT_NS(100); + struct timespec ns1 = TIMESPEC_INIT_NS(200); + + TEST_START(); + + if (ts_diff_ms(&s0, &s1) != -100 * 1000L) { + printf("timespec_diff_ms failed at s0 - s1.\n"); + goto fail; + } + + if (ts_diff_ms(&s1, &s0) != 100 * 1000L) { + printf("timespec_diff_ms failed at s1 - s0.\n"); + goto fail; + } + + if (ts_diff_us(&s0, &s1) != -100 * MILLION) { + printf("timespec_diff_us failed at s1 - s0.\n"); + goto fail; + } + + if (ts_diff_us(&s1, &s0) != 100 * MILLION) { + printf("timespec_diff_us failed at s0 - s1.\n"); + goto fail; + } + + if (ts_diff_ns(&s0, &s1) != -100 * BILLION) { + printf("timespec_diff_ns failed at s0 - s1.\n"); + goto fail; + } + + if (ts_diff_ns(&s1, &s0) != 100 * BILLION) { + printf("timespec_diff_ns failed at s1 - s0.\n"); + goto fail; + } + + if (ts_diff_ms(&ms0, &ms1) != -100) { + printf("timespec_diff_ms failed at ms0 - ms1.\n"); + goto fail; + } + + if (ts_diff_ms(&ms1, &ms0) != 100) { + printf("timespec_diff_ms failed at ms1 - ms0.\n"); + goto fail; + } + + if (ts_diff_us(&ms0, &ms1) != -100 * 1000L) { + printf("timespec_diff_us failed at ms0 - ms1.\n"); + goto fail; + } + + if (ts_diff_us(&ms1, &ms0) != 100 * 1000L) { + printf("timespec_diff_us failed at ms1 - ms0.\n"); + goto fail; + } + + if (ts_diff_ns(&ms0, &ms1) != -100 * MILLION) { + printf("timespec_diff_ns failed at ms0 - ms1.\n"); + goto fail; + } + + if (ts_diff_ns(&ms1, &ms0) != 100 * MILLION) { + printf("timespec_diff_ns failed at ms1 - ms0.\n"); + goto fail; + } + + if (ts_diff_ms(&us0, &us1) != 0) { + printf("timespec_diff_ms failed at us0 - us1.\n"); + goto fail; + } + + if (ts_diff_ms(&us1, &us0) != 0) { + printf("timespec_diff_ms failed at us1 - us0.\n"); + goto fail; + } + + if (ts_diff_us(&us0, &us1) != -100) { + printf("timespec_diff_us failed at us0 - us1.\n"); + goto fail; + } + + if (ts_diff_us(&us1, &us0) != 100) { + printf("timespec_diff_us failed at us1 - us0.\n"); + goto fail; + } + + if (ts_diff_ns(&us0, &us1) != -100 * 1000L) { + printf("timespec_diff_ns failed at us0 - us1.\n"); + goto fail; + } + + if (ts_diff_ns(&us1, &us0) != 100 * 1000L) { + printf("timespec_diff_ns failed at us1 - us0.\n"); + goto fail; + } + + if (ts_diff_ms(&ns0, &ns1) != 0) { + printf("timespec_diff_ms failed at ns0 - ns1.\n"); + goto fail; + } + + if (ts_diff_ms(&ns1, &ns0) != 0) { + printf("timespec_diff_ms failed at ns1 - ns0.\n"); + goto fail; + } + + if (ts_diff_us(&ns0, &ns1) != 0) { + printf("timespec_diff_us failed at ns0 - ns1.\n"); + goto fail; + } + + if (ts_diff_us(&ns1, &ns0) != 0) { + printf("timespec_diff_us failed at ns1 - ns0.\n"); + goto fail; + } + + if (ts_diff_ns(&ns0, &ns1) != -100) { + printf("timespec_diff_ns failed at ns0 - ns1.\n"); + goto fail; + } + + if (ts_diff_ns(&ns1, &ns0) != 100) { + printf("timespec_diff_ns failed at ns1 - ns0.\n"); + goto fail; + } + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_tv_diff_time(void) +{ + struct timeval s0 = TIMEVAL_INIT_S (100); + struct timeval s1 = TIMEVAL_INIT_S (200); + struct timeval ms0 = TIMEVAL_INIT_MS(100); + struct timeval ms1 = TIMEVAL_INIT_MS(200); + struct timeval us0 = TIMEVAL_INIT_US(100); + struct timeval us1 = TIMEVAL_INIT_US(200); + + TEST_START(); + + if (tv_diff_ms(&s0, &s1) != -100 * 1000L) { + printf("timeval_diff_ms failed at s0 - s1.\n"); + goto fail; + } + + if (tv_diff_ms(&s1, &s0) != 100 * 1000L) { + printf("timeval_diff_ms failed at s1 - s0.\n"); + goto fail; + } + + if (tv_diff_us(&s0, &s1) != -100 * MILLION) { + printf("timeval_diff_us failed at s0 - s1.\n"); + goto fail; + } + + if (tv_diff_us(&s1, &s0) != 100 * MILLION) { + printf("timeval_diff_us failed at s1 - s0.\n"); + goto fail; + } + + if (tv_diff_ms(&ms0, &ms1) != -100) { + printf("timeval_diff_ms failed at ms0 - ms1.\n"); + goto fail; + } + + if (tv_diff_ms(&ms1, &ms0) != 100) { + printf("timeval_diff_ms failed at ms1 - ms0.\n"); + goto fail; + } + + if (tv_diff_us(&ms0, &ms1) != -100 * 1000L) { + printf("timeval_diff_us failed at ms0 - ms1.\n"); + goto fail; + } + + if (tv_diff_us(&ms1, &ms0) != 100 * 1000L) { + printf("timeval_diff_us failed at ms1 - ms0.\n"); + goto fail; + } + + if (tv_diff_ms(&us0, &us1) != 0) { + printf("timeval_diff_ms failed at us0 - us1.\n"); + goto fail; + } + + if (tv_diff_ms(&us1, &us0) != 0) { + printf("timeval_diff_ms failed at us1 - us0.\n"); + goto fail; + } + + if (tv_diff_us(&us0, &us1) != -100) { + printf("timeval_diff_us failed at us0 - us1.\n"); + goto fail; + } + + if (tv_diff_us(&us1, &us0) != 100) { + printf("timeval_diff_us failed at us1 - us0.\n"); + goto fail; + } + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int time_test(int argc, + char ** argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + + ret |= test_time_ts_init(); + ret |= test_time_tv_init(); + ret |= test_ts_diff(); + ret |= test_tv_diff(); + ret |= test_ts_diff_time(); + ret |= test_tv_diff_time(); + + return ret; +} diff --git a/src/lib/tests/time_utils_test.c b/src/lib/tests/time_utils_test.c deleted file mode 100644 index fa65c4dc..00000000 --- a/src/lib/tests/time_utils_test.c +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2021 - * - * Test of the time utilities - * - * Dimitri Staessens <dimitri@ouroboros.rocks> - * Sander Vrijders <sander@ouroboros.rocks> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., http://www.fsf.org/about/contact/. - */ - -#define _POSIX_C_SOURCE 200809L - -#include <ouroboros/time_utils.h> - -#include <stdio.h> - -static void ts_print(struct timespec * s) -{ - printf("timespec is %zd:%ld.\n", (ssize_t) s->tv_sec, s->tv_nsec); -} - -static void tv_print(struct timeval * v) -{ - printf("timeval is %zd:%zu.\n", (ssize_t) v->tv_sec, (size_t) v->tv_usec); -} - -static void ts_init(struct timespec * s, - time_t sec, - time_t nsec) -{ - s->tv_sec = sec; - s->tv_nsec = nsec; -} - -static void tv_init(struct timeval * v, - time_t sec, - time_t usec) -{ - v->tv_sec = sec; - v->tv_usec = usec; -} - -static int ts_check(struct timespec * s, - time_t sec, - time_t nsec) -{ - return s->tv_sec == sec && s->tv_nsec == nsec; -} - -static int tv_check(struct timeval * v, - time_t sec, - time_t usec) -{ - return v->tv_sec == sec && v->tv_usec == usec; -} - -int time_utils_test(int argc, - char ** argv) -{ - struct timespec s0; - struct timespec s1; - struct timespec s2; - - struct timeval v0; - struct timeval v1; - struct timeval v2; - - (void) argc; - (void) argv; - - ts_init(&s0, 0, 0); - ts_init(&s1, 5, 0); - - ts_add(&s0, &s1, &s2); - if (!ts_check(&s2, 5, 0)) { - printf("ts_add failed.\n"); - ts_print(&s2); - return -1; - } - - tv_init(&v0, 0, 0); - tv_init(&v1, 5, 0); - - tv_add(&v0, &v1, &v2); - if (!tv_check(&v2, 5, 0)) { - printf("tv_add failed.\n"); - tv_print(&v2); - return -1; - } - - ts_init(&s0, 0, 500 * MILLION); - ts_init(&s1, 0, 600 * MILLION); - - ts_add(&s0, &s1, &s2); - if (!ts_check(&s2, 1, 100 * MILLION)) { - printf("ts_add with nano overflow failed.\n"); - ts_print(&s2); - return -1; - } - - tv_init(&v0, 0, 500 * 1000); - tv_init(&v1, 0, 600 * 1000); - - tv_add(&v0, &v1, &v2); - if (!tv_check(&v2, 1, 100 * 1000)) { - printf("tv_add with nano overflow failed.\n"); - tv_print(&v2); - return -1; - } - - ts_init(&s0, 0, 0); - ts_init(&s1, 5, 0); - - ts_diff(&s0, &s1, &s2); - if (!ts_check(&s2, -5, 0)) { - printf("ts_diff failed.\n"); - ts_print(&s2); - return -1; - } - - tv_init(&v0, 0, 0); - tv_init(&v1, 5, 0); - - tv_diff(&v0, &v1, &v2); - if (!tv_check(&v2, -5, 0)) { - printf("tv_diff failed.\n"); - tv_print(&v2); - return -1; - } - - ts_init(&s0, 0, 500 * MILLION); - ts_init(&s1, 0, 600 * MILLION); - - ts_diff(&s0, &s1, &s2); - if (!ts_check(&s2, -1, 900 * MILLION)) { - printf("ts_diff with nano underflow failed.\n"); - ts_print(&s2); - return -1; - } - - tv_init(&v0, 0, 500 * 1000); - tv_init(&v1, 0, 600 * 1000); - - tv_diff(&v0, &v1, &v2); - if (!tv_check(&v2, -1, 900 * 1000)) { - printf("tv_diff with nano underflow failed.\n"); - tv_print(&v2); - return -1; - } - - return 0; -} diff --git a/src/lib/tests/tpm_test.c b/src/lib/tests/tpm_test.c new file mode 100644 index 00000000..41bce964 --- /dev/null +++ b/src/lib/tests/tpm_test.c @@ -0,0 +1,104 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2024 + * + * Tests for the threadpool manager + * + * Dimitri Staessens <dimitri@ouroboros.rocks> + * Sander Vrijders <sander@ouroboros.rocks> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., http://www.fsf.org/about/contact/. + */ + + +#include "tpm.c" + +#include <test/test.h> + +static void * test_func(void * o) +{ + (void) o; + + while(1) + sleep(1); + + return NULL; +} + +static int test_tpm_create_destroy(void) +{ + struct tpm *tpm; + + TEST_START(); + + tpm = tpm_create(2, 2, &test_func, NULL); + if (tpm == NULL) { + printf("Failed to initialize TPM.\n"); + goto fail; + } + + tpm_destroy(tpm); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_tpm_start_stop(void * (* fn)(void *), + void * o) +{ + struct tpm *tpm; + + TEST_START(); + + tpm = tpm_create(2, 2, fn, o); + if (tpm == NULL) { + printf("Failed to initialize TPM.\n"); + goto fail_create; + } + + if (tpm_start(tpm) < 0) { + printf("Failed to start TPM.\n"); + goto fail_start; + } + + tpm_stop(tpm); + + tpm_destroy(tpm); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_start: + tpm_destroy(tpm); + fail_create: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int tpm_test(int argc, + char ** argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + + ret |= test_tpm_create_destroy(); + ret |= test_tpm_start_stop(&test_func, NULL); + + return ret; +} diff --git a/src/lib/timerwheel.c b/src/lib/timerwheel.c index 487dcd0e..ed235047 100644 --- a/src/lib/timerwheel.c +++ b/src/lib/timerwheel.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Timerwheel * @@ -30,16 +30,12 @@ struct rxm { struct list_head next; uint32_t seqno; -#ifdef RXM_BUFFER_ON_HEAP - uint8_t * pkt; - size_t pkt_len; -#else - struct shm_du_buff * sdb; - uint8_t * head; - uint8_t * tail; +#ifndef RXM_BUFFER_ON_HEAP + struct ssm_pk_buff * spb; #endif + struct frct_pci * pkt; + size_t len; time_t t0; /* Time when original was sent (us). */ - size_t mul; /* RTO multiplier. */ struct frcti * frcti; int fd; int flow_id; /* Prevent rtx when fd reused. */ @@ -62,11 +58,9 @@ struct { struct list_head acks[ACKQ_SLOTS]; bool map[ACKQ_SLOTS][PROG_MAX_FLOWS]; - size_t prv_rxm; /* Last processed rxm slot at lvl 0. */ - size_t prv_ack; /* Last processed ack slot. */ + size_t prv_rxm[RXMQ_LVLS]; /* Last processed rxm slots. */ + size_t prv_ack; /* Last processed ack slot. */ pthread_mutex_t lock; - - bool in_use; } rw; static void timerwheel_fini(void) @@ -87,8 +81,8 @@ static void timerwheel_fini(void) #ifdef RXM_BUFFER_ON_HEAP free(rxm->pkt); #else - shm_du_buff_ack(rxm->sdb); - ipcp_sdb_release(rxm->sdb); + ssm_pk_buff_ack(rxm->spb); + ipcp_spb_release(rxm->spb); #endif free(rxm); } @@ -119,8 +113,10 @@ static int timerwheel_init(void) clock_gettime(PTHREAD_COND_CLOCK, &now); - rw.prv_rxm = (ts_to_rxm_slot(now) - 1) & (RXMQ_SLOTS - 1); for (i = 0; i < RXMQ_LVLS; ++i) { + rw.prv_rxm[i] = (ts_to_rxm_slot(now) - 1); + rw.prv_rxm[i] >>= (RXMQ_BUMP * i); + rw.prv_rxm[i] &= (RXMQ_SLOTS - 1); for (j = 0; j < RXMQ_SLOTS; ++j) list_head_init(&rw.rxms[i][j]); } @@ -142,38 +138,34 @@ static void timerwheel_move(void) size_t i; size_t j; - if (!__sync_bool_compare_and_swap(&rw.in_use, true, true)) - return; - pthread_mutex_lock(&rw.lock); pthread_cleanup_push(__cleanup_mutex_unlock, &rw.lock); clock_gettime(PTHREAD_COND_CLOCK, &now); - rxm_slot = ts_to_ns(now) >> RXMQ_RES; - j = rw.prv_rxm; - rw.prv_rxm = rxm_slot & (RXMQ_SLOTS - 1); + rxm_slot = ts_to_rxm_slot(now); for (i = 0; i < RXMQ_LVLS; ++i) { size_t j_max_slot = rxm_slot & (RXMQ_SLOTS - 1); + j = rw.prv_rxm[i]; if (j_max_slot < j) j_max_slot += RXMQ_SLOTS; - while (j++ < j_max_slot) { list_for_each_safe(p, h, &rw.rxms[i][j & (RXMQ_SLOTS - 1)]) { struct rxm * r; struct frct_cr * snd_cr; struct frct_cr * rcv_cr; + size_t slot; size_t rslot; ssize_t idx; - struct shm_du_buff * sdb; - uint8_t * head; + struct ssm_pk_buff * spb; + struct frct_pci * pci; struct flow * f; uint32_t snd_lwe; uint32_t rcv_lwe; - time_t rto; + size_t lvl = 0; r = list_entry(p, struct rxm, next); @@ -181,19 +173,18 @@ static void timerwheel_move(void) snd_cr = &r->frcti->snd_cr; rcv_cr = &r->frcti->rcv_cr; - f = &ai.flows[r->fd]; + f = &proc.flows[r->fd]; #ifndef RXM_BUFFER_ON_HEAP - shm_du_buff_ack(r->sdb); + ssm_pk_buff_ack(r->spb); #endif if (f->frcti == NULL - || f->flow_id != r->flow_id) + || f->info.id != r->flow_id) goto cleanup; pthread_rwlock_rdlock(&r->frcti->lock); snd_lwe = snd_cr->lwe; rcv_lwe = rcv_cr->lwe; - rto = r->frcti->rto; pthread_rwlock_unlock(&r->frcti->lock); @@ -205,76 +196,80 @@ static void timerwheel_move(void) if (ts_to_ns(now) - r->t0 > r->frcti->r) goto flow_down; - if (r->frcti->probe - && (r->frcti->rttseq + 1) == r->seqno) + pthread_rwlock_wrlock(&r->frcti->lock); + + if (r->seqno == r->frcti->rttseq) { + r->frcti->rto += + r->frcti->rto >> RTO_DIV; r->frcti->probe = false; + } +#ifdef PROC_FLOW_STATS + r->frcti->n_rtx++; +#endif + rslot = r->frcti->rto >> RXMQ_RES; + + pthread_rwlock_unlock(&r->frcti->lock); + + /* Schedule at least in the next time slot. */ + slot = ts_to_ns(now) >> RXMQ_RES; + + while (rslot >= RXMQ_SLOTS) { + ++lvl; + rslot >>= RXMQ_BUMP; + slot >>= RXMQ_BUMP; + } + + if (lvl >= RXMQ_LVLS) /* Can't reschedule */ + goto flow_down; + + rslot = (rslot + slot + 1) & (RXMQ_SLOTS - 1); #ifdef RXM_BLOCKING - #ifdef RXM_BUFFER_ON_HEAP - if (ipcp_sdb_reserve(&sdb, r->pkt_len)) - #else - if (ipcp_sdb_reserve(&sdb, r->tail - r->head)) - #endif + if (ipcp_spb_reserve(&spb, r->len) < 0) #else - #ifdef RXM_BUFFER_ON_HEAP - if (shm_rdrbuff_alloc(ai.rdrb, r->pkt_len, NULL, - &sdb)) - #else - if (shm_rdrbuff_alloc(ai.rdrb, - r->tail - r->head, NULL, - &sdb)) - #endif + if (ssm_pool_alloc(proc.pool, r->len, NULL, + &spb) < 0) #endif - goto reschedule; /* rbuff full */ - idx = shm_du_buff_get_idx(sdb); + goto reschedule; /* rdrbuff full */ - head = shm_du_buff_head(sdb); -#ifdef RXM_BUFFER_ON_HEAP - memcpy(head, r->pkt, r->pkt_len); -#else - memcpy(head, r->head, r->tail - r->head); - ipcp_sdb_release(r->sdb); - r->sdb = sdb; - r->head = head; - r->tail = shm_du_buff_tail(sdb); - shm_du_buff_wait_ack(sdb); + pci = (struct frct_pci *) ssm_pk_buff_head(spb); + memcpy(pci, r->pkt, r->len); +#ifndef RXM_BUFFER_ON_HEAP + ipcp_spb_release(r->spb); + r->spb = spb; + r->pkt = pci; + ssm_pk_buff_wait_ack(spb); #endif + idx = ssm_pk_buff_get_idx(spb); + /* Retransmit the copy. */ - ((struct frct_pci *) head)->ackno = - hton32(rcv_lwe); + pci->ackno = hton32(rcv_lwe); #ifdef RXM_BLOCKING - if (shm_rbuff_write_b(f->tx_rb, idx, NULL) == 0) + if (ssm_rbuff_write_b(f->tx_rb, idx, NULL) < 0) #else - if (shm_rbuff_write(f->tx_rb, idx) == 0) + if (ssm_rbuff_write(f->tx_rb, idx) < 0) #endif - shm_flow_set_notify(f->set, f->flow_id, - FLOW_PKT); - reschedule: - r->mul++; - - /* Schedule at least in the next time slot. */ - rslot = (rxm_slot - + MAX(((rto * r->mul) >> RXMQ_RES), 1)) - & (RXMQ_SLOTS - 1); - - list_add_tail(&r->next, &rw.rxms[i][rslot]); - + goto flow_down; + ssm_flow_set_notify(f->set, f->info.id, + FLOW_PKT); + reschedule: + list_add(&r->next, &rw.rxms[lvl][rslot]); continue; - flow_down: - shm_rbuff_set_acl(f->tx_rb, ACL_FLOWDOWN); - shm_rbuff_set_acl(f->rx_rb, ACL_FLOWDOWN); - cleanup: + flow_down: + ssm_rbuff_set_acl(f->tx_rb, ACL_FLOWDOWN); + ssm_rbuff_set_acl(f->rx_rb, ACL_FLOWDOWN); + cleanup: #ifdef RXM_BUFFER_ON_HEAP free(r->pkt); #else - ipcp_sdb_release(r->sdb); + ipcp_spb_release(r->spb); #endif free(r); } } + rw.prv_rxm[i] = rxm_slot & (RXMQ_SLOTS - 1); /* Move up a level in the wheel. */ rxm_slot >>= RXMQ_BUMP; - j >>= RXMQ_BUMP; } ack_slot = ts_to_ack_slot(now) & (ACKQ_SLOTS - 1) ; @@ -293,15 +288,14 @@ static void timerwheel_move(void) list_del(&a->next); - f = &ai.flows[a->fd]; + f = &proc.flows[a->fd]; rw.map[j & (ACKQ_SLOTS - 1)][a->fd] = false; - if (f->flow_id == a->flow_id && f->frcti != NULL) + if (f->info.id == a->flow_id && f->frcti != NULL) send_frct_pkt(a->frcti); free(a); - } } @@ -312,7 +306,7 @@ static void timerwheel_move(void) static int timerwheel_rxm(struct frcti * frcti, uint32_t seqno, - struct shm_du_buff * sdb) + struct ssm_pk_buff * spb) { struct timespec now; struct rxm * r; @@ -327,21 +321,19 @@ static int timerwheel_rxm(struct frcti * frcti, clock_gettime(PTHREAD_COND_CLOCK, &now); r->t0 = ts_to_ns(now); - r->mul = 0; r->seqno = seqno; r->frcti = frcti; + r->len = ssm_pk_buff_len(spb); #ifdef RXM_BUFFER_ON_HEAP - r->pkt_len = shm_du_buff_tail(sdb) - shm_du_buff_head(sdb); - r->pkt = malloc(r->pkt_len); + r->pkt = malloc(r->len); if (r->pkt == NULL) { free(r); return -ENOMEM; } - memcpy(r->pkt, shm_du_buff_head(sdb), r->pkt_len); + memcpy(r->pkt, ssm_pk_buff_head(spb), r->len); #else - r->sdb = sdb; - r->head = shm_du_buff_head(sdb); - r->tail = shm_du_buff_tail(sdb); + r->spb = spb; + r->pkt = (struct frct_pci *) ssm_pk_buff_head(spb); #endif pthread_rwlock_rdlock(&r->frcti->lock); @@ -349,7 +341,7 @@ static int timerwheel_rxm(struct frcti * frcti, slot = r->t0 >> RXMQ_RES; r->fd = frcti->fd; - r->flow_id = ai.flows[r->fd].flow_id; + r->flow_id = proc.flows[r->fd].info.id; pthread_rwlock_unlock(&r->frcti->lock); @@ -367,23 +359,21 @@ static int timerwheel_rxm(struct frcti * frcti, return -EPERM; } - slot = (slot + rto_slot) & (RXMQ_SLOTS - 1); + slot = (slot + rto_slot + 1) & (RXMQ_SLOTS - 1); pthread_mutex_lock(&rw.lock); list_add_tail(&r->next, &rw.rxms[lvl][slot]); #ifndef RXM_BUFFER_ON_HEAP - shm_du_buff_wait_ack(sdb); + ssm_pk_buff_wait_ack(spb); #endif pthread_mutex_unlock(&rw.lock); - __sync_bool_compare_and_swap(&rw.in_use, false, true); - return 0; } -static int timerwheel_ack(int fd, - struct frcti * frcti) +static int timerwheel_delayed_ack(int fd, + struct frcti * frcti) { struct timespec now; struct ack * a; @@ -395,18 +385,16 @@ static int timerwheel_ack(int fd, clock_gettime(PTHREAD_COND_CLOCK, &now); - slot = DELT_ACK >> ACKQ_RES; - if (slot >= ACKQ_SLOTS) { /* Out of timerwheel range. */ - free(a); - return -EPERM; - } + pthread_rwlock_rdlock(&frcti->lock); - slot = (((ts_to_ns(now) + DELT_ACK) >> ACKQ_RES) + 1) + slot = (((ts_to_ns(now) + (TICTIME << 1)) >> ACKQ_RES) + 1) & (ACKQ_SLOTS - 1); + pthread_rwlock_unlock(&frcti->lock); + a->fd = fd; a->frcti = frcti; - a->flow_id = ai.flows[fd].flow_id; + a->flow_id = proc.flows[fd].info.id; pthread_mutex_lock(&rw.lock); @@ -422,7 +410,5 @@ static int timerwheel_ack(int fd, pthread_mutex_unlock(&rw.lock); - __sync_bool_compare_and_swap(&rw.in_use, false, true); - return 0; } diff --git a/src/lib/tpm.c b/src/lib/tpm.c index dfba6492..ad5c1068 100644 --- a/src/lib/tpm.c +++ b/src/lib/tpm.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Threadpool management * @@ -26,21 +26,32 @@ #include <ouroboros/errno.h> #include <ouroboros/list.h> -#include <ouroboros/time_utils.h> +#include <ouroboros/pthread.h> +#include <ouroboros/time.h> #include <ouroboros/tpm.h> -#include <pthread.h> -#include <stdlib.h> +#ifdef CONFIG_OUROBOROS_DEBUG +#define OUROBOROS_PREFIX "tpm" +#include <ouroboros/logs.h> +#endif + #include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> -#define TPM_TIMEOUT 1000 +#define TPM_TIMEOUT 1000 struct pthr_el { struct list_head next; bool kill; bool busy; - + bool wait; +#ifdef CONFIG_OUROBOROS_DEBUG + struct timespec start; + struct timespec last; +#endif pthread_t thr; }; @@ -63,16 +74,45 @@ struct tpm { enum tpm_state state; pthread_cond_t cond; - pthread_mutex_t lock; + pthread_mutex_t mtx; pthread_t mgr; }; +#ifdef CONFIG_OUROBOROS_DEBUG +#define BETWEEN(a, x, y) ((a) > (x) && (a) <= (y)) +static void tpm_debug_thread(struct pthr_el * e) +{ + struct timespec now; + time_t diff; + time_t intv; + + if (e->wait || !e->busy) + return; + + clock_gettime(CLOCK_REALTIME, &now); + + diff = ts_diff_ms(&now, &e->start) / 1000; + intv = ts_diff_ms(&now, &e->last) / 1000; + + (void) diff; /* Never read if both build options off (0) */ + (void) intv; /* Never read if report option off (0) */ + + if (BETWEEN(TPM_DEBUG_REPORT_INTERVAL, 0, intv)) { + log_dbg("Thread %d:%lx running for %ld s.\n", + getpid(), (unsigned long) e->thr, diff); + e->last = now; + } + + if (BETWEEN(TPM_DEBUG_ABORT_TIMEOUT, 0, diff)) + assert(false); /* TODO: Grab a coffee and fire up GDB */ +} +#endif + static void tpm_join(struct tpm * tpm) { struct list_head * p; struct list_head * h; - list_for_each_safe(p, h, &tpm->pool) { struct pthr_el * e = list_entry(p, struct pthr_el, next); if (tpm->state != TPM_RUNNING) { @@ -86,15 +126,18 @@ static void tpm_join(struct tpm * tpm) list_for_each_safe(p, h, &tpm->pool) { struct pthr_el * e = list_entry(p, struct pthr_el, next); +#ifdef CONFIG_OUROBOROS_DEBUG + tpm_debug_thread(e); +#endif if (e->kill) { pthread_t thr = e->thr; list_del(&e->next); free(e); - pthread_mutex_unlock(&tpm->lock); + pthread_mutex_unlock(&tpm->mtx); pthread_join(thr, NULL); - pthread_mutex_lock(&tpm->lock); + pthread_mutex_lock(&tpm->mtx); } } } @@ -114,57 +157,59 @@ static void tpm_kill(struct tpm * tpm) } } -static void * tpmgr(void * o) +static int __tpm(struct tpm * tpm) { struct timespec dl; - struct timespec to = {(TPM_TIMEOUT / 1000), - (TPM_TIMEOUT % 1000) * MILLION}; - struct tpm * tpm = (struct tpm *) o; - - while (true) { - clock_gettime(PTHREAD_COND_CLOCK, &dl); - ts_add(&dl, &to, &dl); + struct timespec to = TIMESPEC_INIT_MS(TPM_TIMEOUT); - pthread_mutex_lock(&tpm->lock); + clock_gettime(PTHREAD_COND_CLOCK, &dl); + ts_add(&dl, &to, &dl); - if (tpm->state != TPM_RUNNING) { - tpm_join(tpm); - pthread_mutex_unlock(&tpm->lock); - break; - } + pthread_mutex_lock(&tpm->mtx); + if (tpm->state != TPM_RUNNING) { tpm_join(tpm); + pthread_mutex_unlock(&tpm->mtx); + return -1; + } - if (tpm->cur - tpm->wrk < tpm->min) { - size_t i; - for (i = 0; i < tpm->inc; ++i) { - struct pthr_el * e = malloc(sizeof(*e)); - if (e == NULL) - break; + tpm_join(tpm); - e->kill = false; - e->busy = false; + if (tpm->cur - tpm->wrk < tpm->min) { + size_t i; + for (i = 0; i < tpm->inc; ++i) { + struct pthr_el * e = malloc(sizeof(*e)); + if (e == NULL) + break; - if (pthread_create(&e->thr, NULL, - tpm->func, tpm->o)) { - free(e); - break; - } + memset(e, 0, sizeof(*e)); - list_add(&e->next, &tpm->pool); + if (pthread_create(&e->thr, NULL, tpm->func, tpm->o)) { + free(e); + break; } - tpm->cur += i; + list_add(&e->next, &tpm->pool); } - if (pthread_cond_timedwait(&tpm->cond, &tpm->lock, &dl) - == ETIMEDOUT) - if (tpm->cur - tpm->wrk > tpm->min) - tpm_kill(tpm); - - pthread_mutex_unlock(&tpm->lock); + tpm->cur += i; } + pthread_cleanup_push(__cleanup_mutex_unlock, &tpm->mtx); + + if (pthread_cond_timedwait(&tpm->cond, &tpm->mtx, &dl) == ETIMEDOUT) + if (tpm->cur - tpm->wrk > tpm->min) + tpm_kill(tpm); + + pthread_cleanup_pop(true); + + return 0; +} + +static void * tpmgr(void * o) +{ + while (__tpm((struct tpm *) o) == 0); + return (void *) 0; } @@ -176,11 +221,14 @@ struct tpm * tpm_create(size_t min, struct tpm * tpm; pthread_condattr_t cattr; + assert(func != NULL); + assert(inc > 0); + tpm = malloc(sizeof(*tpm)); if (tpm == NULL) goto fail_malloc; - if (pthread_mutex_init(&tpm->lock, NULL)) + if (pthread_mutex_init(&tpm->mtx, NULL)) goto fail_lock; if (pthread_condattr_init(&cattr)) @@ -209,7 +257,7 @@ struct tpm * tpm_create(size_t min, fail_cond: pthread_condattr_destroy(&cattr); fail_cattr: - pthread_mutex_destroy(&tpm->lock); + pthread_mutex_destroy(&tpm->mtx); fail_lock: free(tpm); fail_malloc: @@ -218,34 +266,39 @@ struct tpm * tpm_create(size_t min, int tpm_start(struct tpm * tpm) { - pthread_mutex_lock(&tpm->lock); + pthread_mutex_lock(&tpm->mtx); if (pthread_create(&tpm->mgr, NULL, tpmgr, tpm)) { - pthread_mutex_unlock(&tpm->lock); + pthread_mutex_unlock(&tpm->mtx); return -1; } tpm->state = TPM_RUNNING; - pthread_mutex_unlock(&tpm->lock); + pthread_mutex_unlock(&tpm->mtx); return 0; } void tpm_stop(struct tpm * tpm) { - pthread_mutex_lock(&tpm->lock); + pthread_mutex_lock(&tpm->mtx); + + if (tpm->state != TPM_RUNNING) { + pthread_mutex_unlock(&tpm->mtx); + return; + } tpm->state = TPM_NULL; - pthread_mutex_unlock(&tpm->lock); + pthread_mutex_unlock(&tpm->mtx); + + pthread_join(tpm->mgr, NULL); } void tpm_destroy(struct tpm * tpm) { - pthread_join(tpm->mgr, NULL); - - pthread_mutex_destroy(&tpm->lock); + pthread_mutex_destroy(&tpm->mtx); pthread_cond_destroy(&tpm->cond); free(tpm); @@ -261,40 +314,61 @@ static struct pthr_el * tpm_pthr_el(struct tpm * tpm, e = list_entry(p, struct pthr_el, next); if (e->thr == thr) return e; - } return NULL; } -void tpm_inc(struct tpm * tpm) +void tpm_begin_work(struct tpm * tpm) { struct pthr_el * e; - pthread_mutex_lock(&tpm->lock); +#ifdef CONFIG_OUROBOROS_DEBUG + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); +#endif + + pthread_mutex_lock(&tpm->mtx); e = tpm_pthr_el(tpm, pthread_self()); if (e != NULL) { - e->busy = false; - --tpm->wrk; + e->busy = true; + ++tpm->wrk; +#ifdef CONFIG_OUROBOROS_DEBUG + e->start = now; + e->last = now; +#endif } - pthread_mutex_unlock(&tpm->lock); + pthread_cond_signal(&tpm->cond); + + pthread_mutex_unlock(&tpm->mtx); +} + +void tpm_wait_work(struct tpm * tpm) +{ + struct pthr_el * e; + + pthread_mutex_lock(&tpm->mtx); + + e = tpm_pthr_el(tpm, pthread_self()); + if (e != NULL) + e->wait = true; + + pthread_mutex_unlock(&tpm->mtx); } -void tpm_dec(struct tpm * tpm) +void tpm_end_work(struct tpm * tpm) { struct pthr_el * e; - pthread_mutex_lock(&tpm->lock); + pthread_mutex_lock(&tpm->mtx); e = tpm_pthr_el(tpm, pthread_self()); if (e != NULL) { - e->busy = true; - ++tpm->wrk; + e->busy = false; + --tpm->wrk; } - pthread_cond_signal(&tpm->cond); - - pthread_mutex_unlock(&tpm->lock); + pthread_mutex_unlock(&tpm->mtx); } diff --git a/src/lib/utils.c b/src/lib/utils.c index 931ee449..cfddec62 100644 --- a/src/lib/utils.c +++ b/src/lib/utils.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Handy utilities * @@ -20,29 +20,48 @@ * Foundation, Inc., http://www.fsf.org/about/contact/. */ +#define _DEFAULT_SOURCE + +#include "config.h" + +#include <ouroboros/utils.h> + +#include <ctype.h> +#include <grp.h> +#include <pwd.h> #include <stdlib.h> #include <string.h> +int bufcmp(const buffer_t * a, + const buffer_t * b) +{ + if (a->len != b->len) + return a->len < b->len ? -1 : 1; + + return memcmp(a->data, b->data, a->len); +} + + int n_digits(unsigned i) { - int n = 1; + int n = 1; - while (i > 9) { - ++n; - i /= 10; - } + while (i > 9) { + ++n; + i /= 10; + } - return n; + return n; } -char * path_strip(char * src) +char * path_strip(const char * src) { - char * dst = NULL; + char * dst; if (src == NULL) return NULL; - dst = src + strlen(src); + dst = (char *) src + strlen(src); while (dst > src && *dst != '/') --dst; @@ -52,3 +71,134 @@ char * path_strip(char * src) return dst; } + +char * trim_whitespace(char * str) +{ + char * end; + + while (isspace((unsigned char) *str)) + str++; + + if (*str == '\0') + return str; + + /* Trim trailing space */ + end = str + strlen(str) - 1; + while (end > str && isspace((unsigned char)*end)) + *end-- = '\0'; + + return str; +} + +size_t argvlen(const char ** argv) +{ + size_t argc = 0; + + if (argv == NULL) + return 0; + + while (*argv++ != NULL) + argc++; + + return argc; +} + +void argvfree(char ** argv) +{ + char ** argv_dup; + + if (argv == NULL) + return; + + argv_dup = argv; + while (*argv_dup != NULL) + free(*(argv_dup++)); + + free(argv); +} + +char ** argvdup(char ** argv) +{ + int argc = 0; + char ** argv_dup = argv; + int i; + + if (argv == NULL) + return NULL; + + while (*(argv_dup++) != NULL) + argc++; + + argv_dup = malloc((argc + 1) * sizeof(*argv_dup)); + if (argv_dup == NULL) + return NULL; + + for (i = 0; i < argc; ++i) { + argv_dup[i] = strdup(argv[i]); + if (argv_dup[i] == NULL) { + argvfree(argv_dup); + return NULL; + } + } + + argv_dup[argc] = NULL; + + return argv_dup; +} + +bool is_ouroboros_member_uid(uid_t uid) +{ + struct group * grp; + struct passwd * pw; + gid_t gid; + gid_t * groups = NULL; + int ngroups; + int i; + + /* Root is always privileged */ + if (uid == 0) + return true; + + grp = getgrnam("ouroboros"); + if (grp == NULL) + return false; + + gid = grp->gr_gid; + + pw = getpwuid(uid); + if (pw == NULL) + return false; + + if (pw->pw_gid == gid) + return true; + + ngroups = 0; + getgrouplist(pw->pw_name, pw->pw_gid, NULL, &ngroups); + if (ngroups <= 0) + return false; + + groups = malloc(ngroups * sizeof(*groups)); + if (groups == NULL) + return false; + + if (getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups) < 0) { + free(groups); + return false; + } + + for (i = 0; i < ngroups; i++) { + if (groups[i] == gid) { + free(groups); + return true; + } + } + + free(groups); + + return false; +} + +bool is_ouroboros_member(void) +{ + return is_ouroboros_member_uid(getuid()); +} diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 7c40d9ae..3cec8172 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -1,9 +1,71 @@ -add_subdirectory(irm) -add_subdirectory(ocbr) -add_subdirectory(oecho) -add_subdirectory(obc) -add_subdirectory(oping) -add_subdirectory(operf) -if (CMAKE_SYSTEM_NAME STREQUAL "Linux") - add_subdirectory(ovpn) -endif () +# Tools build configuration + +set(TOOLS_INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_BINARY_DIR}/include +) + +set(IRM_SOURCES + irm/irm.c + irm/irm_bind_program.c + irm/irm_bind_process.c + irm/irm_bind_ipcp.c + irm/irm_ipcp_create.c + irm/irm_ipcp_destroy.c + irm/irm_ipcp_bootstrap.c + irm/irm_ipcp_enroll.c + irm/irm_ipcp_list.c + irm/irm_ipcp_connect.c + irm/irm_ipcp_disconnect.c + irm/irm_unbind_program.c + irm/irm_unbind_process.c + irm/irm_unbind_ipcp.c + irm/irm_unbind.c + irm/irm_bind.c + irm/irm_ipcp.c + irm/irm_name.c + irm/irm_name_create.c + irm/irm_name_destroy.c + irm/irm_name_reg.c + irm/irm_name_unreg.c + irm/irm_name_list.c + irm/irm_utils.c +) + +add_executable(irm ${IRM_SOURCES}) +target_include_directories(irm PRIVATE ${TOOLS_INCLUDE_DIRS}) +target_link_libraries(irm PRIVATE ouroboros-irm) +install(TARGETS irm RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}) + +add_executable(oping oping/oping.c) +target_include_directories(oping PRIVATE ${TOOLS_INCLUDE_DIRS}) +target_link_libraries(oping PRIVATE ${LIBM_LIBRARIES} ouroboros-dev) +install(TARGETS oping RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_executable(oecho oecho/oecho.c) +target_include_directories(oecho PRIVATE ${TOOLS_INCLUDE_DIRS}) +target_link_libraries(oecho PRIVATE ouroboros-dev) +install(TARGETS oecho RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_executable(ocbr ocbr/ocbr.c) +target_include_directories(ocbr PRIVATE ${TOOLS_INCLUDE_DIRS}) +target_link_libraries(ocbr PRIVATE ouroboros-dev) +install(TARGETS ocbr RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_executable(obc obc/obc.c) +target_include_directories(obc PRIVATE ${TOOLS_INCLUDE_DIRS}) +target_link_libraries(obc PRIVATE ouroboros-dev) +install(TARGETS obc RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +add_executable(operf operf/operf.c) +target_include_directories(operf PRIVATE ${TOOLS_INCLUDE_DIRS}) +target_link_libraries(operf PRIVATE ouroboros-dev) +install(TARGETS operf RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + add_executable(ovpn ovpn/ovpn.c) + target_include_directories(ovpn PRIVATE ${TOOLS_INCLUDE_DIRS}) + target_link_libraries(ovpn PRIVATE ouroboros-dev) + install(TARGETS ovpn RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +endif() diff --git a/src/tools/irm/CMakeLists.txt b/src/tools/irm/CMakeLists.txt deleted file mode 100644 index e5e5c466..00000000 --- a/src/tools/irm/CMakeLists.txt +++ /dev/null @@ -1,39 +0,0 @@ -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) - -include_directories(${CMAKE_SOURCE_DIR}/include) -include_directories(${CMAKE_BINARY_DIR}/include) - -set(SOURCE_FILES - # Add source files here - irm.c - irm_bind_program.c - irm_bind_process.c - irm_bind_ipcp.c - irm_ipcp_create.c - irm_ipcp_destroy.c - irm_ipcp_bootstrap.c - irm_ipcp_enroll.c - irm_ipcp_list.c - irm_ipcp_connect.c - irm_ipcp_disconnect.c - irm_unbind_program.c - irm_unbind_process.c - irm_unbind_ipcp.c - irm_unbind.c - irm_bind.c - irm_ipcp.c - irm_name.c - irm_name_create.c - irm_name_destroy.c - irm_name_reg.c - irm_name_unreg.c - irm_name_list.c - irm_utils.c - ) - -add_executable(irm ${SOURCE_FILES}) - -target_link_libraries(irm LINK_PUBLIC ouroboros-irm) - -install(TARGETS irm RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR}) diff --git a/src/tools/irm/irm.c b/src/tools/irm/irm.c index 0078025b..ba0f4713 100644 --- a/src/tools/irm/irm.c +++ b/src/tools/irm/irm.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * A tool to instruct the IRM daemon * @@ -49,7 +49,7 @@ static void usage(void) { printf("Usage: irm [OPERATION]\n\n" - "where OPERATION = {ipcp bind unbind name}\n"); + "where OPERATION in { ipcp bind unbind name }\n"); } static int do_help(int argc, diff --git a/src/tools/irm/irm_bind.c b/src/tools/irm/irm_bind.c index 86294f67..2e8b14ef 100644 --- a/src/tools/irm/irm_bind.c +++ b/src/tools/irm/irm_bind.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Bind names in the processing system * @@ -47,7 +47,7 @@ static void usage(void) { printf("Usage: irm bind [OPERATION]\n" "\n" - "where OPERATION = {program process ipcp help}\n"); + "where OPERATION in {program process ipcp help}\n"); } static int do_help(int argc, diff --git a/src/tools/irm/irm_bind_ipcp.c b/src/tools/irm/irm_bind_ipcp.c index d95a0c97..7d5dd636 100644 --- a/src/tools/irm/irm_bind_ipcp.c +++ b/src/tools/irm/irm_bind_ipcp.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Bind IPCP Instance to a name * @@ -55,11 +55,11 @@ static void usage(void) int do_bind_ipcp(int argc, char ** argv) { - char * ipcp = NULL; - char * name = NULL; - struct ipcp_info * ipcps; - ssize_t len; - ssize_t i; + char * ipcp = NULL; + char * name = NULL; + struct ipcp_list_info * ipcps; + ssize_t len; + ssize_t i; while (argc > 0) { if (matches(*argv, "name") == 0) { diff --git a/src/tools/irm/irm_bind_process.c b/src/tools/irm/irm_bind_process.c index 5fd9e740..fffd5fe9 100644 --- a/src/tools/irm/irm_bind_process.c +++ b/src/tools/irm/irm_bind_process.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Bind a process to a name * diff --git a/src/tools/irm/irm_bind_program.c b/src/tools/irm/irm_bind_program.c index d36521dd..8a0dc33c 100644 --- a/src/tools/irm/irm_bind_program.c +++ b/src/tools/irm/irm_bind_program.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Bind programs to a name * diff --git a/src/tools/irm/irm_ipcp.c b/src/tools/irm/irm_ipcp.c index 9e539ca4..63e617d9 100644 --- a/src/tools/irm/irm_ipcp.c +++ b/src/tools/irm/irm_ipcp.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * A tool to instruct the IRM daemon * @@ -44,10 +44,11 @@ static void usage(void) { printf("Usage: irm ipcp [OPERATION]\n\n" - "where OPERATION = {create destroy\n" - " bootstrap enroll\n" - " connect disconnect\n" - " list help}\n"); + "where OPERATION in {create destroy\n" + " bootstrap enroll\n" + " connect disconnect\n" + " list\n" + " help}\n"); } static int do_help(int argc, char **argv) diff --git a/src/tools/irm/irm_ipcp_bootstrap.c b/src/tools/irm/irm_ipcp_bootstrap.c index a246d661..3fabc3cc 100644 --- a/src/tools/irm/irm_ipcp_bootstrap.c +++ b/src/tools/irm/irm_ipcp_bootstrap.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Bootstrap IPC Processes * @@ -46,119 +46,166 @@ #include <stdlib.h> #include <string.h> #include <arpa/inet.h> +#include <netinet/in.h> #ifdef __FreeBSD__ #include <sys/socket.h> #endif -#define UNICAST "unicast" -#define BROADCAST "broadcast" -#define UDP "udp" -#define ETH_LLC "eth-llc" -#define ETH_DIX "eth-dix" -#define LOCAL "local" - -#define MD5 "MD5" -#define SHA3_224 "SHA3_224" -#define SHA3_256 "SHA3_256" -#define SHA3_384 "SHA3_384" -#define SHA3_512 "SHA3_512" - -#define DEFAULT_ADDR_SIZE 4 -#define DEFAULT_EID_SIZE 8 -#define DEFAULT_DDNS 0 -#define DEFAULT_TTL 60 -#define DEFAULT_ADDR_AUTH ADDR_AUTH_FLAT_RANDOM -#define DEFAULT_ROUTING ROUTING_LINK_STATE -#define DEFAULT_CONG_AVOID CA_MB_ECN -#define DEFAULT_HASH_ALGO DIR_HASH_SHA3_256 -#define DEFAULT_ETHERTYPE 0xA000 -#define DEFAULT_UDP_PORT 0x0D6B /* 3435 */ - -#define FLAT_RANDOM_ADDR_AUTH "flat" -#define LINK_STATE_ROUTING "link_state" -#define LINK_STATE_LFA_ROUTING "lfa" -#define LINK_STATE_ECM_ROUTING "ecmp" -#define NONE_CA "none" -#define MB_ECN_CA "mb-ecn" +#define UNICAST "unicast" +#define BROADCAST "broadcast" +#define IP_UDP4 "udp4" +#define IP_UDP6 "udp6" +#define ETH_LLC "eth-llc" +#define ETH_DIX "eth-dix" +#define LOCAL "local" + +#define MD5 "MD5" +#define SHA3_224 "SHA3_224" +#define SHA3_256 "SHA3_256" +#define SHA3_384 "SHA3_384" +#define SHA3_512 "SHA3_512" + +#define FLAT_RANDOM "flat" +#define DHT_DIR "DHT" +#define LINK_STATE "link_state" +#define LINK_STATE_LFA "lfa" +#define LINK_STATE_ECM "ecmp" +#define NONE_CA "none" +#define MB_ECN_CA "mb-ecn" + +#define DT(x) default_dt_config.x +#define DHT(x) default_dht_config.params.x +#define UNI(x) default_uni_config.x +#define DIX(x) eth_dix_default_conf.eth.x +#define LLC(x) eth_llc_default_conf.eth.x +#define UD4(x) udp4_default_conf.udp4.x +#define UD6(x) udp6_default_conf.udp6.x + +static char * usage_str = \ + "Usage: irm ipcp bootstrap\n" + " name <ipcp name>\n" + " layer <layer name>\n" + " [type [TYPE]]\n" + "where TYPE in {" UNICAST " " BROADCAST " " LOCAL " " + IP_UDP4 " " IP_UDP6 " " ETH_LLC " " ETH_DIX "},\n\n" + "if TYPE == " UNICAST "\n" + " [addr_auth <ADDRESS_POLICY> (default: %s)]\n" + " [directory <DIRECTORY_POLICY> (default: %s)]\n" + " [hash [ALGORITHM] (default: %s)]\n" + " [routing <ROUTING_POLICY> (default: %s)]\n" + " [congestion <CONG_POLICY> (default: %s)]\n" + " [autobind]\n\n" + "where ADDRESS_POLICY in {" FLAT_RANDOM "}\n" + " DIRECTORY_POLICY in {" DHT_DIR "}\n" + " ALGORITHM in {" SHA3_224 " " SHA3_256 " " + SHA3_384 " " SHA3_512 "}\n" + " ROUTING_POLICY in {" LINK_STATE " " + LINK_STATE_LFA " " LINK_STATE_ECM "}\n" + " CONG_POLICY in {" NONE_CA " " MB_ECN_CA "}\n" + " [Data Transfer Constants]\n" + " [addr <address size> (default: %d)]\n" + " [eid <eid size> (default: %d)]\n" + " [ttl <max time-to-live>, default: %d)]\n\n" + "if DIRECTORY_POLICY == " DHT_DIR "\n" + " [dht_alpha <search factor> (default: %u)]\n" + " [dht_k <replication factor> (default: %u)]\n" + " [dht_t_expire <expiration (s)> (default: %u)]\n" + " [dht_t_refresh <contact refresh (s)> (default: %u)]\n" + " [dht_t_replicate <replication (s)> (default: %u)]\n\n" + "if ROUTING_POLICY == " LINK_STATE "\n" + " [ls_t_recalc <pff recalc interval (s)> (default: %ld)]\n" + " [ls_t_update <LSA update interval (s)> (default: %ld)]\n" + " [ls_t_timeo <link timeout (s)> (default: %ld)]\n\n" + "if TYPE == " IP_UDP4 "\n" + " ip <IP address in dotted notation>\n" + " [port <UDP port> (default: %d)]\n" + " [dns <DDNS IPv4 address in dotted notation>" + " (default: none)]\n\n" + "if TYPE == " IP_UDP6 "\n" + " ip <IPv6 address>\n" + " [port <UDP port> (default: %d)]\n" + " [dns <DDNS IPv6 address>" + " (default: none)]\n\n" + + "if TYPE == " ETH_LLC "\n" + " dev <interface name>\n" + " [hash [ALGORITHM] (default: %s)]\n" + "where ALGORITHM in {" SHA3_224 " " SHA3_256 " " + SHA3_384 " " SHA3_512 "}\n\n" + "if TYPE == " ETH_DIX "\n" + " dev <interface name>\n" + " [ethertype <ethertype> (default: 0x%4X)]\n" + " [hash [ALGORITHM] (default: %s)]\n" + "where ALGORITHM in {" SHA3_224 " " SHA3_256 " " + SHA3_384 " " SHA3_512 "}\n\n" + "if TYPE == " LOCAL "\n" + " [hash [ALGORITHM] (default: %s)]\n" + "where ALGORITHM in {" SHA3_224 " " SHA3_256 " " + SHA3_384 " " SHA3_512 "}\n\n" + "if TYPE == " BROADCAST "\n" + " [autobind]\n\n"; static void usage(void) { /* FIXME: Add ipcp_config stuff. */ - printf("Usage: irm ipcp bootstrap\n" - " name <ipcp name>\n" - " layer <layer name>\n" - " [type [TYPE]]\n" - "where TYPE = {" UNICAST " " BROADCAST " " LOCAL " " - UDP " " ETH_LLC " " ETH_DIX "},\n\n" - "if TYPE == " UNICAST "\n" - " [addr <address size> (default: %d)]\n" - " [eid <eid size> (default: %d)]\n" - " [ttl (max time-to-live value, default: %d)]\n" - " [addr_auth <ADDRESS_POLICY> (default: %s)]\n" - " [routing <ROUTING_POLICY> (default: %s)]\n" - " [congestion <CONG_POLICY> (default: %s)]\n" - " [hash [ALGORITHM] (default: %s)]\n" - " [autobind]\n" - "where ADDRESS_POLICY = {" FLAT_RANDOM_ADDR_AUTH "}\n" - " ROUTING_POLICY = {" LINK_STATE_ROUTING " " - LINK_STATE_LFA_ROUTING " " LINK_STATE_ECM_ROUTING "}\n" - " CONG_POLICY = {" NONE_CA " " MB_ECN_CA "}\n" - " ALGORITHM = {" SHA3_224 " " SHA3_256 " " - SHA3_384 " " SHA3_512 "}\n\n" - "if TYPE == " UDP "\n" - " ip <IP address in dotted notation>\n" - " [port <UDP port> (default: %d)]\n" - " [dns <DDNS IP address in dotted notation>" - " (default: none)]\n\n" - "if TYPE == " ETH_LLC "\n" - " dev <interface name>\n" - " [hash [ALGORITHM] (default: %s)]\n" - "where ALGORITHM = {" SHA3_224 " " SHA3_256 " " - SHA3_384 " " SHA3_512 "}\n\n" - "if TYPE == " ETH_DIX "\n" - " dev <interface name>\n" - " [ethertype <ethertype> (default: 0x%4X)]\n" - " [hash [ALGORITHM] (default: %s)]\n" - "where ALGORITHM = {" SHA3_224 " " SHA3_256 " " - SHA3_384 " " SHA3_512 "}\n\n" - "if TYPE == " LOCAL "\n" - " [hash [ALGORITHM] (default: %s)]\n" - "where ALGORITHM = {" SHA3_224 " " SHA3_256 " " - SHA3_384 " " SHA3_512 "}\n\n" - "if TYPE == " BROADCAST "\n" - " [autobind]\n\n", - DEFAULT_ADDR_SIZE, DEFAULT_EID_SIZE, DEFAULT_TTL, - FLAT_RANDOM_ADDR_AUTH, LINK_STATE_ROUTING, MB_ECN_CA, - SHA3_256, DEFAULT_UDP_PORT, SHA3_256, 0xA000, SHA3_256, + printf(usage_str, + /* unicast */ + FLAT_RANDOM, DHT_DIR, SHA3_256, LINK_STATE, MB_ECN_CA, + /* dt */ + DT(addr_size), DT(eid_size), DT(max_ttl), + /* dht */ + DHT(alpha), DHT(k), DHT(t_expire), + DHT(t_refresh), DHT(t_replicate), + /* ls */ + default_ls_config.t_recalc, default_ls_config.t_update, + default_ls_config.t_timeo, + /* udp4 */ + UD4(port), + /* udp6 */ + UD6(port), + /* eth_llc */ + SHA3_256, + /* eth_dix */ + DIX(ethertype), + SHA3_256, + /* local */ + SHA3_256, + /* broadcast */ SHA3_256); } int do_bootstrap_ipcp(int argc, char ** argv) { - char * ipcp = NULL; - pid_t pid = -1; - struct ipcp_config conf; - uint8_t addr_size = DEFAULT_ADDR_SIZE; - uint8_t eid_size = DEFAULT_EID_SIZE; - uint8_t max_ttl = DEFAULT_TTL; - enum pol_addr_auth addr_auth_type = DEFAULT_ADDR_AUTH; - enum pol_routing routing_type = DEFAULT_ROUTING; - enum pol_dir_hash hash_algo = DEFAULT_HASH_ALGO; - enum pol_cong_avoid cong_avoid = DEFAULT_CONG_AVOID; - uint32_t ip_addr = 0; - uint32_t dns_addr = DEFAULT_DDNS; - char * ipcp_type = NULL; - enum ipcp_type type = IPCP_INVALID; - char * layer = NULL; - char * dev = NULL; - uint16_t ethertype = DEFAULT_ETHERTYPE; - struct ipcp_info * ipcps; - ssize_t len = 0; - int i = 0; - bool autobind = false; - int cargs; - int port = DEFAULT_UDP_PORT; + char * ipcp = NULL; + pid_t pid = -1; + struct ipcp_config conf; + struct dir_config dir_config = default_dir_config; + uint8_t addr_size = DT(addr_size); + uint8_t eid_size = DT(eid_size); + uint8_t max_ttl = DT(max_ttl); + struct routing_config routing = default_routing_config; + enum pol_addr_auth addr_auth_type = UNI(addr_auth_type); + enum pol_cong_avoid cong_avoid = UNI(cong_avoid); + enum pol_dir_hash hash_algo = DIR_HASH_SHA3_256; + char * ipstr = NULL; + char * dnsstr = NULL; + struct in_addr ip4_addr = {.s_addr = INADDR_ANY}; + struct in_addr dns4_addr = UD4(dns_addr); + int port4 = UD4(port); + struct in6_addr ip6_addr = IN6ADDR_ANY_INIT; + struct in6_addr dns6_addr = UD6(dns_addr); + int port6 = UD6(port); + char * ipcp_type = NULL; + enum ipcp_type type = IPCP_INVALID; + char * layer = NULL; + char * dev = NULL; + uint16_t ethertype = DIX(ethertype); + struct ipcp_list_info * ipcps; + ssize_t len = 0; + int i = 0; + bool autobind = false; + int cargs; while (argc > 0) { cargs = 2; @@ -180,11 +227,9 @@ int do_bootstrap_ipcp(int argc, else goto unknown_param; } else if (matches(*argv, "ip") == 0) { - if (inet_pton (AF_INET, *(argv + 1), &ip_addr) != 1) - goto unknown_param; + ipstr = *(argv + 1); } else if (matches(*argv, "dns") == 0) { - if (inet_pton(AF_INET, *(argv + 1), &dns_addr) != 1) - goto unknown_param; + dnsstr = *(argv + 1); } else if (matches(*argv, "device") == 0) { dev = *(argv + 1); } else if (matches(*argv, "ethertype") == 0) { @@ -193,7 +238,7 @@ int do_bootstrap_ipcp(int argc, ethertype = strtol(*(argv + 1), NULL, 0); else ethertype = strtol(*(argv + 1), NULL, 16); - if (ethertype < 0x0600 || ethertype == 0xFFFF) { + if (ethertype < 0x0600 || ethertype >= 0xFFFF) { printf("Invalid Ethertype: \"%s\".\n" "Recommended range: 0xA000-0xEFFF.\n", *(argv + 1)); @@ -206,26 +251,50 @@ int do_bootstrap_ipcp(int argc, } else if (matches(*argv, "ttl") == 0) { max_ttl = atoi(*(argv + 1)); } else if (matches(*argv, "port") == 0) { - port = atoi(*(argv + 1)); + port4 = atoi(*(argv + 1)); + port6 = port4; } else if (matches(*argv, "autobind") == 0) { autobind = true; cargs = 1; } else if (matches(*argv, "addr_auth") == 0) { - if (strcmp(FLAT_RANDOM_ADDR_AUTH, *(argv + 1)) == 0) + if (strcmp(FLAT_RANDOM, *(argv + 1)) == 0) addr_auth_type = ADDR_AUTH_FLAT_RANDOM; else goto unknown_param; - } else if (matches(*argv, "routing") == 0) { - if (strcmp(LINK_STATE_ROUTING, *(argv + 1)) == 0) - routing_type = ROUTING_LINK_STATE; - else if (strcmp(LINK_STATE_LFA_ROUTING, - *(argv + 1)) == 0) - routing_type = ROUTING_LINK_STATE_LFA; - else if (strcmp(LINK_STATE_ECM_ROUTING, - *(argv + 1)) == 0) - routing_type = ROUTING_LINK_STATE_ECMP; + } else if (matches(*argv, "directory") == 0) { + if (strcmp(DHT_DIR, *(argv + 1)) == 0) + dir_config.pol = DIR_DHT; else goto unknown_param; + } else if (matches(*argv, "dht_alpha") == 0) { + dir_config.dht.params.alpha = atoi(*(argv + 1)); + } else if (matches(*argv, "dht_k") == 0) { + dir_config.dht.params.k = atoi(*(argv + 1)); + } else if (matches(*argv, "dht_t_expire") == 0) { + dir_config.dht.params.t_expire = atoi(*(argv + 1)); + } else if (matches(*argv, "dht_t_refresh") == 0) { + dir_config.dht.params.t_refresh = atoi(*(argv + 1)); + } else if (matches(*argv, "dht_t_replicate") == 0) { + dir_config.dht.params.t_replicate = atoi(*(argv + 1)); + } else if (matches(*argv, "routing") == 0) { + if (strcmp(LINK_STATE, *(argv + 1)) == 0) { + routing.pol = ROUTING_LINK_STATE; + routing.ls.pol = LS_SIMPLE; + } else if (strcmp(LINK_STATE_LFA, *(argv + 1)) == 0) { + routing.pol = ROUTING_LINK_STATE; + routing.ls.pol = LS_LFA; + } else if (strcmp(LINK_STATE_ECM, *(argv + 1)) == 0) { + routing.pol = ROUTING_LINK_STATE; + routing.ls.pol = LS_ECMP; + } else { + goto unknown_param; + } + } else if (matches(*argv, "ls_t_timeo") == 0) { + routing.ls.t_timeo = atoi(*(argv + 1)); + } else if (matches(*argv, "ls_t_update") == 0) { + routing.ls.t_update = atoi(*(argv + 1)); + } else if (matches(*argv, "ls_t_recalc") == 0) { + routing.ls.t_recalc = atoi(*(argv + 1)); } else if (matches(*argv, "congestion") == 0) { if (strcmp(NONE_CA, *(argv + 1)) == 0) cong_avoid = CA_NONE; @@ -257,21 +326,59 @@ int do_bootstrap_ipcp(int argc, } if (ipcp_type != NULL) { - if (strcmp(ipcp_type, UNICAST) == 0) + if (matches(ipcp_type, UNICAST) == 0) type = IPCP_UNICAST; - else if (strcmp(ipcp_type, BROADCAST) == 0) + else if (matches(ipcp_type, BROADCAST) == 0) type = IPCP_BROADCAST; - else if (strcmp(ipcp_type, UDP) == 0) - type = IPCP_UDP; - else if (strcmp(ipcp_type, ETH_LLC) == 0) - type = IPCP_ETH_LLC; - else if (strcmp(ipcp_type, ETH_DIX) == 0) + else if (matches(ipcp_type, IP_UDP4) == 0) + type = IPCP_UDP4; + else if (matches(ipcp_type, IP_UDP6) == 0) + type = IPCP_UDP6; + else if (matches(ipcp_type, ETH_DIX) == 0) type = IPCP_ETH_DIX; - else if (strcmp(ipcp_type, LOCAL) == 0) + else if (matches(ipcp_type, ETH_LLC) == 0) + type = IPCP_ETH_LLC; + else if (matches(ipcp_type, LOCAL) == 0) type = IPCP_LOCAL; else goto fail_usage; } + if (type == IPCP_UDP4) { + if (inet_pton (AF_INET, ipstr, &ip4_addr) != 1) { + printf("Invalid IPv4 address: \"%s\".\n", ipstr); + goto fail_usage; + } + + if (ip4_addr.s_addr == INADDR_ANY) { + printf("Cannot use IPv4 address: \"%s\".\n", ipstr); + goto fail_usage; + } + + if (dnsstr != NULL && + inet_pton(AF_INET, dnsstr, &dns4_addr) != 1) { + printf("Invalid DNS IPv4 address: \"%s\".\n", dnsstr); + goto fail_usage; + } + } + + if (type == IPCP_UDP6) { + if (inet_pton(AF_INET6, ipstr, &ip6_addr) != 1) { + printf("Invalid IPv6 address: \"%s\".\n", ipstr); + goto fail_usage; + } + + if (IN6_IS_ADDR_UNSPECIFIED(&ip6_addr)) { + printf("Cannot use IPv6 address: \"%s\".\n", ipstr); + goto fail_usage; + } + + if (dnsstr != NULL && + inet_pton(AF_INET6, dnsstr, &dns6_addr) != 1) { + printf("Invalid DNS IPv6 address: \"%s\".\n", dnsstr); + goto fail_usage; + } + } + if (pid == -1) { if (ipcp_type == NULL) { printf("No IPCPs matching %s found.\n\n", ipcp); @@ -292,6 +399,7 @@ int do_bootstrap_ipcp(int argc, printf("Types do not match.\n\n"); goto fail; } + conf.type = ipcps[i].type; if (autobind && (conf.type != IPCP_UNICAST && @@ -302,40 +410,45 @@ int do_bootstrap_ipcp(int argc, } if (strlen(layer) > LAYER_NAME_SIZE) { - printf("Layer name too big.\n\n"); + printf("Layer name too long.\n\n"); goto fail_usage; } - strcpy(conf.layer_info.layer_name, layer); - if (conf.type != IPCP_UDP) - conf.layer_info.dir_hash_algo = hash_algo; + strcpy(conf.layer_info.name, layer); + conf.layer_info.dir_hash_algo = hash_algo; switch (conf.type) { case IPCP_UNICAST: - conf.addr_size = addr_size; - conf.eid_size = eid_size; - conf.max_ttl = max_ttl; - conf.addr_auth_type = addr_auth_type; - conf.routing_type = routing_type; - conf.cong_avoid = cong_avoid; + conf.unicast.dt.addr_size = addr_size; + conf.unicast.dt.eid_size = eid_size; + conf.unicast.dt.max_ttl = max_ttl; + conf.unicast.dt.routing = routing; + conf.unicast.addr_auth_type = addr_auth_type; + conf.unicast.cong_avoid = cong_avoid; + conf.unicast.dir = dir_config; break; - case IPCP_UDP: - if (ip_addr == 0) - goto fail_usage; - conf.ip_addr = ip_addr; - conf.dns_addr = dns_addr; - conf.port = port; + case IPCP_UDP4: + conf.udp4.ip_addr = ip4_addr; + conf.udp4.dns_addr = dns4_addr; + conf.udp4.port = port4; break; - case IPCP_ETH_LLC: - if (dev == NULL) - goto fail_usage; - conf.dev = dev; + case IPCP_UDP6: + conf.udp6.ip_addr = ip6_addr; + conf.udp6.dns_addr = dns6_addr; + conf.udp6.port = port6; break; case IPCP_ETH_DIX: + conf.eth.ethertype = ethertype; + /* FALLTHRU */ + case IPCP_ETH_LLC: if (dev == NULL) goto fail_usage; - conf.dev = dev; - conf.ethertype = ethertype; + if (strlen(dev) > DEV_NAME_SIZE) { + printf("Device name too long.\n\n"); + goto fail_usage; + } + + strcpy(conf.eth.dev, dev); break; case IPCP_BROADCAST: /* FALLTHRU */ diff --git a/src/tools/irm/irm_ipcp_connect.c b/src/tools/irm/irm_ipcp_connect.c index 965a3a26..68e13bd0 100644 --- a/src/tools/irm/irm_ipcp_connect.c +++ b/src/tools/irm/irm_ipcp_connect.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Connect components of unicast or broadcast IPC processes * @@ -55,25 +55,25 @@ static void usage(void) " name <ipcp name>\n" " dst <name of destination IPCP>\n" " [component [COMPONENT]]\n" - "where COMPONENT = {" DT " " MGMT "}\n\n" + "where COMPONENT in {" DT " " MGMT "}\n\n" "if COMPONENT == " DT "\n" " [qos [QOS]\n" - "where QOS = {raw, best, voice, video, data}\n\n"); + "where QOS in {raw, best, voice, video, data}\n"); } int do_connect_ipcp(int argc, char ** argv) { - char * ipcp = NULL; - char * dst = NULL; - char * comp = "*"; - char * component = NULL; - char * qos = NULL; - struct ipcp_info * ipcps; - ssize_t len = 0; - pid_t pid = -1; - ssize_t i; - qosspec_t qs = qos_raw; + char * ipcp = NULL; + char * dst = NULL; + char * comp = "*"; + char * component = NULL; + char * qos = NULL; + struct ipcp_list_info * ipcps; + ssize_t len = 0; + pid_t pid = -1; + ssize_t i; + qosspec_t qs = qos_raw; while (argc > 0) { if (matches(*argv, "name") == 0) { diff --git a/src/tools/irm/irm_ipcp_create.c b/src/tools/irm/irm_ipcp_create.c index 586cdf85..e2a5c488 100644 --- a/src/tools/irm/irm_ipcp_create.c +++ b/src/tools/irm/irm_ipcp_create.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Create IPC Processes * @@ -44,9 +44,10 @@ #include "irm_ops.h" #include "irm_utils.h" -#define UNICAST "unicast" +#define UNICAST "unicast" #define BROADCAST "broadcast" -#define UDP "udp" +#define UDP4 "udp4" +#define UDP6 "udp6" #define ETH_LLC "eth-llc" #define ETH_DIX "eth-dix" #define LOCAL "local" @@ -56,17 +57,17 @@ static void usage(void) printf("Usage: irm ipcp create\n" " name <ipcp name>\n" " type [TYPE]\n\n" - "where TYPE = {" UNICAST " " BROADCAST " " LOCAL " " - UDP " " ETH_LLC "}\n"); + "where TYPE in {" UNICAST " " BROADCAST " " LOCAL " " + UDP4 " " UDP6 " " ETH_LLC " " ETH_DIX "}\n"); } int do_create_ipcp(int argc, char ** argv) { - char * ipcp_type = NULL; - char * ipcp_name = NULL; - enum ipcp_type type = 0; - pid_t pid; + char * ipcp_type = NULL; + char * ipcp_name = NULL; + enum ipcp_type type = 0; + pid_t pid; while (argc > 0) { if (matches(*argv, "type") == 0) { @@ -92,8 +93,10 @@ int do_create_ipcp(int argc, type = IPCP_UNICAST; else if (strcmp(ipcp_type, BROADCAST) == 0) type = IPCP_BROADCAST; - else if (strcmp(ipcp_type, UDP) == 0) - type = IPCP_UDP; + else if (strcmp(ipcp_type, UDP4) == 0) + type = IPCP_UDP4; + else if (strcmp(ipcp_type, UDP6) == 0) + type = IPCP_UDP6; else if (strcmp(ipcp_type, LOCAL) == 0) type = IPCP_LOCAL; else if (strcmp(ipcp_type, ETH_LLC) == 0) @@ -101,12 +104,13 @@ int do_create_ipcp(int argc, else if (strcmp(ipcp_type, ETH_DIX) == 0) type = IPCP_ETH_DIX; else { + printf("IPCP type \"%s\" is unknown.\n", ipcp_type); usage(); return -1; } pid = irm_create_ipcp(ipcp_name, type); - if (pid <= 0) + if (pid < 0) return -1; return 0; diff --git a/src/tools/irm/irm_ipcp_destroy.c b/src/tools/irm/irm_ipcp_destroy.c index 6f42d09f..1a5e564e 100644 --- a/src/tools/irm/irm_ipcp_destroy.c +++ b/src/tools/irm/irm_ipcp_destroy.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Destroy IPC Processes * @@ -55,10 +55,10 @@ static void usage(void) int do_destroy_ipcp(int argc, char ** argv) { - char * ipcp = NULL; - struct ipcp_info * ipcps; - ssize_t len; - int i; + char * ipcp = NULL; + struct ipcp_list_info * ipcps; + ssize_t len; + int i; while (argc > 0) { if (matches(*argv, "name") == 0) { diff --git a/src/tools/irm/irm_ipcp_disconnect.c b/src/tools/irm/irm_ipcp_disconnect.c index 87685e25..7ce724e1 100644 --- a/src/tools/irm/irm_ipcp_disconnect.c +++ b/src/tools/irm/irm_ipcp_disconnect.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Connect components of unicast or broadcast IPC processes * @@ -54,20 +54,20 @@ static void usage(void) " name <ipcp name>\n" " dst <name of destination IPCP>\n" " [component [COMPONENT]]\n\n" - "where COMPONENT = {" DT " " MGMT "}\n"); + "where COMPONENT in {" DT " " MGMT "}\n"); } int do_disconnect_ipcp(int argc, char ** argv) { - char * ipcp = NULL; - char * dst = NULL; - char * comp = "*"; - char * component = NULL; - struct ipcp_info * ipcps; - ssize_t len = 0; - pid_t pid = -1; - ssize_t i; + char * ipcp = NULL; + char * dst = NULL; + char * comp = "*"; + char * component = NULL; + struct ipcp_list_info * ipcps; + ssize_t len = 0; + pid_t pid = -1; + ssize_t i; while (argc > 0) { if (matches(*argv, "name") == 0) { diff --git a/src/tools/irm/irm_ipcp_enroll.c b/src/tools/irm/irm_ipcp_enroll.c index 2feffc54..86a22a71 100644 --- a/src/tools/irm/irm_ipcp_enroll.c +++ b/src/tools/irm/irm_ipcp_enroll.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Enroll IPC Processes * @@ -57,15 +57,15 @@ static void usage(void) " [dst <destination to enroll with>]\n" " [type [TYPE], default = " UNICAST "]\n" " [autobind]\n" - "where TYPE = {" UNICAST " " BROADCAST "}\n"); + "where TYPE in {" UNICAST " " BROADCAST "}\n"); } static int get_layer_name(const char * ipcp, char * layer_name) { - struct ipcp_info * ipcps; - size_t len; - size_t i; + struct ipcp_list_info * ipcps; + ssize_t len; + ssize_t i; len = irm_list_ipcps(&ipcps); for (i = 0; i < len; i++) @@ -83,17 +83,17 @@ static int get_layer_name(const char * ipcp, int do_enroll_ipcp(int argc, char ** argv) { - char * ipcp = NULL; - char * layer = NULL; - char * dst = NULL; - struct ipcp_info * ipcps; - pid_t pid = -1; - ssize_t len = 0; - int i = 0; - bool autobind = false; - int cargs; - char * ipcp_type = UNICAST; - enum ipcp_type type = IPCP_INVALID; + char * ipcp = NULL; + char * layer = NULL; + char * dst = NULL; + struct ipcp_list_info * ipcps; + pid_t pid = -1; + ssize_t len = 0; + int i = 0; + bool autobind = false; + int cargs; + char * ipcp_type = UNICAST; + enum ipcp_type type = IPCP_INVALID; while (argc > 0) { cargs = 2; @@ -146,40 +146,39 @@ int do_enroll_ipcp(int argc, } for (i = 0; i < len; i++) { + char enr_layer[LAYER_NAME_SIZE]; if (ipcps[i].type != type) continue; - if (wildcard_match(ipcps[i].name, ipcp) == 0) { - char enr_layer[LAYER_NAME_SIZE]; + if (wildcard_match(ipcps[i].name, ipcp) != 0) + continue; - pid = ipcps[i].pid; + pid = ipcps[i].pid; + + if (irm_enroll_ipcp(pid, dst)) { + printf("Failed to enroll IPCP.\n"); + goto fail; + } + + if (get_layer_name(ipcps[i].name, enr_layer)) { + printf("Could not get layer name.\n"); + goto fail; + } - if (irm_enroll_ipcp(pid, dst)) { - printf("Failed to enroll IPCP.\n"); - goto fail; - } - - if (get_layer_name(ipcps[i].name, enr_layer)) { - printf("Could not get layer name.\n"); - goto fail; - } - - if (layer != NULL && strcmp(enr_layer, layer)) { - printf("Enrollment destination does not " - "match requested layer.\n"); - goto fail; - } - - if (autobind && irm_bind_process(pid, ipcp)) { - printf("Failed to bind %d to %s.\n", pid, ipcp); - goto fail; - } - - if (autobind && irm_bind_process(pid, enr_layer)) { - printf("Failed to bind %d to %s.\n", - pid, enr_layer); - goto fail; - } + if (layer != NULL && strcmp(enr_layer, layer)) { + printf("Enrollment destination does not " + "match requested layer.\n"); + goto fail; + } + + if (autobind && irm_bind_process(pid, ipcp)) { + printf("Failed to bind %d to %s.\n", pid, ipcp); + goto fail; + } + + if (autobind && irm_bind_process(pid, enr_layer)) { + printf("Failed to bind %d to %s.\n", pid, enr_layer); + goto fail; } } diff --git a/src/tools/irm/irm_ipcp_list.c b/src/tools/irm/irm_ipcp_list.c index 512e1645..54985eb4 100644 --- a/src/tools/irm/irm_ipcp_list.c +++ b/src/tools/irm/irm_ipcp_list.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * List IPC Processes * @@ -48,7 +48,8 @@ #define UNICAST "unicast" #define BROADCAST "broadcast" -#define UDP "udp" +#define UDP4 "udp4" +#define UDP6 "udp6" #define ETH_LLC "eth-llc" #define ETH_DIX "eth-dix" #define LOCAL "local" @@ -60,7 +61,7 @@ static void usage(void) " [layer <layer_name>]\n\n" " [type [TYPE]]\n\n" "where TYPE = {" UNICAST " " LOCAL " " - UDP " " ETH_LLC " " ETH_DIX "}\n"); + UDP4 " " UDP6 " " ETH_LLC " " ETH_DIX "}\n"); } static char * str_type(enum ipcp_type type) @@ -74,8 +75,10 @@ static char * str_type(enum ipcp_type type) return ETH_LLC; case IPCP_ETH_DIX: return ETH_DIX; - case IPCP_UDP: - return UDP; + case IPCP_UDP4: + return UDP4; + case IPCP_UDP6: + return UDP6; case IPCP_LOCAL: return LOCAL; default: @@ -86,12 +89,12 @@ static char * str_type(enum ipcp_type type) int do_list_ipcp(int argc, char ** argv) { - char * ipcp_type = NULL; - char * ipcp_name = NULL; - enum ipcp_type type = -1; - struct ipcp_info * ipcps; - ssize_t len; - ssize_t i; + char * ipcp_type = NULL; + char * ipcp_name = NULL; + enum ipcp_type type = -1; + struct ipcp_list_info * ipcps; + ssize_t len; + ssize_t i; while (argc > 0) { if (matches(*argv, "type") == 0) { @@ -113,8 +116,10 @@ int do_list_ipcp(int argc, type = IPCP_UNICAST; else if (strcmp(ipcp_type, BROADCAST) == 0) type = IPCP_BROADCAST; - else if (strcmp(ipcp_type, UDP) == 0) - type = IPCP_UDP; + else if (strcmp(ipcp_type, UDP4) == 0) + type = IPCP_UDP4; + else if (strcmp(ipcp_type, UDP6) == 0) + type = IPCP_UDP6; else if (strcmp(ipcp_type, LOCAL) == 0) type = IPCP_LOCAL; else if (strcmp(ipcp_type, ETH_LLC) == 0) diff --git a/src/tools/irm/irm_name.c b/src/tools/irm/irm_name.c index a14db4a0..d60b6c78 100644 --- a/src/tools/irm/irm_name.c +++ b/src/tools/irm/irm_name.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * A tool to instruct the IRM daemon * @@ -44,9 +44,10 @@ static void usage(void) { printf("Usage: irm name [OPERATION]\n\n" - "where OPERATION = {create destroy\n" - " register unregister\n" - " list help}\n"); + "where OPERATION in {create destroy\n" + " register unregister\n" + " list\n" + " help}\n"); } static int do_help(int argc, char **argv) diff --git a/src/tools/irm/irm_name_create.c b/src/tools/irm/irm_name_create.c index 61f20ea8..b132d355 100644 --- a/src/tools/irm/irm_name_create.c +++ b/src/tools/irm/irm_name_create.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Create IPC Processes * @@ -36,31 +36,82 @@ * OF THE POSSIBILITY OF SUCH DAMAGE. */ +#define _POSIX_C_SOURCE 200809L +#define _XOPEN_SOURCE 500 + +#include <ouroboros/errno.h> #include <ouroboros/irm.h> #include <stdio.h> #include <string.h> +#include <stdlib.h> #include "irm_ops.h" #include "irm_utils.h" #define RR "round-robin" #define SPILL "spillover" +#define SENC "<security_dir>/server/<name>/enc.conf" +#define SCRT "<security_dir>/server/<name>/crt.pem" +#define SKEY "<security_dir>/server/<name>/key.pem" +#define CENC "<security_dir>/client/<name>/enc.conf" +#define CCRT "<security_dir>/client/<name>/crt.pem" +#define CKEY "<security_dir>/client/<name>/key.pem" static void usage(void) { printf("Usage: irm name create\n" - " <name>\n" - " lb [LB_POLICY], default: %s\n\n" - "where LB_POLICY = {" RR " " SPILL "}\n", RR); + " <name>. max %d chars.\n" + " [lb LB_POLICY], default: %s\n" + " [sencpath <path>, default: " SENC "]\n" + " [scrtpath <path>, default: " SCRT "]\n" + " [skeypath <path>, default: " SKEY "]\n" + " [cencpath <path>, default: " CENC "]\n" + " [ccrtpath <path>, default: " CCRT "]\n" + " [ckeypath <path>, default: " CKEY "]\n" + "\n" + "where LB_POLICY in {" RR " " SPILL "}\n", + NAME_SIZE, RR); +} + +static int cp_chk_path(char * buf, + const char * path) +{ + char * rp = realpath(path, NULL); + if (rp == NULL) { + printf("Failed to check path %s: %s\n.", + path, strerror(errno)); + goto fail_rp; + } + + if (strlen(rp) > NAME_PATH_SIZE) { + printf("File path too long: %s.\n", rp); + goto fail_len; + } + + strcpy(buf, rp); + free(rp); + + return 0; + + fail_len: + free(rp); + fail_rp: + return -1; } int do_create_name(int argc, char ** argv) { - char * name = NULL; - char * lb_pol = RR; - enum pol_balance pol_lb = LB_RR; + struct name_info info = {}; + char * name = NULL; + char * sencpath = NULL; + char * scrtpath = NULL; + char * skeypath = NULL; + char * cencpath = NULL; + char * ccrtpath = NULL; + char * ckeypath = NULL; + char * lb_pol = RR; name = *(argv++); --argc; @@ -68,6 +119,18 @@ int do_create_name(int argc, while (argc > 0) { if (matches(*argv, "lb") == 0) { lb_pol = *(argv + 1); + } else if (matches(*argv, "sencpath") == 0) { + sencpath = *(argv + 1); + } else if (matches(*argv, "scrtpath") == 0) { + scrtpath = *(argv + 1); + } else if (matches(*argv, "skeypath") == 0) { + skeypath = *(argv + 1); + } else if (matches(*argv, "cencpath") == 0) { + cencpath = *(argv + 1); + } else if (matches(*argv, "ccrtpath") == 0) { + ccrtpath = *(argv + 1); + } else if (matches(*argv, "ckeypath") == 0) { + ckeypath = *(argv + 1); } else { printf("\"%s\" is unknown, try \"irm " "name create\".\n", *argv); @@ -78,19 +141,45 @@ int do_create_name(int argc, argv += 2; } - if (name == NULL) { - usage(); - return -1; + if (name == NULL) + goto fail; + + if (strlen(name) > NAME_SIZE) { + printf("Name too long.\n"); + goto fail; } + strcpy(info.name, name); + + if (sencpath != NULL && cp_chk_path(info.s.enc, sencpath) < 0) + goto fail; + + if (scrtpath != NULL && cp_chk_path(info.s.crt, scrtpath) < 0) + goto fail; + + if (skeypath != NULL && cp_chk_path(info.s.key, skeypath) < 0) + goto fail; + + if (cencpath != NULL && cp_chk_path(info.c.enc, cencpath) < 0) + goto fail; + + if (ccrtpath != NULL && cp_chk_path(info.c.crt, ccrtpath) < 0) + goto fail; + + if (ckeypath != NULL && cp_chk_path(info.c.key, ckeypath) < 0) + goto fail; + if (strcmp(lb_pol, RR) == 0) - pol_lb = LB_RR; + info.pol_lb = LB_RR; else if (strcmp(lb_pol, SPILL) == 0) - pol_lb = LB_SPILL; + info.pol_lb = LB_SPILL; else { usage(); return -1; } - return irm_create_name(name, pol_lb); + return irm_create_name(&info); + fail: + usage(); + return -1; } diff --git a/src/tools/irm/irm_name_destroy.c b/src/tools/irm/irm_name_destroy.c index f2d833e0..d4bd6c82 100644 --- a/src/tools/irm/irm_name_destroy.c +++ b/src/tools/irm/irm_name_destroy.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Create IPC Processes * diff --git a/src/tools/irm/irm_name_list.c b/src/tools/irm/irm_name_list.c index 0398b1cb..a807008c 100644 --- a/src/tools/irm/irm_name_list.c +++ b/src/tools/irm/irm_name_list.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * List names * diff --git a/src/tools/irm/irm_name_reg.c b/src/tools/irm/irm_name_reg.c index 04ca0b47..7689119a 100644 --- a/src/tools/irm/irm_name_reg.c +++ b/src/tools/irm/irm_name_reg.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Register names with IPCPs * @@ -64,17 +64,17 @@ static void usage(void) int do_reg_name(int argc, char ** argv) { - char * name = NULL; - char * layers[MAX_LAYERS]; - size_t layers_len = 0; - char * ipcp[MAX_IPCPS]; - size_t ipcp_len = 0; - struct ipcp_info * ipcps; - ssize_t ipcps_len; - struct name_info * names; - ssize_t names_len; - bool name_create = true; - ssize_t i; + char * name = NULL; + char * layers[MAX_LAYERS]; + size_t layers_len = 0; + char * ipcp[MAX_IPCPS]; + size_t ipcp_len = 0; + struct ipcp_list_info * ipcps; + ssize_t ipcps_len; + struct name_info * names; + ssize_t names_len; + bool name_create = true; + ssize_t i; name = *(argv++); --argc; @@ -107,14 +107,23 @@ int do_reg_name(int argc, return -1; } + if (strlen(name) > NAME_SIZE) { + printf("Name too long.\n"); + usage(); + return -1; + } + ipcps_len = irm_list_ipcps(&ipcps); - if (ipcps_len < 0) - return ipcps_len; + if (ipcps_len <= 0) { + printf("Failed to list IPCPs.\n"); + return -1; + } names_len = irm_list_names(&names); if (names_len < 0) { + printf("Failed to list names.\n"); free(ipcps); - return names_len; + return -1; } for (i = 0; i < names_len; ++i) { @@ -124,11 +133,19 @@ int do_reg_name(int argc, } } - if (name_create && irm_create_name(name, LB_SPILL)) { - printf("Error creating name."); - free(ipcps); - free(name); - return -1; + if (name_create) { + struct name_info info = { + .pol_lb = LB_SPILL + }; + + strcpy(info.name, name); + + if (irm_create_name(&info) < 0) { + printf("Error creating name."); + free(ipcps); + free(names); + return -1; + } } for (i = 0; i < ipcps_len; ++i) { @@ -136,6 +153,8 @@ int do_reg_name(int argc, for (j = 0; j < layers_len; j++) { if (wildcard_match(layers[j], ipcps[i].layer) == 0) { if (irm_reg_name(name, ipcps[i].pid)) { + printf("Failed to register with %s", + ipcps[i].layer); free(ipcps); free(names); return -1; @@ -145,6 +164,8 @@ int do_reg_name(int argc, for (j = 0; j < ipcp_len; j++) { if (wildcard_match(ipcp[j], ipcps[i].name) == 0) { if (irm_reg_name(name, ipcps[i].pid)) { + printf("Failed to register with %s", + ipcps[i].name); free(ipcps); free(names); return -1; diff --git a/src/tools/irm/irm_name_unreg.c b/src/tools/irm/irm_name_unreg.c index 39d4b5f3..6e579f04 100644 --- a/src/tools/irm/irm_name_unreg.c +++ b/src/tools/irm/irm_name_unreg.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Unregister names from IPCPs * @@ -63,14 +63,14 @@ static void usage(void) int do_unreg_name(int argc, char ** argv) { - char * name = NULL; - char * layers[MAX_LAYERS]; - size_t layers_len = 0; - char * ipcp[MAX_IPCPS]; - size_t ipcp_len = 0; - struct ipcp_info * ipcps; - ssize_t len; - size_t i; + char * name = NULL; + char * layers[MAX_LAYERS]; + size_t layers_len = 0; + char * ipcp[MAX_IPCPS]; + size_t ipcp_len = 0; + struct ipcp_list_info * ipcps; + ssize_t len; + size_t i; name = *(argv++); --argc; diff --git a/src/tools/irm/irm_ops.h b/src/tools/irm/irm_ops.h index 7ce773d0..e04ffc02 100644 --- a/src/tools/irm/irm_ops.h +++ b/src/tools/irm/irm_ops.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Functions of the IRM tool that are one level deep * diff --git a/src/tools/irm/irm_unbind.c b/src/tools/irm/irm_unbind.c index ff9f0d38..d6594d01 100644 --- a/src/tools/irm/irm_unbind.c +++ b/src/tools/irm/irm_unbind.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Unbind names in the processing system * @@ -47,7 +47,7 @@ static void usage(void) { printf("Usage: irm unbind [OPERATION]\n" "\n" - "where OPERATION = {program process ipcp help}\n"); + "where OPERATION in {program process ipcp help}\n"); } static int do_help(int argc, diff --git a/src/tools/irm/irm_unbind_ipcp.c b/src/tools/irm/irm_unbind_ipcp.c index 705af1ab..53a2d16c 100644 --- a/src/tools/irm/irm_unbind_ipcp.c +++ b/src/tools/irm/irm_unbind_ipcp.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Unbind name from IPCP Instance * @@ -57,11 +57,11 @@ static void usage(void) int do_unbind_ipcp(int argc, char ** argv) { - char * ipcp = NULL; - char * name = NULL; - struct ipcp_info * ipcps; - ssize_t len; - ssize_t i; + char * ipcp = NULL; + char * name = NULL; + struct ipcp_list_info * ipcps; + ssize_t len; + ssize_t i; while (argc > 0) { if (matches(*argv, "name") == 0) { diff --git a/src/tools/irm/irm_unbind_process.c b/src/tools/irm/irm_unbind_process.c index 733410a1..264ed538 100644 --- a/src/tools/irm/irm_unbind_process.c +++ b/src/tools/irm/irm_unbind_process.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Unbind process names * diff --git a/src/tools/irm/irm_unbind_program.c b/src/tools/irm/irm_unbind_program.c index 41395c85..0c751e80 100644 --- a/src/tools/irm/irm_unbind_program.c +++ b/src/tools/irm/irm_unbind_program.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Unbind programs * diff --git a/src/tools/irm/irm_utils.c b/src/tools/irm/irm_utils.c index 4a15dfce..9694d647 100644 --- a/src/tools/irm/irm_utils.c +++ b/src/tools/irm/irm_utils.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Handy helper functions for the IRM tool * diff --git a/src/tools/irm/irm_utils.h b/src/tools/irm/irm_utils.h index f32b519a..27a0b941 100644 --- a/src/tools/irm/irm_utils.h +++ b/src/tools/irm/irm_utils.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Handy helper functions for the IRM tool * diff --git a/src/tools/obc/CMakeLists.txt b/src/tools/obc/CMakeLists.txt deleted file mode 100644 index db5e999b..00000000 --- a/src/tools/obc/CMakeLists.txt +++ /dev/null @@ -1,16 +0,0 @@ -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) - -include_directories(${CMAKE_SOURCE_DIR}/include) -include_directories(${CMAKE_BINARY_DIR}/include) - -set(SOURCE_FILES - # Add source files here - obc.c - ) - -add_executable(obc ${SOURCE_FILES}) - -target_link_libraries(obc LINK_PUBLIC ouroboros-dev) - -install(TARGETS obc RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/src/tools/obc/obc.c b/src/tools/obc/obc.c index 63e4eca1..778eb8a8 100644 --- a/src/tools/obc/obc.c +++ b/src/tools/obc/obc.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * A simple broadcast application * @@ -63,7 +63,7 @@ static int reader_main(const char * dst) printf("Starting a reader.\n"); - fd = flow_join(dst, NULL, NULL); + fd = flow_join(dst, NULL); if (fd < 0) { printf("Failed to join broadcast.\n"); return -1; @@ -91,7 +91,7 @@ static int writer_main(const char * dst, int fd = 0; size_t len = strlen(message) + 1; - fd = flow_join(dst, NULL, NULL); + fd = flow_join(dst, NULL); if (fd < 0) { printf("Failed to join broadcast.\n"); return -1; diff --git a/src/tools/ocbr/CMakeLists.txt b/src/tools/ocbr/CMakeLists.txt deleted file mode 100644 index f7ba66cd..00000000 --- a/src/tools/ocbr/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) - -include_directories(${CMAKE_SOURCE_DIR}/include) -include_directories(${CMAKE_BINARY_DIR}/include) - -get_filename_component(CURRENT_SOURCE_PARENT_DIR - ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) - -include_directories(${CURRENT_SOURCE_PARENT_DIR}) - -set(SOURCE_FILES - # Add source files here - ocbr.c - ) - -add_executable(ocbr ${SOURCE_FILES}) - -target_link_libraries(ocbr LINK_PUBLIC ouroboros-dev) - -install(TARGETS ocbr RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/src/tools/ocbr/ocbr.c b/src/tools/ocbr/ocbr.c index 2643b588..775bcaac 100644 --- a/src/tools/ocbr/ocbr.c +++ b/src/tools/ocbr/ocbr.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * CBR traffic generator * @@ -155,17 +155,22 @@ int main(int argc, char ** argv) if (s_apn == NULL) { printf("No server specified.\n"); usage(); - return 0; + return 1; } if (size > BUF_SIZE) { printf("Maximum size: %ld.\n", BUF_SIZE); - return 0; + return 1; } if (size < 0) { printf("Size overflow.\n"); - return 0; + return 1; + } + + if (rate <= 0) { + printf("Invalid rate.\n"); + return 1; } ret = client_main(s_apn, duration, size, rate, flood, sleep); diff --git a/src/tools/ocbr/ocbr_client.c b/src/tools/ocbr/ocbr_client.c index f2a6e20f..eada6e60 100644 --- a/src/tools/ocbr/ocbr_client.c +++ b/src/tools/ocbr/ocbr_client.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * A simple CBR generator * @@ -129,7 +129,7 @@ int client_main(char * server, ++seqnr; - if (ts_diff_us(&start, &end) / MILLION >= duration) + if (ts_diff_us(&end, &start) / MILLION >= duration) stop = true; } } else { /* flood */ @@ -142,7 +142,7 @@ int client_main(char * server, ++seqnr; - if (ts_diff_us(&start, &end) / MILLION + if (ts_diff_us(&end, &start) / MILLION >= (long) duration) stop = true; } @@ -151,7 +151,7 @@ int client_main(char * server, clock_gettime(CLOCK_REALTIME, &end); - ms = ts_diff_ms(&start, &end); + ms = ts_diff_ms(&end, &start); printf("sent statistics: " "%9ld packets, %12ld bytes in %9d ms, %4.4f Mb/s\n", diff --git a/src/tools/ocbr/ocbr_server.c b/src/tools/ocbr/ocbr_server.c index 05659dc4..34c4fa94 100644 --- a/src/tools/ocbr/ocbr_server.c +++ b/src/tools/ocbr/ocbr_server.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * A simple CBR generator * @@ -114,14 +114,14 @@ static void handle_flow(int fd) bytes_read += count; } - if (ts_diff_us(&alive, &now) + if (ts_diff_us(&now, &alive) > server_settings.timeout * MILLION) { printf("Test on flow %d timed out\n", fd); stop = true; } - if (stop || ts_diff_ms(&now, &iv_end) < 0) { - long us = ts_diff_us(&iv_start, &now); + if (stop || ts_diff_ms(&now, &iv_end) > 0) { + long us = ts_diff_us(&now, &iv_start); printf("Flow %4d: %9ld packets (%12ld bytes) in %9ld ms" " => %9.4f pps, %9.4f Mbps\n", fd, diff --git a/src/tools/oecho/CMakeLists.txt b/src/tools/oecho/CMakeLists.txt deleted file mode 100644 index 50a66138..00000000 --- a/src/tools/oecho/CMakeLists.txt +++ /dev/null @@ -1,16 +0,0 @@ -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) - -include_directories(${CMAKE_SOURCE_DIR}/include) -include_directories(${CMAKE_BINARY_DIR}/include) - -set(SOURCE_FILES - # Add source files here - oecho.c - ) - -add_executable(oecho ${SOURCE_FILES}) - -target_link_libraries(oecho LINK_PUBLIC ouroboros-dev) - -install(TARGETS oecho RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/src/tools/oecho/oecho.c b/src/tools/oecho/oecho.c index 62f3f636..d5d03027 100644 --- a/src/tools/oecho/oecho.c +++ b/src/tools/oecho/oecho.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * A simple echo application * diff --git a/src/tools/operf/CMakeLists.txt b/src/tools/operf/CMakeLists.txt deleted file mode 100644 index b6faf04e..00000000 --- a/src/tools/operf/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) - -include_directories(${CMAKE_SOURCE_DIR}/include) -include_directories(${CMAKE_BINARY_DIR}/include) - -get_filename_component(CURRENT_SOURCE_PARENT_DIR - ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) - -include_directories(${CURRENT_SOURCE_PARENT_DIR}) - -find_library(LIBM_LIBRARIES m) -if(NOT LIBM_LIBRARIES) - message(FATAL_ERROR "libm not found") -endif() - -set(SOURCE_FILES - # Add source files here - operf.c - ) - -add_executable(operf ${SOURCE_FILES}) - -target_link_libraries(operf LINK_PUBLIC ${LIBM_LIBRARIES} ouroboros-dev) - -install(TARGETS operf RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/src/tools/operf/operf.c b/src/tools/operf/operf.c index 2afeb71f..3263d3bf 100644 --- a/src/tools/operf/operf.c +++ b/src/tools/operf/operf.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Ouroboros perf application * @@ -54,7 +54,6 @@ #include <stdlib.h> #include <sys/time.h> #include <arpa/inet.h> -#include <math.h> #include <errno.h> #include <float.h> diff --git a/src/tools/operf/operf_client.c b/src/tools/operf/operf_client.c index 58d9aaff..7060ce5b 100644 --- a/src/tools/operf/operf_client.c +++ b/src/tools/operf/operf_client.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Ouroboros ping application * @@ -133,7 +133,7 @@ void * writer(void * o) clock_gettime(CLOCK_REALTIME, &start); clock_gettime(CLOCK_REALTIME, &now); - while (!stop && ts_diff_ms(&start, &now) < client.duration) { + while (!stop && ts_diff_ms(&now, &start) > client.duration) { if (!client.flood) { clock_gettime(CLOCK_REALTIME, &now); ts_add(&now, &intv, &end); @@ -230,10 +230,10 @@ int client_main(void) printf("%ld received, ", client.rcvd); printf("%ld%% packet loss, ", client.sent == 0 ? 0 : 100 - ((100 * client.rcvd) / client.sent)); - printf("time: %.3f ms, ", ts_diff_us(&tic, &toc) / 1000.0); + printf("time: %.3f ms, ", ts_diff_us(&toc, &tic) / 1000.0); printf("bandwidth: %.3lf Mb/s.\n", (client.rcvd * client.size * 8) - / (double) ts_diff_us(&tic, &toc)); + / (double) ts_diff_us(&toc, &tic)); } flow_dealloc(fd); diff --git a/src/tools/operf/operf_server.c b/src/tools/operf/operf_server.c index aa900919..a611f79c 100644 --- a/src/tools/operf/operf_server.c +++ b/src/tools/operf/operf_server.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Ouroboros perf application * @@ -66,7 +66,7 @@ void * cleaner_thread(void * o) pthread_mutex_lock(&server.lock); for (i = 0; i < OPERF_MAX_FLOWS; ++i) if (fset_has(server.flows, i) && - ts_diff_ms(&server.times[i], &now) + ts_diff_ms(&now, &server.times[i]) > server.timeout) { printf("Flow %d timed out.\n", i); fset_del(server.flows, i); diff --git a/src/tools/oping/CMakeLists.txt b/src/tools/oping/CMakeLists.txt deleted file mode 100644 index 31a4f961..00000000 --- a/src/tools/oping/CMakeLists.txt +++ /dev/null @@ -1,28 +0,0 @@ -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) - -include_directories(${CMAKE_SOURCE_DIR}/include) -include_directories(${CMAKE_BINARY_DIR}/include) - -get_filename_component(CURRENT_SOURCE_PARENT_DIR - ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) - -include_directories(${CURRENT_SOURCE_PARENT_DIR}) - -find_library(LIBM_LIBRARIES m) -if(NOT LIBM_LIBRARIES) - message(FATAL_ERROR "libm not found") -endif() - -mark_as_advanced(LIBM_LIBRARIES) - -set(SOURCE_FILES - # Add source files here - oping.c - ) - -add_executable(oping ${SOURCE_FILES}) - -target_link_libraries(oping LINK_PUBLIC ${LIBM_LIBRARIES} ouroboros-dev) - -install(TARGETS oping RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/src/tools/oping/oping.c b/src/tools/oping/oping.c index 093f3e84..47b86c68 100644 --- a/src/tools/oping/oping.c +++ b/src/tools/oping/oping.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Ouroboros ping application * @@ -60,19 +60,39 @@ #include <errno.h> #include <float.h> -#define OPING_BUF_SIZE 1500 - -#define ECHO_REQUEST 0 -#define ECHO_REPLY 1 - +#define OPING_BUF_SIZE 1500 +#define ECHO_REQUEST 0 +#define ECHO_REPLY 1 #define OPING_MAX_FLOWS 256 -struct c { +#define USAGE_STRING \ +"Usage: oping [OPTION]...\n" \ +"\n" \ +"Checks liveness between a client and a server\n" \ +"and reports the Round Trip Time (RTT)\n" \ +"\n" \ +" -l, --listen Run in server mode\n" \ +"\n" \ +" -c, --count Number of packets\n" \ +" -d, --duration Duration of the test (default 1s)\n" \ +" -f, --flood Send back-to-back without waiting\n" \ +" -i, --interval Interval (default 1000ms)\n" \ +" -n, --server-name Name of the oping server\n" \ +" -q, --qos QoS (raw, best, video, voice, data)\n" \ +" -s, --size Payload size (B, default 64)\n" \ +" -Q, --quiet Only print final statistics\n" \ +" -D, --timeofday Print time of day before each line\n" \ +" --poll Server uses polling (lower latency)\n" \ +"\n" \ +" --help Display this help text and exit\n" \ + +struct { char * s_apn; int interval; uint32_t count; int size; bool timestamp; + bool flood; qosspec_t qs; /* stats */ @@ -90,12 +110,15 @@ struct c { pthread_t writer_pt; } client; -struct s { +struct { struct timespec times[OPING_MAX_FLOWS]; fset_t * flows; fqueue_t * fq; pthread_mutex_t lock; + bool quiet; + bool poll; + pthread_t cleaner_pt; pthread_t accept_pt; pthread_t server_pt; @@ -114,22 +137,7 @@ struct oping_msg { static void usage(void) { - printf("Usage: oping [OPTION]...\n" - "Checks liveness between a client and a server\n" - "and reports the Round Trip Time (RTT)\n\n" - " -l, --listen Run in server mode\n" - "\n" - " -c, --count Number of packets\n" - " -d, --duration Duration of the test (default 1s)\n" - " -i, --interval Interval (default 1000ms)\n" - " -n, --server-name Name of the oping server\n" - " -q, --qos QoS (raw, best, video, voice, data)" - "\n" - " -s, --size Payload size (B, default 64)\n" - " -Q, --quiet Only print final statistics\n" - " -D, --timeofday Print time of day before each line" - "\n" - " --help Display this help text and exit\n"); + printf(USAGE_STRING); } /* Times are in ms. */ @@ -168,46 +176,60 @@ int main(int argc, client.size = 64; client.count = INT_MAX; client.timestamp = false; + client.flood = false; client.qs = qos_raw; client.quiet = false; + server.quiet = false; + server.poll = false; while (argc > 0) { - if (strcmp(*argv, "-i") == 0 || - strcmp(*argv, "--interval") == 0) { + if ((strcmp(*argv, "-i") == 0 || + strcmp(*argv, "--interval") == 0) && + argc > 1) { client.interval = strtol(*(++argv), &rem, 10); client.interval *= time_mul(rem); --argc; - } else if (strcmp(*argv, "-n") == 0 || - strcmp(*argv, "--server-name") == 0) { + } else if ((strcmp(*argv, "-n") == 0 || + strcmp(*argv, "--server-name") == 0) && + argc > 1) { client.s_apn = *(++argv); --argc; - } else if (strcmp(*argv, "-c") == 0 || - strcmp(*argv, "--count") == 0) { + } else if ((strcmp(*argv, "-c") == 0 || + strcmp(*argv, "--count") == 0) && + argc > 1) { client.count = strtol(*(++argv), &rem, 10); --argc; - } else if (strcmp(*argv, "-d") == 0 || - strcmp(*argv, "--duration") == 0) { + } else if ((strcmp(*argv, "-d") == 0 || + strcmp(*argv, "--duration") == 0) && + argc > 1) { duration = strtol(*(++argv), &rem, 10); duration *= time_mul(rem); --argc; - } else if (strcmp(*argv, "-s") == 0 || - strcmp(*argv, "--size") == 0) { + } else if ((strcmp(*argv, "-s") == 0 || + strcmp(*argv, "--size") == 0) && + argc > 1) { client.size = strtol(*(++argv), &rem, 10); --argc; - } else if (strcmp(*argv, "-q") == 0 || - strcmp(*argv, "--qos") == 0) { + } else if ((strcmp(*argv, "-q") == 0 || + strcmp(*argv, "--qos") == 0) && + argc > 1) { qos = *(++argv); --argc; } else if (strcmp(*argv, "-l") == 0 || strcmp(*argv, "--listen") == 0) { serv = true; + } else if (strcmp(*argv, "-f") == 0 || + strcmp(*argv, "--flood") == 0) { + client.flood = true; } else if (strcmp(*argv, "-D") == 0 || strcmp(*argv, "--timeofday") == 0) { client.timestamp = true; } else if (strcmp(*argv, "-Q") == 0 || strcmp(*argv, "--quiet") == 0) { client.quiet = true; - + server.quiet = true; + } else if (strcmp(*argv, "--poll") == 0) { + server.poll = true; } else { goto fail; } @@ -233,8 +255,6 @@ int main(int argc, client.qs = qos_voice; else if (strcmp(qos, "data") == 0) client.qs = qos_data; - else if (strcmp(qos, "raw_crypt") == 0) - client.qs = qos_raw_crypt; else printf("Unknown QoS cube, defaulting to raw.\n"); } diff --git a/src/tools/oping/oping_client.c b/src/tools/oping/oping_client.c index 0ab5a4af..1513eac8 100644 --- a/src/tools/oping/oping_client.c +++ b/src/tools/oping/oping_client.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Ouroboros ping application * @@ -53,6 +53,20 @@ void shutdown_client(int signo, siginfo_t * info, void * c) } } +static void update_rtt_stats(double ms) +{ + double d; + + if (ms < client.rtt_min) + client.rtt_min = ms; + if (ms > client.rtt_max) + client.rtt_max = ms; + + d = (ms - client.rtt_avg); + client.rtt_avg += d / client.rcvd; + client.rtt_m2 += d * (ms - client.rtt_avg); +} + void * reader(void * o) { struct timespec timeout = {client.interval / 1000 + 2, 0}; @@ -64,7 +78,6 @@ void * reader(void * o) int fd = *((int *) o); int msg_len = 0; double ms = 0; - double d = 0; uint32_t exp_id = 0; fccntl(fd, FLOWSRCVTIMEO, &timeout); @@ -100,7 +113,7 @@ void * reader(void * o) sent.tv_sec = msg->tv_sec; sent.tv_nsec = msg->tv_nsec; - ms = ts_diff_us(&sent, &now) / 1000.0; + ms = ts_diff_us(&now, &sent) / 1000.0; if (id < exp_id) ++client.ooo; @@ -122,14 +135,7 @@ void * reader(void * o) id < exp_id ? " [out-of-order]" : ""); } - if (ms < client.rtt_min) - client.rtt_min = ms; - if (ms > client.rtt_max) - client.rtt_max = ms; - - d = (ms - client.rtt_avg); - client.rtt_avg += d / client.rcvd; - client.rtt_m2 += d * (ms - client.rtt_avg); + update_rtt_stats(ms); if (id >= exp_id) exp_id = id + 1; @@ -204,13 +210,103 @@ static void client_fini(void) return; } +static void print_stats(struct timespec * tic, + struct timespec * toc) +{ + printf("\n"); + printf("--- %s ping statistics ---\n", client.s_apn); + printf("%d packets transmitted, ", client.sent); + printf("%d received, ", client.rcvd); + printf("%zd out-of-order, ", client.ooo); + printf("%.0lf%% packet loss, ", client.sent == 0 ? 0 : + ceil(100 - (100 * (client.rcvd / (float) client.sent)))); + printf("time: %.3f ms\n", ts_diff_us(toc, tic) / 1000.0); + + if (client.rcvd > 0) { + printf("rtt min/avg/max/mdev = %.3f/%.3f/%.3f/", + client.rtt_min, + client.rtt_avg, + client.rtt_max); + if (client.rcvd > 1) + printf("%.3f ms\n", + sqrt(client.rtt_m2 / (client.rcvd - 1))); + else + printf("NaN ms\n"); + } +} + +static int flood_ping(int fd) +{ + char buf[OPING_BUF_SIZE]; + struct oping_msg * msg = (struct oping_msg *) buf; + struct timespec sent; + struct timespec rcvd; + double ms; + + memset(buf, 0, client.size); + + if (!client.quiet) + printf("Pinging %s with %d bytes of data (%u packets):\n\n", + client.s_apn, client.size, client.count); + + while (!stop && client.sent < client.count) { + clock_gettime(CLOCK_MONOTONIC, &sent); + + msg->type = htonl(ECHO_REQUEST); + msg->id = htonl(client.sent); + msg->tv_sec = sent.tv_sec; + msg->tv_nsec = sent.tv_nsec; + + if (flow_write(fd, buf, client.size) < 0) { + printf("Failed to send packet.\n"); + break; + } + + ++client.sent; + + if (flow_read(fd, buf, OPING_BUF_SIZE) < 0) { + printf("Failed to read packet.\n"); + break; + } + + clock_gettime(CLOCK_MONOTONIC, &rcvd); + + if (ntohl(msg->type) != ECHO_REPLY) + continue; + + ++client.rcvd; + + sent.tv_sec = msg->tv_sec; + sent.tv_nsec = msg->tv_nsec; + ms = ts_diff_us(&rcvd, &sent) / 1000.0; + + update_rtt_stats(ms); + + if (!client.quiet) + printf("%d bytes from %s: seq=%d time=%.3f ms\n", + client.size, client.s_apn, + ntohl(msg->id), ms); + } + + return 0; +} + +static int threaded_ping(int fd) +{ + pthread_create(&client.reader_pt, NULL, reader, &fd); + pthread_create(&client.writer_pt, NULL, writer, &fd); + + pthread_join(client.writer_pt, NULL); + pthread_join(client.reader_pt, NULL); + + return 0; +} + static int client_main(void) { struct sigaction sig_act; - struct timespec tic; struct timespec toc; - int fd; memset(&sig_act, 0, sizeof sig_act); @@ -232,7 +328,7 @@ static int client_main(void) fd = flow_alloc(client.s_apn, &client.qs, NULL); if (fd < 0) { - printf("Failed to allocate flow.\n"); + printf("Failed to allocate flow: %d.\n", fd); client_fini(); return -1; } @@ -241,37 +337,16 @@ static int client_main(void) clock_gettime(CLOCK_REALTIME, &tic); - pthread_create(&client.reader_pt, NULL, reader, &fd); - pthread_create(&client.writer_pt, NULL, writer, &fd); - - pthread_join(client.writer_pt, NULL); - pthread_join(client.reader_pt, NULL); + if (client.flood) + flood_ping(fd); + else + threaded_ping(fd); clock_gettime(CLOCK_REALTIME, &toc); - printf("\n"); - printf("--- %s ping statistics ---\n", client.s_apn); - printf("%d packets transmitted, ", client.sent); - printf("%d received, ", client.rcvd); - printf("%zd out-of-order, ", client.ooo); - printf("%.0lf%% packet loss, ", client.sent == 0 ? 0 : - ceil(100 - (100 * (client.rcvd / (float) client.sent)))); - printf("time: %.3f ms\n", ts_diff_us(&tic, &toc) / 1000.0); - - if (client.rcvd > 0) { - printf("rtt min/avg/max/mdev = %.3f/%.3f/%.3f/", - client.rtt_min, - client.rtt_avg, - client.rtt_max); - if (client.rcvd > 1) - printf("%.3f ms\n", - sqrt(client.rtt_m2 / (client.rcvd - 1))); - else - printf("NaN ms\n"); - } + print_stats(&tic, &toc); flow_dealloc(fd); - client_fini(); return 0; diff --git a/src/tools/oping/oping_server.c b/src/tools/oping/oping_server.c index ec9e85ab..13ab8f47 100644 --- a/src/tools/oping/oping_server.c +++ b/src/tools/oping/oping_server.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Ouroboros ping application * @@ -36,6 +36,8 @@ * OF THE POSSIBILITY OF SUCH DAMAGE. */ + #include <ouroboros/hash.h> + void shutdown_server(int signo, siginfo_t * info, void * c) { (void) info; @@ -67,7 +69,7 @@ void * cleaner_thread(void * o) time_t diff; pthread_mutex_lock(&server.lock); - diff = ts_diff_ms(&server.times[i], &now); + diff = ts_diff_ms(&now, &server.times[i]); pthread_mutex_unlock(&server.lock); if (diff > deadline_ms) { @@ -87,12 +89,15 @@ void * server_thread(void *o) struct oping_msg * msg = (struct oping_msg *) buf; struct timespec now = {0, 0}; struct timespec timeout = {0, 100 * MILLION}; + struct timespec poll_timeout = {0, 0}; int fd; (void) o; while (true) { - if (fevent(server.flows, server.fq, &timeout) == -ETIMEDOUT) + if (fevent(server.flows, server.fq, + server.poll ? &poll_timeout : &timeout) + == -ETIMEDOUT) continue; while ((fd = fqueue_next(server.fq)) >= 0) { @@ -100,13 +105,15 @@ void * server_thread(void *o) if (msg_len < 0) continue; + if (!server.quiet) + printf("Received %d bytes on fd %d.\n", + msg_len, fd); + if (ntohl(msg->type) != ECHO_REQUEST) { printf("Invalid message on fd %d.\n", fd); continue; } - printf("Received %d bytes on fd %d.\n", msg_len, fd); - clock_gettime(CLOCK_REALTIME, &now); pthread_mutex_lock(&server.lock); @@ -136,8 +143,8 @@ void * accept_thread(void * o) while (true) { fd = flow_accept(&qs, NULL); if (fd < 0) { - printf("Failed to accept flow.\n"); - break; + printf("Failed to accept flow: %d \n", fd); + continue; } printf("New flow %d.\n", fd); diff --git a/src/tools/ovpn/CMakeLists.txt b/src/tools/ovpn/CMakeLists.txt deleted file mode 100644 index f3a2cac8..00000000 --- a/src/tools/ovpn/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) -include_directories(${CMAKE_CURRENT_BINARY_DIR}) - -include_directories(${CMAKE_SOURCE_DIR}/include) -include_directories(${CMAKE_BINARY_DIR}/include) - -get_filename_component(CURRENT_SOURCE_PARENT_DIR - ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) - -include_directories(${CURRENT_SOURCE_PARENT_DIR}) - -set(SOURCE_FILES - # Add source files here - ovpn.c - ) - -add_executable(ovpn ${SOURCE_FILES}) - -target_link_libraries(ovpn LINK_PUBLIC ouroboros-dev) - -install(TARGETS ovpn RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/src/tools/ovpn/ovpn.c b/src/tools/ovpn/ovpn.c index dddbdde0..b25e3ea2 100644 --- a/src/tools/ovpn/ovpn.c +++ b/src/tools/ovpn/ovpn.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Ouroboros VPN * @@ -68,7 +68,6 @@ static void usage(void) "server to connect to\n" " -i, --ip IP address to give to TUN device\n" " -m, --mask Subnet mask to give to TUN device\n" - " -C, --crypt AES encryption (default: off)\n" "\n" " --help Display this help text and exit\n"); } @@ -194,7 +193,6 @@ int main(int argc, {{"ip", required_argument, NULL, 'i'}, {"mask", required_argument, NULL, 'm'}, {"name", required_argument, NULL, 'n'}, - {"crypt", no_argument, NULL, 'C'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0} }; @@ -231,9 +229,6 @@ int main(int argc, case 'n': name = optarg; break; - case 'C': - qs = qos_raw_crypt; - break; case 'h': usage(); exit(EXIT_SUCCESS); diff --git a/src/tools/time_utils.h b/src/tools/time_utils.h index 17f03d61..a4117f44 100644 --- a/src/tools/time_utils.h +++ b/src/tools/time_utils.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2021 + * Ouroboros - Copyright (C) 2016 - 2024 * * Time utilities * @@ -53,17 +53,17 @@ #include <sys/time.h> /* functions for timespecs */ -#define ts_diff_ns(t0, tx) (((tx)->tv_sec - (t0)->tv_sec) * BILLION \ +#define ts_diff_ns(tx, t0) (((tx)->tv_sec - (t0)->tv_sec) * BILLION \ + ((tx)->tv_nsec - (t0)->tv_nsec)) -#define ts_diff_us(t0, tx) (((tx)->tv_sec - (t0)->tv_sec) * MILLION \ +#define ts_diff_us(tx, t0) (((tx)->tv_sec - (t0)->tv_sec) * MILLION \ + ((tx)->tv_nsec - (t0)->tv_nsec) / 1000L) -#define ts_diff_ms(t0, tx) (((tx)->tv_sec - (t0)->tv_sec) * 1000L \ +#define ts_diff_ms(tx, t0) (((tx)->tv_sec - (t0)->tv_sec) * 1000L \ + ((tx)->tv_nsec - (t0)->tv_nsec) / MILLION) /* functions for timevals are the same */ -#define tv_diff_us(t0, tx) (((tx)->tv_sec - (t0)->tv_sec) * MILLION \ +#define tv_diff_us(tx, t0) (((tx)->tv_sec - (t0)->tv_sec) * MILLION \ + ((tx)->tv_usec - (t0)->tv_usec) / 1000L) -#define tv_diff_ms(t0, tx) (((tx)->tv_sec - (t0)->tv_sec) * 1000L \ +#define tv_diff_ms(tx, t0) (((tx)->tv_sec - (t0)->tv_sec) * 1000L \ + ((tx)->tv_usec - (t0)->tv_usec) / MILLION) /* functions for timespecs */ |
