diff options
Diffstat (limited to 'src/ipcpd')
89 files changed, 14855 insertions, 7813 deletions
diff --git a/src/ipcpd/CMakeLists.txt b/src/ipcpd/CMakeLists.txt index 54294f11..a84e5369 100644 --- a/src/ipcpd/CMakeLists.txt +++ b/src/ipcpd/CMakeLists.txt @@ -1,62 +1,25 @@ -set(CONNMGR_RCV_TIMEOUT 1000 CACHE STRING - "Timeout for the connection manager to wait for OCEP info (ms).") -set(IPCP_DEBUG_LOCAL FALSE CACHE BOOL - "Use PID as address for local debugging") -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 - ) +) -set (COMMON_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/common/enroll.c - ) +set(COMMON_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/common/enroll.c +) -add_subdirectory(local) -add_subdirectory(eth) -add_subdirectory(udp) -add_subdirectory(unicast) -add_subdirectory(broadcast) +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) diff --git a/src/ipcpd/broadcast/CMakeLists.txt b/src/ipcpd/broadcast/CMakeLists.txt index d85f335e..6749f660 100644 --- a/src/ipcpd/broadcast/CMakeLists.txt +++ b/src/ipcpd/broadcast/CMakeLists.txt @@ -1,35 +1,20 @@ -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}) - -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(IPCP_BROADCAST_MPL 60 CACHE STRING - "Default maximum packet lifetime for the broadcast IPCP, in seconds") - -set(SOURCE_FILES - # Add source files here +set(BROADCAST_SOURCES connmgr.c dt.c main.c - ) +) + +add_executable(${IPCP_BROADCAST_TARGET} + ${BROADCAST_SOURCES} + ${IPCP_SOURCES} + ${COMMON_SOURCES} +) -add_executable(ipcpd-broadcast ${SOURCE_FILES} ${IPCP_SOURCES} ${COMMON_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 f297175d..a4d20ee7 100644 --- a/src/ipcpd/broadcast/connmgr.c +++ b/src/ipcpd/broadcast/connmgr.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Handles connections between components * diff --git a/src/ipcpd/broadcast/dt.c b/src/ipcpd/broadcast/dt.c index 938c9085..95483e33 100644 --- a/src/ipcpd/broadcast/dt.c +++ b/src/ipcpd/broadcast/dt.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Forward loop for broadcast * @@ -28,7 +28,7 @@ #include "config.h" -#define BROADCAST_MTU 1400 /* FIXME: avoid packet copy. */ +#define BROADCAST_MTU IPCP_BROADCAST_MTU /* FIXME: avoid packet copy. */ #define DT "dt" #define OUROBOROS_PREFIX DT @@ -58,14 +58,13 @@ struct nb { }; struct { - struct list_head nbs; - size_t nbs_len; - pthread_rwlock_t nbs_lock; + struct llist nbs; + pthread_rwlock_t lock; - fset_t * set; + fset_t * set; - pthread_t reader; - pthread_t listener; + pthread_t reader; + pthread_t listener; } fwd; static int dt_add_nb(int fd) @@ -73,12 +72,12 @@ static int dt_add_nb(int fd) struct list_head * p; struct nb * nb; - pthread_rwlock_wrlock(&fwd.nbs_lock); + pthread_rwlock_wrlock(&fwd.lock); - list_for_each(p, &fwd.nbs) { + llist_for_each(p, &fwd.nbs) { struct nb * el = list_entry(p, struct nb, next); if (el->fd == fd) { - pthread_rwlock_unlock(&fwd.nbs_lock); + pthread_rwlock_unlock(&fwd.lock); log_warn("Already know neighbor on fd %d.", fd); return 0; } @@ -86,18 +85,16 @@ static int dt_add_nb(int fd) nb = malloc(sizeof(*nb)); if (nb == NULL) { - pthread_rwlock_unlock(&fwd.nbs_lock); + pthread_rwlock_unlock(&fwd.lock); log_err("Failed to malloc neighbor struct."); return -ENOMEM; } nb->fd = fd; - list_add_tail(&nb->next, p); + llist_add_tail(&nb->next, &fwd.nbs); - ++fwd.nbs_len; - - pthread_rwlock_unlock(&fwd.nbs_lock); + pthread_rwlock_unlock(&fwd.lock); log_dbg("Neighbor %d added.", fd); @@ -109,21 +106,20 @@ static int dt_del_nb(int fd) struct list_head * p; struct list_head * h; - pthread_rwlock_wrlock(&fwd.nbs_lock); + pthread_rwlock_wrlock(&fwd.lock); - list_for_each_safe(p, h, &fwd.nbs) { + llist_for_each_safe(p, h, &fwd.nbs) { struct nb * nb = list_entry(p, struct nb, next); if (nb->fd == fd) { - list_del(&nb->next); - --fwd.nbs_len; - pthread_rwlock_unlock(&fwd.nbs_lock); + llist_del(&nb->next, &fwd.nbs); + pthread_rwlock_unlock(&fwd.lock); log_dbg("Neighbor %d deleted.", nb->fd); free(nb); return 0; } } - pthread_rwlock_unlock(&fwd.nbs_lock); + pthread_rwlock_unlock(&fwd.lock); log_err("Neighbor not found on fd %d.", fd); @@ -157,11 +153,11 @@ static void dt_packet(uint8_t * buf, { struct list_head * p; - pthread_rwlock_rdlock(&fwd.nbs_lock); + pthread_rwlock_rdlock(&fwd.lock); - pthread_cleanup_push(__cleanup_rwlock_unlock, &fwd.nbs_lock); + pthread_cleanup_push(__cleanup_rwlock_unlock, &fwd.lock); - list_for_each(p, &fwd.nbs) { + llist_for_each(p, &fwd.nbs) { struct nb * nb = list_entry(p, struct nb, next); if (nb->fd != in_fd) flow_write(nb->fd, buf, len); /* FIXME: avoid copy. */ @@ -252,12 +248,12 @@ int dt_init(void) strcpy(info.comp_name, DT); strcpy(info.comp_name, DT_COMP); - list_head_init(&fwd.nbs); + llist_init(&fwd.nbs); if (notifier_reg(handle_event, NULL)) goto fail_notifier_reg; - if (pthread_rwlock_init(&fwd.nbs_lock, NULL)) + if (pthread_rwlock_init(&fwd.lock, NULL)) goto fail_lock_init; fwd.set = fset_create(); @@ -273,8 +269,6 @@ int dt_init(void) if (connmgr_comp_init(COMPID_DT, &info)) goto fail_connmgr_comp_init; - fwd.nbs_len = 0; - return 0; fail_connmgr_comp_init: @@ -286,7 +280,7 @@ int dt_init(void) fail_pthread_create_reader: fset_destroy(fwd.set); fail_fset_create: - pthread_rwlock_destroy(&fwd.nbs_lock); + pthread_rwlock_destroy(&fwd.lock); fail_lock_init: notifier_unreg(handle_event); fail_notifier_reg: @@ -308,15 +302,15 @@ void dt_fini(void) fset_destroy(fwd.set); - pthread_rwlock_wrlock(&fwd.nbs_lock); + pthread_rwlock_wrlock(&fwd.lock); - list_for_each_safe(p, h, &fwd.nbs) { + llist_for_each_safe(p, h, &fwd.nbs) { struct nb * n = list_entry(p, struct nb, next); - list_del(&n->next); + llist_del(&n->next, &fwd.nbs); free(n); } - pthread_rwlock_unlock(&fwd.nbs_lock); + pthread_rwlock_unlock(&fwd.lock); - pthread_rwlock_destroy(&fwd.nbs_lock); + pthread_rwlock_destroy(&fwd.lock); } diff --git a/src/ipcpd/broadcast/dt.h b/src/ipcpd/broadcast/dt.h index 8d3b83f8..2472831e 100644 --- a/src/ipcpd/broadcast/dt.h +++ b/src/ipcpd/broadcast/dt.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Forward loop for broadcast * diff --git a/src/ipcpd/broadcast/main.c b/src/ipcpd/broadcast/main.c index f51fc629..22b34a76 100644 --- a/src/ipcpd/broadcast/main.c +++ b/src/ipcpd/broadcast/main.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Broadcast IPC Process * @@ -36,6 +36,8 @@ #include <ouroboros/ipcp-dev.h> #include <ouroboros/logs.h> #include <ouroboros/notifier.h> +#include <ouroboros/np1_flow.h> +#include <ouroboros/qos.h> #include <ouroboros/random.h> #include <ouroboros/rib.h> #include <ouroboros/time.h> @@ -52,13 +54,8 @@ #include <assert.h> #include <inttypes.h> -struct ipcp ipcpi; - -static int initialize_components(const struct ipcp_config * conf) +static int initialize_components(void) { - strcpy(ipcpi.layer_name, conf->layer_info.name); - ipcpi.dir_hash_algo = (enum hash_algo) conf->layer_info.dir_hash_algo; - assert(ipcp_dir_hash_len() != 0); if (dt_init() < 0) { @@ -104,11 +101,14 @@ static void stop_components(void) enroll_stop(); } -static int broadcast_ipcp_enroll(const char * dst, - struct layer_info * info) +static int broadcast_ipcp_enroll(const char * dst, + const struct poa_addr * addr, + struct layer_info * info) { - struct conn conn; - uint8_t id[ENROLL_ID_LEN]; + struct ipcp_config * conf; + struct conn conn; + uint8_t id[ENROLL_ID_LEN]; + qosspec_t qs = qos_msg; if (random_buffer(id, ENROLL_ID_LEN) < 0) { log_err("Failed to generate enrollment ID."); @@ -117,7 +117,7 @@ static int broadcast_ipcp_enroll(const char * dst, log_info_id(id, "Requesting enrollment."); - if (connmgr_alloc(COMPID_ENROLL, dst, NULL, &conn) < 0) { + if (connmgr_alloc(COMPID_ENROLL, dst, &qs, addr, &conn) < 0) { log_err_id(id, "Failed to get connection."); goto fail_id; } @@ -128,7 +128,10 @@ static int broadcast_ipcp_enroll(const char * dst, goto fail_enroll_boot; } - if (initialize_components(enroll_get_conf()) < 0) { + conf = enroll_get_conf(); + *info = conf->layer_info; + + if (initialize_components() < 0) { log_err_id(id, "Failed to initialize components."); goto fail_enroll_boot; } @@ -146,9 +149,6 @@ static int broadcast_ipcp_enroll(const char * dst, log_info_id(id, "Enrolled with %s.", dst); - info->dir_hash_algo = (enum pol_dir_hash) ipcpi.dir_hash_algo; - strcpy(info->name, ipcpi.layer_name); - return 0; fail_start_comp: @@ -159,16 +159,15 @@ static int broadcast_ipcp_enroll(const char * dst, 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); - ((struct ipcp_config *) conf)->layer_info.dir_hash_algo = - DIR_HASH_SHA3_256; + 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; } @@ -190,44 +189,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; + } - str_hash(ipcpi.dir_hash_algo, buf, ipcpi.layer_name); + err = ipcp_get_layer_name(layer); + if (err < 0) { + log_err("Failed to get layer name."); + goto fail_layer; + } - ret = memcmp(buf, dst, len); + str_hash(HASH_SHA3_256, buf, layer); + + 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; time_t mpl = IPCP_BROADCAST_MPL; - buffer_t data = {NULL, 0}; - - (void) qs; + 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) { + err = name_check(dst); + if (err < 0) { log_err("Failed to check name."); - return -1; + return err; } notifier_event(NOTIFY_DT_CONN_ADD, &conn); - ipcp_flow_alloc_reply(fd, 0, mpl, &data); + ipcp_flow_alloc_reply(fd, 0, mpl, IPCP_BROADCAST_MTU, &data); return 0; } @@ -292,12 +310,13 @@ int main(int argc, ipcp_sigwait(); if (ipcp_get_state() == IPCP_SHUTDOWN) { + ipcp_stop(); stop_components(); finalize_components(); + } else { + ipcp_stop(); } - ipcp_stop(); - enroll_fini(); connmgr_fini(); diff --git a/src/ipcpd/common/comp.h b/src/ipcpd/common/comp.h index f3790d9c..e1d025b6 100644 --- a/src/ipcpd/common/comp.h +++ b/src/ipcpd/common/comp.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Components for the unicast/broadcast IPC process * diff --git a/src/ipcpd/common/connmgr.c b/src/ipcpd/common/connmgr.c index 4b5fd420..e0ad80cb 100644 --- a/src/ipcpd/common/connmgr.c +++ b/src/ipcpd/common/connmgr.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Handles connections between components * @@ -26,10 +26,12 @@ #include <ouroboros/dev.h> #include <ouroboros/errno.h> #include <ouroboros/fccntl.h> +#include <ouroboros/ipcp-dev.h> #include <ouroboros/list.h> #include <ouroboros/logs.h> #include <ouroboros/notifier.h> #include <ouroboros/pthread.h> +#include <ouroboros/qos.h> #include "connmgr.h" #include "ipcp.h" @@ -38,11 +40,9 @@ #include <stdlib.h> #include <string.h> -enum connmgr_state { - CONNMGR_NULL = 0, - CONNMGR_INIT, - CONNMGR_RUNNING -}; +#define CONNMGR_ETH_PROBE_TIMEO 20 /* ms, one query attempt */ +#define CONNMGR_ETH_RETRY_TIMEO 1500 /* ms, the remaining tries */ +#define CONNMGR_DHT_TIMEO 1000 /* ms, bounded lower-layer */ struct conn_el { struct list_head next; @@ -61,11 +61,18 @@ struct comp { struct { struct comp comps[COMPID_MAX]; - enum connmgr_state state; pthread_t acceptor; } connmgr; +static bool is_eth_query(const struct poa_addr * addr) +{ + static const uint8_t zero[POA_MAC_SIZE] = { 0 }; + + return addr->type == POA_ETH && + memcmp(addr->eth.dst.mac, zero, POA_MAC_SIZE) == 0; +} + static int get_id_by_name(const char * name) { enum comp_id i; @@ -127,10 +134,10 @@ static int add_comp_conn(enum comp_id id, return 0; } +/* qs is also an in-parameter, and flow_accept writes it back. */ static void * flow_acceptor(void * o) { int fd; - qosspec_t qs; struct conn_info rcv_info; struct conn_info fail_info; struct timespec timeo = TIMESPEC_INIT_MS(CONNMGR_RCV_TIMEOUT); @@ -141,7 +148,8 @@ static void * flow_acceptor(void * o) memset(&fail_info, 0, sizeof(fail_info)); while (true) { - int id; + qosspec_t qs = qos_raw; + int id; fd = flow_accept(&qs, NULL); if (fd < 0) { @@ -179,6 +187,8 @@ static void * flow_acceptor(void * o) continue; } + 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); @@ -226,8 +236,6 @@ static void handle_event(void * self, int connmgr_init(void) { - connmgr.state = CONNMGR_INIT; - if (notifier_reg(handle_event, NULL)) { log_err("Failed to register notifier."); return -1; @@ -240,13 +248,10 @@ 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) @@ -256,15 +261,13 @@ int connmgr_start(void) 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, @@ -334,9 +337,10 @@ void connmgr_comp_fini(enum comp_id id) memset(&connmgr.comps[id].info, 0, sizeof(connmgr.comps[id].info)); } -int connmgr_ipcp_connect(const char * dst, - const char * component, - qosspec_t qs) +int connmgr_ipcp_connect(const char * dst, + const char * component, + qosspec_t qs, + const struct poa_addr * addr) { struct conn_el * ce; int id; @@ -345,6 +349,11 @@ int connmgr_ipcp_connect(const char * dst, assert(dst); assert(component); + if (qs.service == SVC_STREAM) { + log_err("No stream service on component flows."); + return -ENOTSUP; + } + ce = malloc(sizeof(*ce)); if (ce == NULL) { log_err("Out of memory."); @@ -359,7 +368,7 @@ int connmgr_ipcp_connect(const char * dst, pthread_cleanup_push(free, ce); - ret = connmgr_alloc(id, dst, &qs, &ce->conn); + ret = connmgr_alloc(id, dst, &qs, addr, &ce->conn); pthread_cleanup_pop(false); @@ -425,10 +434,46 @@ int connmgr_ipcp_disconnect(const char * dst, return 0; } -int connmgr_alloc(enum comp_id id, - const char * dst, - qosspec_t * qs, - struct conn * conn) +/* + * Without an address, a peer may be on the wire or reachable through + * the layer below. A PoA query is cheap and creates no flow, so it + * goes first; the layer below gets a bounded try before the query + * retries, and the last try is unbounded. + */ +static int alloc_any(const char * dst, + qosspec_t * qs) +{ + struct timespec probe = TIMESPEC_INIT_MS(CONNMGR_ETH_PROBE_TIMEO); + struct timespec retry = TIMESPEC_INIT_MS(CONNMGR_ETH_RETRY_TIMEO); + struct timespec below = TIMESPEC_INIT_MS(CONNMGR_DHT_TIMEO); + struct poa_addr addr; + int fd; + + if (poa_query(dst, &probe, &addr) == 0) { + fd = poa_flow_alloc(dst, &addr, qs, NULL); + if (fd >= 0) + return fd; + } + + fd = flow_alloc(dst, qs, &below); + if (fd >= 0) + return fd; + + if (poa_query(dst, &retry, &addr) == 0) { + fd = poa_flow_alloc(dst, &addr, qs, NULL); + if (fd >= 0) + return fd; + } + + return flow_alloc(dst, qs, NULL); +} + +/* A literal peer address bypasses the layer below. */ +int connmgr_alloc(enum comp_id id, + const char * dst, + qosspec_t * qs, + const struct poa_addr * addr, + struct conn * conn) { struct comp * comp; int fd; @@ -439,7 +484,26 @@ int connmgr_alloc(enum comp_id id, comp = connmgr.comps + id; - fd = flow_alloc(dst, qs, NULL); + if (addr != NULL) + fd = poa_flow_alloc(dst, addr, qs, NULL); + else + fd = alloc_any(dst, qs); + + if (fd == -EPERM && addr != NULL) { + log_err("No PoA attached to reach %s.", dst); + goto fail_alloc; + } + + if (fd == -EINVAL && addr != NULL) { + log_err("More than one PoA could reach %s", dst); + goto fail_alloc; + } + + if (fd == -ETIMEDOUT && addr != NULL && is_eth_query(addr)) { + log_err("No answer to name query for %s.", dst); + goto fail_alloc; + } + if (fd < 0) { log_err("Failed to allocate flow to %s.", dst); goto fail_alloc; @@ -487,9 +551,6 @@ int connmgr_alloc(enum comp_id id, 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); @@ -513,7 +574,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; diff --git a/src/ipcpd/common/connmgr.h b/src/ipcpd/common/connmgr.h index 0710dbbf..86f80fb0 100644 --- a/src/ipcpd/common/connmgr.h +++ b/src/ipcpd/common/connmgr.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Handles the different AP connections * @@ -24,6 +24,7 @@ #define OUROBOROS_IPCPD_COMMON_CONNMGR_H #include <ouroboros/cep.h> +#include <ouroboros/ipcp.h> #include <ouroboros/qos.h> #include "comp.h" @@ -53,17 +54,19 @@ int connmgr_comp_init(enum comp_id id, void connmgr_comp_fini(enum comp_id id); -int connmgr_ipcp_connect(const char * dst, - const char * component, - qosspec_t qs); +int connmgr_ipcp_connect(const char * dst, + const char * component, + qosspec_t qs, + const struct poa_addr * addr); int connmgr_ipcp_disconnect(const char * dst, const char * component); -int connmgr_alloc(enum comp_id id, - const char * dst, - qosspec_t * qs, - struct conn * conn); +int connmgr_alloc(enum comp_id id, + const char * dst, + qosspec_t * qs, + const struct poa_addr * addr, + struct conn * conn); int connmgr_dealloc(enum comp_id id, struct conn * conn); diff --git a/src/ipcpd/common/enroll.c b/src/ipcpd/common/enroll.c index 5e35ce37..959cca07 100644 --- a/src/ipcpd/common/enroll.c +++ b/src/ipcpd/common/enroll.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Enrollment Task * @@ -43,20 +43,19 @@ #include <string.h> #include <pthread.h> +#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; @@ -107,8 +106,6 @@ static void * enroll_handle(void * o) log_info_id(req.id, "Handling incoming enrollment."); - /* TODO: authentication, timezone handling (UTC). */ - ack.result = -100; clock_gettime(CLOCK_REALTIME, &resp.t); @@ -227,12 +224,14 @@ int enroll_boot(struct conn * conn, return -1; } - if (resp.conf.type != ipcpi.type) { + if (resp.conf.type != ipcp_get_type()) { log_err_id(id, "Wrong type in enrollment response %d (%d).", - resp.conf.type, ipcpi.type); + resp.conf.type, ipcp_get_type()); return -1; } + enroll.conf = resp.conf; + clock_gettime(CLOCK_REALTIME, &rtt); delta_t = ts_diff_ms(&t0, &rtt); @@ -240,11 +239,9 @@ int enroll_boot(struct conn * conn, rtt.tv_sec = resp.t.tv_sec; rtt.tv_nsec = resp.t.tv_nsec; - if (labs(ts_diff_ms(&t0, &rtt)) - delta_t > ENROLL_WARN_TIME_OFFSET) + if (llabs(ts_diff_ms(&t0, &rtt)) - delta_t > ENROLL_WARN_TIME_OFFSET) log_warn_id(id, "Clock offset above threshold."); - enroll.conf = resp.conf; - return 0; } @@ -307,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); } @@ -325,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 f26c31a3..f56adfc8 100644 --- a/src/ipcpd/common/enroll.h +++ b/src/ipcpd/common/enroll.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Enrollment Task * diff --git a/src/ipcpd/config.h.in b/src/ipcpd/config.h.in index fe4f5fd2..55b0120c 100644 --- a/src/ipcpd/config.h.in +++ b/src/ipcpd/config.h.in @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * IPC process configuration * @@ -23,17 +23,16 @@ #define PTHREAD_COND_CLOCK @PTHREAD_COND_CLOCK@ #define SYS_MAX_FLOWS @SYS_MAX_FLOWS@ -#define PROG_RES_FDS @PROG_RES_FDS@ -#define PROG_MAX_FLOWS @PROG_MAX_FLOWS@ +#define PROC_RES_FDS @PROC_RES_FDS@ +#define PROC_MAX_FLOWS @PROC_MAX_FLOWS@ +#define POA_MAX_POAS @POA_MAX_POAS@ #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@ @@ -41,43 +40,32 @@ #define IPCP_LINUX_SLACK_NS @IPCP_LINUX_TIMERSLACK_NS@ -#cmakedefine IPCP_DEBUG_LOCAL - /* unicast IPCP */ #define QOS_PRIO_BE @IPCP_QOS_CUBE_BE_PRIO@ #define QOS_PRIO_VIDEO @IPCP_QOS_CUBE_VIDEO_PRIO@ #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 IPCP_UNICAST_MTU @IPCP_UNICAST_MTU@ #define CONNMGR_RCV_TIMEOUT @CONNMGR_RCV_TIMEOUT@ +#define CA_BUCKETS @IPCP_CA_BUCKETS@ +#cmakedefine IPCP_CA_PER_FLOW -#cmakedefine IPCP_CONN_WAIT_DIR #cmakedefine DISABLE_CORE_LOCK +#cmakedefine BUILD_CONTAINER #cmakedefine IPCP_FLOW_STATS - -/* udp */ -#cmakedefine HAVE_DDNS -#define NSUPDATE_EXEC "@NSUPDATE_EXECUTABLE@" -#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 */ -#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_MPL @IPCP_ETH_MPL@ +#cmakedefine IPCP_DEBUG_LOCAL +#ifdef CONFIG_OUROBOROS_DEBUG +#cmakedefine DEBUG_PROTO_DHT +#cmakedefine DEBUG_PROTO_OEP +#cmakedefine DEBUG_PROTO_LS +#endif /* local */ #define IPCP_LOCAL_MPL @IPCP_LOCAL_MPL@ +#define IPCP_LOCAL_MTU @IPCP_LOCAL_MTU@ /* broadcast */ -/* local */ #define IPCP_BROADCAST_MPL @IPCP_BROADCAST_MPL@ +#define IPCP_BROADCAST_MTU @IPCP_BROADCAST_MTU@ diff --git a/src/ipcpd/eth/CMakeLists.txt b/src/ipcpd/eth/CMakeLists.txt deleted file mode 100644 index d57e1848..00000000 --- a/src/ipcpd/eth/CMakeLists.txt +++ /dev/null @@ -1,134 +0,0 @@ -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(IPCP_ETH_MPL 5 CACHE STRING - "Default maximum packet lifetime for the Ethernet IPCPs, in seconds") - - 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 () diff --git a/src/ipcpd/eth/eth.c b/src/ipcpd/eth/eth.c deleted file mode 100644 index ea6e0f1c..00000000 --- a/src/ipcpd/eth/eth.c +++ /dev/null @@ -1,1820 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2024 - * - * IPC processes over Ethernet - * - * 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(BUILD_ETH_DIX) && !defined(BUILD_ETH_LLC) -#error Define BUILD_ETH_DIX or BUILD_ETH_LLC to build an Ethernet IPCP -#endif - -#if defined(__APPLE__) -#define _BSD_SOURCE -#define _DARWIN_C_SOURCE -#elif defined(__FreeBSD__) -#define __BSD_VISIBLE 1 -#elif defined (__linux__) || defined (__CYGWIN__) -#define _DEFAULT_SOURCE -#else -#define _POSIX_C_SOURCE 200112L -#endif - -#include "config.h" - -#include <ouroboros/endian.h> -#include <ouroboros/hash.h> -#include <ouroboros/errno.h> -#include <ouroboros/list.h> -#include <ouroboros/utils.h> -#include <ouroboros/bitmap.h> -#include <ouroboros/dev.h> -#include <ouroboros/ipcp-dev.h> -#include <ouroboros/fqueue.h> -#include <ouroboros/logs.h> -#include <ouroboros/time.h> -#include <ouroboros/fccntl.h> -#include <ouroboros/pthread.h> - -#include "ipcp.h" -#include "shim-data.h" - -#include <signal.h> -#include <stdlib.h> -#include <fcntl.h> -#include <unistd.h> -#include <string.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <sys/ioctl.h> - -#include <net/if.h> -#include <netinet/in.h> - -#ifdef __linux__ -#include <linux/if_packet.h> -#include <linux/if_ether.h> -#include <linux/netlink.h> -#include <linux/rtnetlink.h> -#endif - -#ifdef __FreeBSD__ -#include <net/if_dl.h> -#include <netinet/if_ether.h> -#include <ifaddrs.h> -#endif - -#ifdef __APPLE__ -#include <net/if_dl.h> -#include <ifaddrs.h> -#endif - -#include <poll.h> -#include <sys/mman.h> - -#if defined(HAVE_NETMAP) -#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> -#endif - -#ifdef __linux__ -#ifndef ETH_MAX_MTU /* In if_ether.h as of Linux 4.10. */ -#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 -#else -#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) - -#if defined(BUILD_ETH_DIX) -#define THIS_TYPE IPCP_ETH_DIX -#define MGMT_EID 0 -#define DIX_EID_SIZE sizeof(uint16_t) -#define DIX_LENGTH_SIZE sizeof(uint16_t) -#define DIX_HEADER_SIZE (DIX_EID_SIZE + DIX_LENGTH_SIZE) -#define ETH_HEADER_TOT_SIZE (ETH_HEADER_SIZE + DIX_HEADER_SIZE) -#define MAX_EIDS (1 << (8 * DIX_EID_SIZE)) -#define ETH_MAX_PACKET_SIZE (ETH_MTU - DIX_HEADER_SIZE) -#define ETH_FRAME_SIZE (ETH_HEADER_SIZE + ETH_MTU_MAX) -#elif defined(BUILD_ETH_LLC) -#define THIS_TYPE IPCP_ETH_LLC -#define MGMT_SAP 0x01 -#define LLC_HEADER_SIZE 3 -#define ETH_HEADER_TOT_SIZE (ETH_HEADER_SIZE + LLC_HEADER_SIZE) -#define MAX_SAPS 64 -#define ETH_MAX_PACKET_SIZE (ETH_MTU - LLC_HEADER_SIZE) -#define ETH_FRAME_SIZE (ETH_HEADER_SIZE + ETH_MTU_MAX) -#endif - -#define NAME_QUERY_TIMEO 2000 /* ms */ -#define MGMT_TIMEO 100 /* ms */ -#define MGMT_FRAME_SIZE 2048 - -#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; - uint16_t deid; -#elif defined(BUILD_ETH_LLC) - uint8_t ssap; - uint8_t dsap; - /* QoS here for alignment */ - uint8_t code; - uint8_t availability; -#endif - /* QoS parameters from spec, aligned */ - uint32_t loss; - uint64_t bandwidth; - uint32_t ber; - uint32_t max_gap; - uint32_t delay; - uint32_t timeout; - uint16_t cypher_s; - uint8_t in_order; -#if defined (BUILD_ETH_DIX) - uint8_t code; - uint8_t availability; -#endif - int8_t response; -} __attribute__((packed)); - -struct eth_frame { - uint8_t dst_hwaddr[MAC_SIZE]; - uint8_t src_hwaddr[MAC_SIZE]; -#if defined(BUILD_ETH_DIX) - uint16_t ethertype; - uint16_t eid; - uint16_t length; -#elif defined(BUILD_ETH_LLC) - uint16_t length; - uint8_t dsap; - uint8_t ssap; - uint8_t cf; -#endif - uint8_t payload; -} __attribute__((packed)); - -struct ef { -#if defined(BUILD_ETH_DIX) - int32_t r_eid; -#elif defined(BUILD_ETH_LLC) - int8_t sap; - int8_t r_sap; -#endif - uint8_t r_addr[MAC_SIZE]; -}; - -struct mgmt_frame { - struct list_head next; - uint8_t r_addr[MAC_SIZE]; - uint8_t buf[MGMT_FRAME_SIZE]; - size_t len; -}; - -struct { - struct shim_data * shim_data; -#ifdef __linux__ - int mtu; - int if_idx; -#endif -#if defined(HAVE_NETMAP) - struct nm_desc * nmd; - uint8_t hw_addr[MAC_SIZE]; - struct pollfd poll_in; - struct pollfd poll_out; -#elif defined(HAVE_BPF) - int bpf; - uint8_t hw_addr[MAC_SIZE]; -#elif defined(HAVE_RAW_SOCKETS) - int s_fd; - struct sockaddr_ll device; -#endif /* HAVE_NETMAP */ -#if defined (BUILD_ETH_DIX) - uint16_t ethertype; -#elif defined(BUILD_ETH_LLC) - struct bmp * saps; - int * ef_to_fd; -#endif - struct ef * fd_to_ef; - fset_t * np1_flows; - pthread_rwlock_t flows_lock; - - pthread_t packet_writer[IPCP_ETH_WR_THR]; - pthread_t packet_reader[IPCP_ETH_RD_THR]; - -#ifdef __linux__ - pthread_t if_monitor; -#endif - - /* Handle mgmt frames in a different thread */ - pthread_t mgmt_handler; - pthread_mutex_t mgmt_lock; - pthread_cond_t mgmt_cond; - struct list_head mgmt_frames; -} eth_data; - -static int eth_data_init(void) -{ - int i; - int ret = -ENOMEM; - pthread_condattr_t cattr; - - eth_data.fd_to_ef = - malloc(sizeof(*eth_data.fd_to_ef) * SYS_MAX_FLOWS); - if (eth_data.fd_to_ef == NULL) - goto fail_fd_to_ef; - -#ifdef BUILD_ETH_LLC - eth_data.ef_to_fd = - malloc(sizeof(*eth_data.ef_to_fd) * MAX_SAPS); - if (eth_data.ef_to_fd == NULL) - goto fail_ef_to_fd; - - for (i = 0; i < MAX_SAPS; ++i) - eth_data.ef_to_fd[i] = -1; - - eth_data.saps = bmp_create(MAX_SAPS, 2); - if (eth_data.saps == NULL) - goto fail_saps; -#endif - eth_data.np1_flows = fset_create(); - if (eth_data.np1_flows == NULL) - goto fail_np1_flows; - - for (i = 0; i < SYS_MAX_FLOWS; ++i) { -#if defined(BUILD_ETH_DIX) - eth_data.fd_to_ef[i].r_eid = -1; -#elif defined(BUILD_ETH_LLC) - eth_data.fd_to_ef[i].sap = -1; - eth_data.fd_to_ef[i].r_sap = -1; -#endif - memset(ð_data.fd_to_ef[i].r_addr, 0, MAC_SIZE); - } - - eth_data.shim_data = shim_data_create(); - if (eth_data.shim_data == NULL) - goto fail_shim_data; - - ret = -1; - - if (pthread_rwlock_init(ð_data.flows_lock, NULL)) - goto fail_flows_lock; - - if (pthread_mutex_init(ð_data.mgmt_lock, NULL)) - goto fail_mgmt_lock; - - if (pthread_condattr_init(&cattr)) - goto fail_condattr; - -#ifndef __APPLE__ - pthread_condattr_setclock(&cattr, PTHREAD_COND_CLOCK); -#endif - - if (pthread_cond_init(ð_data.mgmt_cond, &cattr)) - goto fail_mgmt_cond; - - pthread_condattr_destroy(&cattr); - - list_head_init(ð_data.mgmt_frames); - - return 0; - - fail_mgmt_cond: - pthread_condattr_destroy(&cattr); - fail_condattr: - pthread_mutex_destroy(ð_data.mgmt_lock); - fail_mgmt_lock: - pthread_rwlock_destroy(ð_data.flows_lock); - fail_flows_lock: - shim_data_destroy(eth_data.shim_data); - fail_shim_data: - fset_destroy(eth_data.np1_flows); - fail_np1_flows: -#ifdef BUILD_ETH_LLC - bmp_destroy(eth_data.saps); - fail_saps: - free(eth_data.ef_to_fd); - fail_ef_to_fd: -#endif - free(eth_data.fd_to_ef); - fail_fd_to_ef: - return ret; -} - -static void eth_data_fini(void) -{ -#if defined(HAVE_NETMAP) - nm_close(eth_data.nmd); -#elif defined(HAVE_BPF) - close(eth_data.bpf); -#elif defined(HAVE_RAW_SOCKETS) - close(eth_data.s_fd); -#endif - pthread_cond_destroy(ð_data.mgmt_cond); - pthread_mutex_destroy(ð_data.mgmt_lock); - pthread_rwlock_destroy(ð_data.flows_lock); - shim_data_destroy(eth_data.shim_data); - fset_destroy(eth_data.np1_flows); -#ifdef BUILD_ETH_LLC - bmp_destroy(eth_data.saps); - free(eth_data.ef_to_fd); -#endif - free(eth_data.fd_to_ef); -} - -#ifdef BUILD_ETH_LLC -static uint8_t reverse_bits(uint8_t b) -{ - b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; - b = (b & 0xCC) >> 2 | (b & 0x33) << 2; - b = (b & 0xAA) >> 1 | (b & 0x55) << 1; - - return b; -} -#endif - -/* Pass a buffer that contains space for the header. */ -static int eth_ipcp_send_frame(const uint8_t * dst_addr, -#if defined(BUILD_ETH_DIX) - uint16_t deid, -#elif defined(BUILD_ETH_LLC) - uint8_t dsap, - uint8_t ssap, -#endif - const uint8_t * frame, - size_t len) -{ - uint32_t frame_len = 0; -#ifdef BUILD_ETH_LLC - uint8_t cf = 0x03; -#endif - struct eth_frame * e_frame; -#ifdef HAVE_RAW_SOCKETS - fd_set fds; - - FD_ZERO(&fds); -#endif - - assert(frame); - - if (len > (size_t) ETH_MAX_PACKET_SIZE) - return -1; - - e_frame = (struct eth_frame *) frame; - - memcpy(e_frame->dst_hwaddr, dst_addr, MAC_SIZE); - memcpy(e_frame->src_hwaddr, -#if defined(HAVE_NETMAP) || defined(HAVE_BPF) - eth_data.hw_addr, -#elif defined(HAVE_RAW_SOCKETS) - eth_data.device.sll_addr, -#endif /* HAVE_NETMAP */ - MAC_SIZE); -#if defined(BUILD_ETH_DIX) - e_frame->ethertype = eth_data.ethertype; - e_frame->eid = htons(deid); - e_frame->length = htons(len); - frame_len = ETH_HEADER_TOT_SIZE + len; -#elif defined(BUILD_ETH_LLC) - e_frame->length = htons(LLC_HEADER_SIZE + len); - e_frame->dsap = dsap; - e_frame->ssap = ssap; - e_frame->cf = cf; - frame_len = ETH_HEADER_TOT_SIZE + len; -#endif - -#if defined(HAVE_NETMAP) - if (poll(ð_data.poll_out, 1, -1) < 0) - return -1; - - if (nm_inject(eth_data.nmd, frame, frame_len) != (int) frame_len) { - log_dbg("Failed to send message."); - return -1; - } -#elif defined(HAVE_BPF) - if (write(eth_data.bpf, frame, frame_len) < 0) { - log_dbg("Failed to send message."); - return -1; - } - -#elif defined(HAVE_RAW_SOCKETS) - FD_SET(eth_data.s_fd, &fds); - if (select(eth_data.s_fd + 1, NULL, &fds, NULL, NULL) < 0) { - log_dbg("Select() failed: %s.", strerror(errno)); - return -1; - } - assert(FD_ISSET(eth_data.s_fd, &fds)); - - if (sendto(eth_data.s_fd, - frame, - frame_len, - 0, - (struct sockaddr *) ð_data.device, - sizeof(eth_data.device)) <= 0) { - log_dbg("Failed to send message: %s.", strerror(errno)); - return -1; - } -#endif /* HAVE_NETMAP */ - - return 0; -} - -static int eth_ipcp_alloc(const uint8_t * dst_addr, -#if defined(BUILD_ETH_DIX) - uint16_t eid, -#elif defined(BUILD_ETH_LLC) - uint8_t ssap, -#endif - const uint8_t * hash, - qosspec_t qs, - const buffer_t * data) -{ - uint8_t * buf; - struct mgmt_msg * msg; - size_t len; - int ret; - - len = sizeof(*msg) + ipcp_dir_hash_len(); - - buf = malloc(len + ETH_HEADER_TOT_SIZE + data->len); - if (buf == NULL) - return -1; - - msg = (struct mgmt_msg *) (buf + ETH_HEADER_TOT_SIZE); - msg->code = FLOW_REQ; -#if defined(BUILD_ETH_DIX) - msg->seid = htons(eid); -#elif defined(BUILD_ETH_LLC) - msg->ssap = ssap; -#endif - - msg->delay = hton32(qs.delay); - msg->bandwidth = hton64(qs.bandwidth); - msg->availability = qs.availability; - msg->loss = hton32(qs.loss); - 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()); - 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) - MGMT_EID, -#elif defined(BUILD_ETH_LLC) - reverse_bits(MGMT_SAP), - reverse_bits(MGMT_SAP), -#endif - buf, len + data->len); - free(buf); - - return ret; -} - -static int eth_ipcp_alloc_resp(uint8_t * dst_addr, -#if defined(BUILD_ETH_DIX) - uint16_t seid, - uint16_t deid, -#elif defined(BUILD_ETH_LLC) - uint8_t ssap, - uint8_t dsap, -#endif - int response, - const buffer_t * data) -{ - struct mgmt_msg * msg; - uint8_t * buf; - - buf = malloc(sizeof(*msg) + ETH_HEADER_TOT_SIZE + data->len); - if (buf == NULL) - return -1; - - msg = (struct mgmt_msg *) (buf + ETH_HEADER_TOT_SIZE); - - msg->code = FLOW_REPLY; -#if defined(BUILD_ETH_DIX) - msg->seid = htons(seid); - msg->deid = htons(deid); -#elif defined(BUILD_ETH_LLC) - msg->ssap = ssap; - msg->dsap = dsap; -#endif - msg->response = response; - - if (data->len > 0) - memcpy(msg + 1, data->data, data->len); - - if (eth_ipcp_send_frame(dst_addr, -#if defined(BUILD_ETH_DIX) - MGMT_EID, -#elif defined(BUILD_ETH_LLC) - reverse_bits(MGMT_SAP), - reverse_bits(MGMT_SAP), -#endif - buf, sizeof(*msg) + data->len)) { - free(buf); - return -1; - } - - free(buf); - - return 0; -} - -static int eth_ipcp_req(uint8_t * r_addr, -#if defined(BUILD_ETH_DIX) - uint16_t r_eid, -#elif defined(BUILD_ETH_LLC) - uint8_t r_sap, -#endif - const uint8_t * dst, - qosspec_t qs, - const buffer_t * data) -{ - int fd; - - fd = ipcp_wait_flow_req_arr(dst, qs, IPCP_ETH_MPL, data); - if (fd < 0) { - log_err("Could not get new flow from IRMd."); - return -1; - } - - pthread_rwlock_wrlock(ð_data.flows_lock); -#if defined(BUILD_ETH_DIX) - eth_data.fd_to_ef[fd].r_eid = r_eid; -#elif defined(BUILD_ETH_LLC) - eth_data.fd_to_ef[fd].r_sap = r_sap; -#endif - memcpy(eth_data.fd_to_ef[fd].r_addr, r_addr, MAC_SIZE); - - pthread_rwlock_unlock(ð_data.flows_lock); - -#if defined(BUILD_ETH_DIX) - log_dbg("New flow request, fd %d, remote endpoint %d.", fd, r_eid); -#elif defined(BUILD_ETH_LLC) - log_dbg("New flow request, fd %d, remote SAP %d.", fd, r_sap); -#endif - return 0; -} - -static int eth_ipcp_alloc_reply(uint8_t * r_addr, -#if defined(BUILD_ETH_DIX) - uint16_t seid, - uint16_t deid, -#elif defined(BUILD_ETH_LLC) - uint8_t ssap, - int dsap, -#endif - int response, - const buffer_t * data) -{ - int ret = 0; - int fd = -1; - time_t mpl = IPCP_ETH_MPL; - - pthread_rwlock_wrlock(ð_data.flows_lock); - -#if defined(BUILD_ETH_DIX) - fd = deid; -#elif defined(BUILD_ETH_LLC) - fd = eth_data.ef_to_fd[dsap]; -#endif - if (fd < 0) { - pthread_rwlock_unlock(& eth_data.flows_lock); - log_err("No flow found with that SAP."); - return -1; /* -EFLOWNOTFOUND */ - } - - if (response) { -#ifdef BUILD_ETH_LLC - bmp_release(eth_data.saps, eth_data.fd_to_ef[fd].sap); -#endif - } else { -#if defined(BUILD_ETH_DIX) - eth_data.fd_to_ef[fd].r_eid = seid; -#elif defined(BUILD_ETH_LLC) - eth_data.fd_to_ef[fd].r_sap = ssap; -#endif - memcpy(eth_data.fd_to_ef[fd].r_addr, r_addr, MAC_SIZE); - } - - pthread_rwlock_unlock(ð_data.flows_lock); - -#if defined(BUILD_ETH_DIX) - log_dbg("Flow reply, fd %d, src eid %d, dst eid %d.", fd, seid, deid); -#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, 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, - uint8_t * r_addr) -{ - uint8_t * buf; - struct mgmt_msg * msg; - size_t len; - - if (shim_data_reg_has(eth_data.shim_data, hash)) { - len = sizeof(*msg) + ipcp_dir_hash_len(); - - buf = malloc(len + ETH_HEADER_TOT_SIZE); - if (buf == NULL) - return -1; - - msg = (struct mgmt_msg *) (buf + ETH_HEADER_TOT_SIZE); - msg->code = NAME_QUERY_REPLY; - - memcpy(msg + 1, hash, ipcp_dir_hash_len()); - - if (eth_ipcp_send_frame(r_addr, -#if defined(BUILD_ETH_DIX) - MGMT_EID, -#elif defined(BUILD_ETH_LLC) - reverse_bits(MGMT_SAP), - reverse_bits(MGMT_SAP), -#endif - buf, len)) { - log_err("Failed to send management frame."); - free(buf); - return -1; - } - - free(buf); - } - - return 0; -} - -static int eth_ipcp_name_query_reply(const uint8_t * hash, - uint8_t * r_addr) -{ - uint64_t address = 0; - - memcpy(&address, r_addr, MAC_SIZE); - - shim_data_dir_add_entry(eth_data.shim_data, hash, address); - - shim_data_dir_query_respond(eth_data.shim_data, hash); - - return 0; -} - -static int eth_ipcp_mgmt_frame(const uint8_t * buf, - size_t len, - uint8_t * r_addr) -{ - struct mgmt_msg * msg; - size_t msg_len; - qosspec_t qs; - buffer_t data; - - msg = (struct mgmt_msg *) buf; - - switch (msg->code) { - case FLOW_REQ: - msg_len = sizeof(*msg) + ipcp_dir_hash_len(); - - assert(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.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))) { - eth_ipcp_req(r_addr, -#if defined(BUILD_ETH_DIX) - ntohs(msg->seid), -#elif defined(BUILD_ETH_LLC) - msg->ssap, -#endif - buf + sizeof(*msg), - qs, - &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), - ntohs(msg->deid), -#elif defined(BUILD_ETH_LLC) - msg->ssap, - msg->dsap, -#endif - msg->response, - &data); - break; - case NAME_QUERY_REQ: - eth_ipcp_name_query_req(buf + sizeof(*msg), r_addr); - break; - case NAME_QUERY_REPLY: - eth_ipcp_name_query_reply(buf + sizeof(*msg), r_addr); - break; - default: - log_err("Unknown message received %d.", msg->code); - return -1; - } - - return 0; -} - -static void * eth_ipcp_mgmt_handler(void * o) -{ - (void) o; - - pthread_cleanup_push(__cleanup_mutex_unlock, ð_data.mgmt_lock); - - while (true) { - 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); - - pthread_mutex_lock(ð_data.mgmt_lock); - - while (list_is_empty(ð_data.mgmt_frames) && - ret != -ETIMEDOUT) - 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); - - pthread_mutex_unlock(ð_data.mgmt_lock); - - if (frame == NULL) - continue; - - eth_ipcp_mgmt_frame(frame->buf, frame->len, frame->r_addr); - - free(frame); - } - - pthread_cleanup_pop(false); - - return (void *) 0; -} - -static void * eth_ipcp_packet_reader(void * o) -{ - uint8_t br_addr[MAC_SIZE]; -#if defined(BUILD_ETH_DIX) - uint16_t deid; -#elif defined(BUILD_ETH_LLC) - uint8_t dsap; - uint8_t ssap; -#endif - uint16_t length; - int fd; - uint8_t * buf; -#if defined(HAVE_NETMAP) - struct nm_pkthdr hdr; -#else - struct shm_du_buff * sdb; - fd_set fds; - int frame_len; -#endif - struct eth_frame * e_frame; - struct mgmt_frame * frame; - - (void) o; - - ipcp_lock_to_core(); - - memset(br_addr, 0xff, MAC_SIZE * sizeof(uint8_t)); - - while (true) { -#if defined(HAVE_NETMAP) - if (poll(ð_data.poll_in, 1, -1) < 0) - continue; - if (eth_data.poll_in.revents == 0) /* TIMED OUT */ - continue; - - buf = nm_nextpkt(eth_data.nmd, &hdr); - if (buf == NULL) { - log_dbg("Bad read from netmap device."); - continue; - } -#else - FD_ZERO(&fds); - #if defined(HAVE_BPF) - FD_SET(eth_data.bpf, &fds); - 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)) - continue; - buf = shm_du_buff_head(sdb); - 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)) - continue; - buf = shm_du_buff_head_alloc(sdb, ETH_HEADER_TOT_SIZE); - if (buf == NULL) { - log_dbg("Failed to allocate header."); - ipcp_sdb_release(sdb); - continue; - } - frame_len = recv(eth_data.s_fd, buf, - ETH_MTU + ETH_HEADER_TOT_SIZE, 0); - #endif - if (frame_len <= 0) { - log_dbg("Failed to receive frame."); - ipcp_sdb_release(sdb); - continue; - } -#endif - -#if defined(HAVE_BPF) && !defined(HAVE_NETMAP) - e_frame = (struct eth_frame *) - (buf + ((struct bpf_hdr *) buf)->bh_hdrlen); -#else - e_frame = (struct eth_frame *) buf; -#endif - assert(e_frame->dst_hwaddr); - -#if !defined(HAVE_BPF) - #if defined(HAVE_NETMAP) - if (memcmp(eth_data.hw_addr, - #elif defined(HAVE_RAW_SOCKETS) - if (memcmp(eth_data.device.sll_addr, - #endif /* HAVE_NETMAP */ - e_frame->dst_hwaddr, - MAC_SIZE) && - memcmp(br_addr, e_frame->dst_hwaddr, MAC_SIZE)) { - } -#endif - length = ntohs(e_frame->length); -#if defined(BUILD_ETH_DIX) - 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 */ - goto fail_frame; - - length -= LLC_HEADER_SIZE; - - dsap = reverse_bits(e_frame->dsap); - ssap = reverse_bits(e_frame->ssap); - - if (ssap == MGMT_SAP && dsap == MGMT_SAP) { -#endif - ipcp_sdb_release(sdb); /* No need for the N+1 buffer. */ - - frame = malloc(sizeof(*frame)); - if (frame == NULL) { - log_err("Failed to allocate frame."); - goto fail_frame; - } - - memcpy(frame->buf, &e_frame->payload, length); - memcpy(frame->r_addr, e_frame->src_hwaddr, MAC_SIZE); - frame->len = length; - - pthread_mutex_lock(ð_data.mgmt_lock); - list_add(&frame->next, ð_data.mgmt_frames); - pthread_cond_signal(ð_data.mgmt_cond); - pthread_mutex_unlock(ð_data.mgmt_lock); - } else { - pthread_rwlock_rdlock(ð_data.flows_lock); - -#if defined(BUILD_ETH_DIX) - fd = deid; -#elif defined(BUILD_ETH_LLC) - fd = eth_data.ef_to_fd[dsap]; -#endif - if (fd < 0) { - pthread_rwlock_unlock(ð_data.flows_lock); - goto fail_frame; - } - -#ifdef BUILD_ETH_LLC - if (eth_data.fd_to_ef[fd].r_sap != ssap - || memcmp(eth_data.fd_to_ef[fd].r_addr, - e_frame->src_hwaddr, MAC_SIZE)) { - pthread_rwlock_unlock(ð_data.flows_lock); - 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); -#else - if (ipcp_sdb_reserve(&sdb, length)) - continue; - - buf = shm_du_buff_head(sdb); - memcpy(buf, &e_frame->payload, length); -#endif - if (np1_flow_write(fd, sdb) < 0) - ipcp_sdb_release(sdb); - - continue; - fail_frame: -#ifndef HAVE_NETMAP - ipcp_sdb_release(sdb); -#endif - } - } - - return (void *) 0; -} - -static void cleanup_writer(void * o) -{ - fqueue_destroy((fqueue_t *) o); -} - -static void * eth_ipcp_packet_writer(void * o) -{ - int fd; - struct shm_du_buff * sdb; - size_t len; -#if defined(BUILD_ETH_DIX) - uint16_t deid; -#elif defined(BUILD_ETH_LLC) - uint8_t dsap; - uint8_t ssap; -#endif - uint8_t r_addr[MAC_SIZE]; - - fqueue_t * fq; - - fq = fqueue_create(); - if (fq == NULL) - return (void *) -1; - - (void) o; - - 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 (np1_flow_read(fd, &sdb)) { - log_dbg("Bad read from fd %d.", fd); - continue; - } - - len = shm_du_buff_len(sdb); - - if (shm_du_buff_head_alloc(sdb, ETH_HEADER_TOT_SIZE) - == NULL) { - log_dbg("Failed to allocate header."); - ipcp_sdb_release(sdb); - continue; - } - - pthread_rwlock_rdlock(ð_data.flows_lock); -#if defined(BUILD_ETH_DIX) - deid = eth_data.fd_to_ef[fd].r_eid; -#elif defined(BUILD_ETH_LLC) - dsap = reverse_bits(eth_data.fd_to_ef[fd].r_sap); - ssap = reverse_bits(eth_data.fd_to_ef[fd].sap); -#endif - memcpy(r_addr, - eth_data.fd_to_ef[fd].r_addr, - MAC_SIZE); - - pthread_rwlock_unlock(ð_data.flows_lock); - - 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)) - log_dbg("Failed to send frame."); - ipcp_sdb_release(sdb); - } - } - - pthread_cleanup_pop(true); - - return (void *) 1; -} - -#ifdef __linux__ -static int open_netlink_socket(void) -{ - struct sockaddr_nl sa; - int fd; - - memset(&sa, 0, sizeof(sa)); - sa.nl_family = AF_NETLINK; - sa.nl_pid = getpid(); - sa.nl_groups = RTMGRP_LINK; - - fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); - if (fd < 0) - return -1; - - if (bind(fd, (struct sockaddr *) &sa, sizeof(sa))) { - close(fd); - return -1; - } - - return fd; -} - -static void change_flows_state(bool up) -{ - int i; - uint32_t flags; - - pthread_rwlock_rdlock(ð_data.flows_lock); - -#if defined(BUILD_ETH_DIX) - for (i = 0; i < SYS_MAX_FLOWS; ++i) - if (eth_data.fd_to_ef[i].r_eid != -1) { - fccntl(i, FLOWGFLAGS, &flags); - if (up) - fccntl(i, FLOWSFLAGS, flags & ~FLOWFDOWN); - else - fccntl(i, FLOWSFLAGS, flags | FLOWFDOWN); - } -#elif defined(BUILD_ETH_LLC) - for (i = 0; i < MAX_SAPS; i++) - if (eth_data.ef_to_fd[i] != -1) { - fccntl(eth_data.ef_to_fd[i], FLOWGFLAGS, &flags); - if (up) - fccntl(eth_data.ef_to_fd[i], - FLOWSFLAGS, flags & ~FLOWFDOWN); - else - fccntl(eth_data.ef_to_fd[i], - FLOWSFLAGS, flags | FLOWFDOWN); - } -#endif - - pthread_rwlock_unlock(ð_data.flows_lock); -} - -static void * eth_ipcp_if_monitor(void * o) -{ - int fd; - int status; - char buf[4096]; - struct iovec iov = {buf, sizeof(buf)}; - struct sockaddr_nl snl; - struct msghdr msg = {(void *) &snl, sizeof(snl), - &iov, 1, NULL, 0, 0}; - struct nlmsghdr * h; - struct ifinfomsg * ifi; - - (void ) o; - - fd = open_netlink_socket(); - if (fd < 0) { - log_err("Failed to open socket."); - return (void *) -1; - } - - pthread_cleanup_push(__cleanup_close_ptr, &fd); - - while (true) { - status = recvmsg(fd, &msg, 0); - if (status < 0) - continue; - - for (h = (struct nlmsghdr *) buf; - NLMSG_OK(h, (unsigned int) status); - h = NLMSG_NEXT(h, status)) { - - /* Finish reading */ - if (h->nlmsg_type == NLMSG_DONE) - break; - - /* Message is some kind of error */ - if (h->nlmsg_type == NLMSG_ERROR) - continue; - - /* Only interested in link up/down */ - if (h->nlmsg_type != RTM_NEWLINK) - continue; - - ifi = NLMSG_DATA(h); - - /* Not our interface */ - if (ifi->ifi_index != eth_data.if_idx) - continue; - - if (ifi->ifi_flags & IFF_UP) { - change_flows_state(true); - log_dbg("Interface up."); - } else { - change_flows_state(false); - log_dbg("Interface down."); - } - } - } - - pthread_cleanup_pop(true); - - return (void *) 0; -} -#endif - -#if defined (HAVE_BPF) && !defined(HAVE_NETMAP) -static int open_bpf_device(void) -{ - char dev[32]; - size_t i = 0; - - for (i = 0; i < BPF_DEV_MAX; i++) { - int fd = -1; - - snprintf(dev, sizeof(dev), "/dev/bpf%zu", i); - - fd = open(dev, O_RDWR); - if (fd > -1) - return fd; - } - - return -1; -} -#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__) - 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); - - ipcpi.dir_hash_algo = (enum hash_algo) conf->layer_info.dir_hash_algo; - strcpy(ipcpi.layer_name, conf->layer_info.name); - - if (strlen(conf->eth.dev) >= IFNAMSIZ) { - log_err("Invalid device name: %s.", conf->eth.dev); - return -1; - } - - memset(&ifr, 0, sizeof(ifr)); - strcpy(ifr.ifr_name, conf->eth.dev); - -#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 defined(__FreeBSD__) || defined(__APPLE__) - if (getifaddrs(&ifaddr) < 0) { - log_err("Could not get interfaces."); - return -1; - } - - for (ifa = ifaddr, idx = 0; ifa != NULL; ifa = ifa->ifa_next, ++idx) { - if (strcmp(ifa->ifa_name, conf->eth.dev)) - continue; - log_dbg("Interface %s found.", conf->eth.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; - } - - freeifaddrs(ifaddr); - - if (ifa == NULL) { - log_err("Interface not found."); - return -1; - } - -#elif defined(__linux__) - skfd = socket(AF_UNIX, SOCK_STREAM, 0); - if (skfd < 0) { - log_err("Failed to open socket."); - return -1; - } - - if (ioctl(skfd, SIOCGIFMTU, &ifr)) { - log_err("Failed to get MTU."); - close(skfd); - return -1; - } - - log_dbg("Device MTU is %d.", ifr.ifr_mtu); - - eth_data.mtu = MIN((int) ETH_MTU_MAX, ifr.ifr_mtu); - if (memcmp(conf->eth.dev, "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->eth.dev); - if (idx == 0) { - log_err("Failed to retrieve interface index."); - return -1; - } - eth_data.if_idx = idx; -#endif /* __FreeBSD__ */ - -#if defined(HAVE_NETMAP) - strcpy(ifn, "netmap:"); - strcat(ifn, conf->eth.dev); - - eth_data.nmd = nm_open(ifn, NULL, 0, NULL); - if (eth_data.nmd == NULL) { - log_err("Failed to open netmap device."); - return -1; - } - - memset(ð_data.poll_in, 0, sizeof(eth_data.poll_in)); - memset(ð_data.poll_out, 0, sizeof(eth_data.poll_out)); - - eth_data.poll_in.fd = NETMAP_FD(eth_data.nmd); - eth_data.poll_in.events = POLLIN; - eth_data.poll_out.fd = NETMAP_FD(eth_data.nmd); - eth_data.poll_out.events = POLLOUT; - - log_info("Using netmap device."); -#elif defined(HAVE_BPF) /* !HAVE_NETMAP */ - eth_data.bpf = open_bpf_device(); - if (eth_data.bpf < 0) { - log_err("Failed to open bpf device."); - return -1; - } - - ioctl(eth_data.bpf, BIOCGBLEN, &blen); - if (BPF_BLEN < blen) { - log_err("BPF buffer too small (is: %ld must be: %d).", - BPF_BLEN, blen); - goto fail_device; - } - - if (ioctl(eth_data.bpf, BIOCSETIF, &ifr) < 0) { - log_err("Failed to set interface."); - goto fail_device; - } - - if (ioctl(eth_data.bpf, BIOCSHDRCMPLT, &enable) < 0) { - log_err("Failed to set BIOCSHDRCMPLT."); - goto fail_device; - } - - if (ioctl(eth_data.bpf, BIOCSSEESENT, &disable) < 0) { - log_err("Failed to set BIOCSSEESENT."); - goto fail_device; - } - - if (ioctl(eth_data.bpf, BIOCIMMEDIATE, &enable) < 0) { - log_err("Failed to set BIOCIMMEDIATE."); - goto fail_device; - } - - log_info("Using Berkeley Packet Filter."); -#elif defined(HAVE_RAW_SOCKETS) - 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); - eth_data.device.sll_halen = MAC_SIZE; - eth_data.device.sll_protocol = htons(ETH_P_ALL); - - #if defined (BUILD_ETH_DIX) - eth_data.s_fd = socket(AF_PACKET, SOCK_RAW, eth_data.ethertype); - #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."); - - if (eth_data.s_fd < 0) { - log_err("Failed to create socket."); - goto fail_socket; - } - - flags = fcntl(eth_data.s_fd, F_GETFL, 0); - if (flags < 0) { - log_err("Failed to get flags."); - goto fail_device; - } - - if (fcntl(eth_data.s_fd, F_SETFL, flags | O_NONBLOCK)) { - log_err("Failed to set socket non-blocking."); - goto fail_device; - } - - #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 - - if (bind(eth_data.s_fd, (struct sockaddr *) ð_data.device, - sizeof(eth_data.device)) < 0) { - log_err("Failed to bind socket to interface."); - goto fail_device; - } -#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_device; - } -#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)) { - 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)) { - 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->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); - } - idx = 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); - } - pthread_cancel(eth_data.mgmt_handler); - pthread_join(eth_data.mgmt_handler, NULL); - fail_mgmt_handler: -#if defined(__linux__) - pthread_cancel(eth_data.if_monitor); - pthread_join(eth_data.if_monitor, NULL); -#endif -#if defined(__linux__) || !defined(HAVE_NETMAP) - fail_device: -#endif -#if defined(HAVE_NETMAP) - nm_close(eth_data.nmd); -#elif defined(HAVE_BPF) - close(eth_data.bpf); -#elif defined(HAVE_RAW_SOCKETS) - close(eth_data.s_fd); -#endif - fail_socket: - return -1; -} - -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_FMT32 " to local registry.", - HASH_VAL32(hash)); - return -1; - } - - return 0; -} - -static int eth_ipcp_unreg(const uint8_t * hash) -{ - shim_data_reg_del_entry(eth_data.shim_data, hash); - - return 0; -} - -static int eth_ipcp_query(const uint8_t * hash) -{ - uint8_t r_addr[MAC_SIZE]; - struct timespec timeout = TIMESPEC_INIT_MS(NAME_QUERY_TIMEO); - struct dir_query * query; - int ret; - uint8_t * buf; - struct mgmt_msg * msg; - size_t len; - - if (shim_data_dir_has(eth_data.shim_data, hash)) - return 0; - - len = sizeof(*msg) + ipcp_dir_hash_len(); - - buf = malloc(len + ETH_HEADER_TOT_SIZE); - if (buf == NULL) - return -1; - - msg = (struct mgmt_msg *) (buf + ETH_HEADER_TOT_SIZE); - msg->code = NAME_QUERY_REQ; - - memcpy(msg + 1, hash, ipcp_dir_hash_len()); - - memset(r_addr, 0xff, MAC_SIZE); - - query = shim_data_dir_query_create(eth_data.shim_data, hash); - if (query == NULL) { - free(buf); - return -1; - } - - if (eth_ipcp_send_frame(r_addr, -#if defined(BUILD_ETH_DIX) - MGMT_EID, -#elif defined(BUILD_ETH_LLC) - reverse_bits(MGMT_SAP), - reverse_bits(MGMT_SAP), -#endif - buf, len)) { - log_err("Failed to send management frame."); - shim_data_dir_query_destroy(eth_data.shim_data, query); - free(buf); - return -1; - } - - free(buf); - - ret = shim_data_dir_query_wait(query, &timeout); - - shim_data_dir_query_destroy(eth_data.shim_data, query); - - return ret; -} - -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; - - assert(hash); - - if (!shim_data_dir_has(eth_data.shim_data, hash)) { - log_err("Destination "HASH_FMT32 "unreachable.", - HASH_VAL32(hash)); - return -1; - } - addr = shim_data_dir_get_addr(eth_data.shim_data, hash); - - pthread_rwlock_wrlock(ð_data.flows_lock); -#ifdef BUILD_ETH_LLC - 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); - - if (eth_ipcp_alloc(r_addr, -#if defined(BUILD_ETH_DIX) - fd, -#elif defined(BUILD_ETH_LLC) - ssap, -#endif - hash, - qs, - 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_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 buffer_t * data) -{ -#if defined(BUILD_ETH_DIX) - uint16_t r_eid; -#elif defined(BUILD_ETH_LLC) - uint8_t ssap; - uint8_t r_sap; -#endif - uint8_t r_addr[MAC_SIZE]; - - if (ipcp_wait_flow_resp(fd) < 0) { - log_err("Failed to wait for flow response."); - return -1; - } - - pthread_rwlock_wrlock(ð_data.flows_lock); -#if defined(BUILD_ETH_DIX) - r_eid = eth_data.fd_to_ef[fd].r_eid; -#elif defined(BUILD_ETH_LLC) - 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; - r_sap = eth_data.fd_to_ef[fd].r_sap; - eth_data.ef_to_fd[ssap] = fd; -#endif - memcpy(r_addr, eth_data.fd_to_ef[fd].r_addr, MAC_SIZE); - - pthread_rwlock_unlock(ð_data.flows_lock); - - if (eth_ipcp_alloc_resp(r_addr, -#if defined(BUILD_ETH_DIX) - fd, r_eid, -#elif defined(BUILD_ETH_LLC) - ssap, r_sap, -#endif - response, - 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_LLC) - log_dbg("Assigned SAP %d for fd %d.", ssap, fd); -#endif - return 0; -} - -static int eth_ipcp_flow_dealloc(int fd) -{ -#ifdef BUILD_ETH_LLC - uint8_t sap; -#endif - ipcp_flow_fini(fd); - - fset_del(eth_data.np1_flows, fd); - - pthread_rwlock_wrlock(ð_data.flows_lock); - -#if defined(BUILD_ETH_DIX) - eth_data.fd_to_ef[fd].r_eid = -1; -#elif defined BUILD_ETH_LLC - sap = eth_data.fd_to_ef[fd].sap; - bmp_release(eth_data.saps, sap); - eth_data.fd_to_ef[fd].sap = -1; - eth_data.fd_to_ef[fd].r_sap = -1; - eth_data.ef_to_fd[sap] = -1; -#endif - memset(ð_data.fd_to_ef[fd].r_addr, 0, MAC_SIZE); - - pthread_rwlock_unlock(ð_data.flows_lock); - - ipcp_flow_dealloc(fd); - - return 0; -} - -static struct ipcp_ops eth_ops = { - .ipcp_bootstrap = eth_ipcp_bootstrap, - .ipcp_enroll = NULL, - .ipcp_connect = NULL, - .ipcp_disconnect = NULL, - .ipcp_reg = eth_ipcp_reg, - .ipcp_unreg = eth_ipcp_unreg, - .ipcp_query = eth_ipcp_query, - .ipcp_flow_alloc = eth_ipcp_flow_alloc, - .ipcp_flow_join = NULL, - .ipcp_flow_alloc_resp = eth_ipcp_flow_alloc_resp, - .ipcp_flow_dealloc = eth_ipcp_flow_dealloc -}; - -int main(int argc, - char * argv[]) -{ - int i; - - if (eth_data_init() < 0) { -#if defined(BUILD_ETH_DIX) - log_err("Failed to init eth-llc data."); -#elif defined(BUILD_ETH_LLC) - log_err("Failed to init eth-dix data."); -#endif - goto fail_data_init; - } - - if (ipcp_init(argc, argv, ð_ops, THIS_TYPE) < 0) { - log_err("Failed to initialize IPCP."); - goto fail_init; - } - - if (ipcp_start() < 0) { - log_err("Failed to start IPCP."); - goto fail_start; - } - - ipcp_sigwait(); - - if (ipcp_get_state() == IPCP_SHUTDOWN) { - for (i = 0; i < IPCP_ETH_WR_THR; ++i) - pthread_cancel(eth_data.packet_writer[i]); - for (i = 0; i < IPCP_ETH_RD_THR; ++i) - pthread_cancel(eth_data.packet_reader[i]); - - pthread_cancel(eth_data.mgmt_handler); -#ifdef __linux__ - pthread_cancel(eth_data.if_monitor); -#endif - for (i = 0; i < IPCP_ETH_WR_THR; ++i) - pthread_join(eth_data.packet_writer[i], NULL); - for (i = 0; i < IPCP_ETH_RD_THR; ++i) - pthread_join(eth_data.packet_reader[i], NULL); - - pthread_join(eth_data.mgmt_handler, NULL); -#ifdef __linux__ - pthread_join(eth_data.if_monitor, NULL); -#endif - } - - ipcp_stop(); - - ipcp_fini(); - - eth_data_fini(); - - exit(EXIT_SUCCESS); - - fail_start: - ipcp_fini(); - fail_init: - eth_data_fini(); - fail_data_init: - exit(EXIT_FAILURE); -} diff --git a/src/ipcpd/ipcp.c b/src/ipcpd/ipcp.c index 966c4920..b25b7d04 100644 --- a/src/ipcpd/ipcp.c +++ b/src/ipcpd/ipcp.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * IPC process main loop * @@ -36,6 +36,7 @@ #define OUROBOROS_PREFIX "ipcpd/ipcp" #define IPCP_INFO "info" #define ALLOC_TIMEOUT 50 /* ms */ +#define HAS_POA (ipcpd.type != IPCP_LOCAL) #include <ouroboros/bitmap.h> #include <ouroboros/dev.h> @@ -52,7 +53,9 @@ #include <ouroboros/utils.h> #include "ipcp.h" +#include "np1.h" +#include <arpa/inet.h> #include <signal.h> #include <string.h> #include <sys/socket.h> @@ -64,13 +67,75 @@ #endif #endif -char * info[LAYER_NAME_SIZE + 1] = { - "_state", - "_type", - "_layer", - NULL +#ifndef CLOCK_REALTIME_COARSE +#define CLOCK_REALTIME_COARSE CLOCK_REALTIME +#endif + +/* Fits "eth <dev> 0x<type>", the longest PoA rendering. */ +/* Matches src/tools/irm/irm_utils.h; keep in sync. */ +#define POA_STRLEN (DEV_NAME_SIZE + 11) + +static char * ipcp_type_str[] = { + "local", + "unicast", + "broadcast" }; +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; @@ -79,9 +144,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; @@ -105,11 +199,18 @@ 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) { - char * entry; + const char * entry; if (len < LAYER_NAME_SIZE + 2) /* trailing \n */ return 0; @@ -132,18 +233,12 @@ 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) - strcpy(buf, "eth-llc\n"); - else if (ipcpi.type == IPCP_ETH_DIX) - strcpy(buf, "eth-dix\n"); - else if (ipcpi.type == IPCP_UDP) - strcpy(buf, "udp\n"); else strcpy(buf, "bug\n"); } @@ -153,7 +248,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'; } @@ -165,12 +260,11 @@ 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; @@ -183,12 +277,11 @@ static int ipcp_rib_readdir(char *** buf) return i; fail_dup: - while (i > 0) - free((*buf)[--i]); - fail: + while (i-- > 0) + free((*buf)[i]); free(*buf); - - return -1; + fail_entries: + return -ENOMEM; } static int ipcp_rib_getattr(const char * path, @@ -217,11 +310,10 @@ static void * acceptloop(void * o) (void) o; - while (ipcp_get_state() != IPCP_SHUTDOWN && - ipcp_get_state() != IPCP_NULL) { + while (ipcp_get_state() != IPCP_SHUTDOWN) { struct cmd * cmd; - csockfd = accept(ipcpi.sockfd, 0, 0); + csockfd = accept(ipcpd.sockfd, 0, 0); if (csockfd < 0) continue; @@ -249,13 +341,13 @@ 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; @@ -264,6 +356,7 @@ static void * acceptloop(void * o) int ipcp_wait_flow_req_arr(const uint8_t * dst, qosspec_t qs, time_t mpl, + uint32_t mtu, const buffer_t * data) { struct timespec ts = TIMESPEC_INIT_MS(ALLOC_TIMEOUT); @@ -276,34 +369,34 @@ int ipcp_wait_flow_req_arr(const uint8_t * dst, clock_gettime(PTHREAD_COND_CLOCK, &abstime); - pthread_mutex_lock(&ipcpi.alloc_lock); + pthread_mutex_lock(&ipcpd.alloc_lock); - while (ipcpi.alloc_id != -1 && ipcp_get_state() == IPCP_OPERATIONAL) { + while (ipcpd.alloc_id != -1 && ipcp_get_state() == IPCP_OPERATIONAL) { ts_add(&abstime, &ts, &abstime); - pthread_cond_timedwait(&ipcpi.alloc_cond, - &ipcpi.alloc_lock, + pthread_cond_timedwait(&ipcpd.alloc_cond, + &ipcpd.alloc_lock, &abstime); } if (ipcp_get_state() != IPCP_OPERATIONAL) { - pthread_mutex_unlock(&ipcpi.alloc_lock); + pthread_mutex_unlock(&ipcpd.alloc_lock); log_err("Won't allocate over non-operational IPCP."); return -EIPCPSTATE; } - assert(ipcpi.alloc_id == -1); + assert(ipcpd.alloc_id == -1); - fd = ipcp_flow_req_arr(&hash, qs, mpl, data); + fd = ipcp_flow_req_arr(&hash, qs, mpl, mtu, data); if (fd < 0) { - pthread_mutex_unlock(&ipcpi.alloc_lock); + pthread_mutex_unlock(&ipcpd.alloc_lock); log_err("Failed to get fd for flow."); return fd; } - ipcpi.alloc_id = fd; - pthread_cond_broadcast(&ipcpi.alloc_cond); + ipcpd.alloc_id = fd; + pthread_cond_broadcast(&ipcpd.alloc_cond); - pthread_mutex_unlock(&ipcpi.alloc_lock); + pthread_mutex_unlock(&ipcpd.alloc_lock); return fd; @@ -311,31 +404,31 @@ int ipcp_wait_flow_req_arr(const uint8_t * dst, int ipcp_wait_flow_resp(const int fd) { - struct timespec ts = TIMESPEC_INIT_MS(ALLOC_TIMEOUT); - struct timespec abstime; + struct timespec ts = TIMESPEC_INIT_MS(ALLOC_TIMEOUT); + struct timespec abstime; clock_gettime(PTHREAD_COND_CLOCK, &abstime); - pthread_mutex_lock(&ipcpi.alloc_lock); + pthread_mutex_lock(&ipcpd.alloc_lock); - while (ipcpi.alloc_id != fd && ipcp_get_state() == IPCP_OPERATIONAL) { + while (ipcpd.alloc_id != fd && ipcp_get_state() == IPCP_OPERATIONAL) { ts_add(&abstime, &ts, &abstime); - pthread_cond_timedwait(&ipcpi.alloc_cond, - &ipcpi.alloc_lock, + pthread_cond_timedwait(&ipcpd.alloc_cond, + &ipcpd.alloc_lock, &abstime); } if (ipcp_get_state() != IPCP_OPERATIONAL) { - pthread_mutex_unlock(&ipcpi.alloc_lock); + pthread_mutex_unlock(&ipcpd.alloc_lock); return -1; } - assert(ipcpi.alloc_id == fd); + assert(ipcpd.alloc_id == fd); - ipcpi.alloc_id = -1; - pthread_cond_broadcast(&ipcpi.alloc_cond); + ipcpd.alloc_id = -1; + pthread_cond_broadcast(&ipcpd.alloc_cond); - pthread_mutex_unlock(&ipcpi.alloc_lock); + pthread_mutex_unlock(&ipcpd.alloc_lock); return 0; } @@ -349,76 +442,260 @@ static void free_msg(void * o) static void do_bootstrap(ipcp_config_msg_t * conf_msg, ipcp_msg_t * ret_msg) { - struct ipcp_config conf; + struct ipcp_config conf; + struct layer_info * info; log_info("Bootstrapping..."); - if (ipcpi.ops->ipcp_bootstrap == NULL) { - log_err("Bootstrap unsupported."); + if (ipcpd.ops->ipcp_bootstrap == NULL) { + log_err("Failed to Bootstrap: operation unsupported."); ret_msg->result = -ENOTSUP; - goto finish; + return; } - if (ipcp_get_state() != IPCP_INIT) { - log_err("IPCP in wrong state."); + 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; - goto finish; + return; } conf = ipcp_config_msg_to_s(conf_msg); - ret_msg->result = ipcpi.ops->ipcp_bootstrap(&conf); - if (ret_msg->result == 0) { - ret_msg->layer_info = layer_info_s_to_msg(&conf.layer_info); - ipcp_set_state(IPCP_OPERATIONAL); + switch(conf.type) { /* FIXED algorithms */ + case IPCP_BROADCAST: + conf.layer_info.dir_hash_algo = DIR_HASH_SHA3_256; + break; + default: + break; } - finish: - log_info("Finished bootstrapping: %d.", ret_msg->result); + + 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; + if (poa_set_layer(info->name) < 0) + log_warn("Failed to set layer name for PoA queries."); + 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) +static void do_enroll(const char * dst, + const poa_addr_msg_t * peer, + ipcp_msg_t * ret_msg) { struct layer_info info; + struct poa_addr addr; + struct poa_addr * pa = NULL; log_info("Enrolling with %s...", dst); - if (ipcpi.ops->ipcp_enroll == NULL) { - log_err("Enroll unsupported."); + if (ipcpd.ops->ipcp_enroll == NULL) { + log_err("Failed to enroll: operation unsupported."); ret_msg->result = -ENOTSUP; - goto finish; + return; } - if (ipcp_get_state() != IPCP_INIT) { - log_err("IPCP in wrong state."); + 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; - goto finish; + return; + } + + if (peer != NULL) { + addr = poa_addr_msg_to_s(peer); + if (addr.type == POA_INVALID || addr.type == POA_UDP) { + ret_msg->result = -EINVAL; + return; + } + pa = &addr; + } + + ret_msg->result = ipcpd.ops->ipcp_enroll(dst, pa, &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; + if (poa_set_layer(info.name) < 0) + log_warn("Failed to set layer name for PoA queries."); + 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()); +} + +/* Bounded so one oversized reply cannot be built; 64 is generous. */ +#define POA_LIST_MAX 64 + +static void do_list_poas(ipcp_msg_t * ret_msg) +{ + struct poa_spec eps[POA_LIST_MAX]; + ssize_t n; + ssize_t i; + + if (ipcpd.type != IPCP_UNICAST && ipcpd.type != IPCP_BROADCAST) { + ret_msg->result = -ENOTSUP; + return; } - ret_msg->result = ipcpi.ops->ipcp_enroll(dst, &info); - if (ret_msg->result == 0) { - ret_msg->layer_info = layer_info_s_to_msg(&info); - ipcp_set_state(IPCP_OPERATIONAL); + n = poa_list(eps, POA_LIST_MAX); + if (n < 0) { + ret_msg->result = (int) n; + return; + } + + if (n > POA_LIST_MAX) { + log_warn("Listing %d of %zd PoAs.", POA_LIST_MAX, n); + + n = POA_LIST_MAX; + } + + if (n == 0) { + ret_msg->result = 0; + return; + } + + ret_msg->poas = malloc(n * sizeof(*ret_msg->poas)); + if (ret_msg->poas == NULL) { + ret_msg->result = -ENOMEM; + return; + } + + for (i = 0; i < n; i++) { + ret_msg->poas[i] = poa_spec_s_to_msg(&eps[i]); + if (ret_msg->poas[i] == NULL) { + ret_msg->result = -ENOMEM; + return; + } + ret_msg->n_poas++; + } + + ret_msg->result = 0; +} + +/* Names a PoA the way "irm ipcp poa list" prints it. */ +/* Matches src/tools/irm/irm_utils.c; keep in sync. */ +static void poa_spec_str(const struct poa_spec * poa, + char * buf, + size_t len) +{ + char addr[INET6_ADDRSTRLEN]; + + switch (poa->type) { + case POA_UDP4: + if (inet_ntop(AF_INET, &poa->udp4.ip_addr, + addr, sizeof(addr)) == NULL) + break; + + snprintf(buf, len, "udp4 %s:%u", addr, poa->udp4.port); + return; + case POA_UDP6: + if (inet_ntop(AF_INET6, &poa->udp6.ip_addr, + addr, sizeof(addr)) == NULL) + break; + + snprintf(buf, len, "udp6 [%s]:%u", addr, poa->udp6.port); + return; + case POA_ETH: + snprintf(buf, len, "eth %s 0x%04X", poa->eth.dev, + poa->eth.ethertype); + return; + default: + break; + } + + snprintf(buf, len, "(unknown)"); +} + +static void do_attach(poa_spec_msg_t * msg, + ipcp_msg_t * ret_msg) +{ + struct poa_spec poa; + char str[POA_STRLEN + 1]; + + poa = poa_spec_msg_to_s(msg); + + poa_spec_str(&poa, str, sizeof(str)); + + ret_msg->result = poa_attach(&poa); + if (ret_msg->result < 0) { + log_err("Failed to attach %s.", str); + return; + } + + log_info("Attached %s.", str); +} + +static void do_detach(poa_spec_msg_t * msg, + ipcp_msg_t * ret_msg) +{ + struct poa_spec poa; + char str[POA_STRLEN + 1]; + + poa = poa_spec_msg_to_s(msg); + + poa_spec_str(&poa, str, sizeof(str)); + + ret_msg->result = poa_detach(&poa); + if (ret_msg->result < 0) { + log_err("Failed to detach %s.", str); + return; } - finish: - log_info("Finished enrolling with %s: %d.", dst, ret_msg->result); + + log_info("Detached %s.", str); } -static void do_connect(const char * dst, - const char * comp, - qosspec_t qs, - ipcp_msg_t * ret_msg) +static void do_connect(const char * dst, + const char * comp, + qosspec_t qs, + const poa_addr_msg_t * peer, + ipcp_msg_t * ret_msg) { + struct poa_addr addr; + struct poa_addr * pa = NULL; + log_info("Connecting %s to %s...", comp, dst); - if (ipcpi.ops->ipcp_connect == NULL) { - log_err("Connect unsupported."); + if (ipcpd.ops->ipcp_connect == NULL) { + log_err("Failed to connect: operation unsupported."); ret_msg->result = -ENOTSUP; - goto finish; + return; } - ret_msg->result = ipcpi.ops->ipcp_connect(dst, comp, qs); - finish: - log_info("Finished connecting: %d.", ret_msg->result); + if (peer != NULL) { + addr = poa_addr_msg_to_s(peer); + if (addr.type == POA_INVALID || addr.type == POA_UDP) { + ret_msg->result = -EINVAL; + return; + } + pa = &addr; + } + + ret_msg->result = ipcpd.ops->ipcp_connect(dst, comp, qs, pa); + + log_info("Finished connecting."); } static void do_disconnect(const char * dst, @@ -427,17 +704,15 @@ static void do_disconnect(const char * dst, { log_info("Disconnecting %s from %s...", comp, dst); - if (ipcpi.ops->ipcp_disconnect == NULL) { - log_err("Disconnect unsupported."); + if (ipcpd.ops->ipcp_disconnect == NULL) { + log_err("Failed to disconnect: operation unsupported."); ret_msg->result = -ENOTSUP; - goto finish; + return; } - ret_msg->result = ipcpi.ops->ipcp_disconnect(dst, comp); + ret_msg->result = ipcpd.ops->ipcp_disconnect(dst, comp); - finish: - log_info("Finished disconnecting %s from %s: %d.", - comp, dst, ret_msg->result); + log_info("Finished disconnecting %s from %s.", comp, dst); } static void do_reg(const uint8_t * hash, @@ -446,16 +721,15 @@ static void do_reg(const uint8_t * hash, log_info("Registering " HASH_FMT32 "...", HASH_VAL32(hash)); - if (ipcpi.ops->ipcp_reg == NULL) { - log_err("Registration unsupported."); + if (ipcpd.ops->ipcp_reg == NULL) { + log_err("Failed to register: operation unsupported."); ret_msg->result = -ENOTSUP; - goto finish; + return; } - ret_msg->result = ipcpi.ops->ipcp_reg(hash); - finish: - log_info("Finished registering " HASH_FMT32 " : %d.", - HASH_VAL32(hash), ret_msg->result); + 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, @@ -463,16 +737,15 @@ static void do_unreg(const uint8_t * hash, { log_info("Unregistering " HASH_FMT32 "...", HASH_VAL32(hash)); - if (ipcpi.ops->ipcp_unreg == NULL) { - log_err("Unregistration unsupported."); + if (ipcpd.ops->ipcp_unreg == NULL) { + log_err("Failed to unregister: operation unsupported."); ret_msg->result = -ENOTSUP; - goto finish; + return; } - ret_msg->result = ipcpi.ops->ipcp_unreg(hash); - finish: - log_info("Finished unregistering " HASH_FMT32 ": %d.", - HASH_VAL32(hash), ret_msg->result); + 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, @@ -480,19 +753,21 @@ static void do_query(const uint8_t * hash, { /* TODO: Log this operation when IRMd has internal caches. */ - if (ipcpi.ops->ipcp_query == NULL) { - log_err("Directory query unsupported."); + 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_err("IPCP in wrong state."); + 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 = ipcpi.ops->ipcp_query(hash); + ret_msg->result = ipcpd.ops->ipcp_query(hash); } static void do_flow_alloc(pid_t pid, @@ -500,23 +775,27 @@ static void do_flow_alloc(pid_t pid, uint8_t * dst, qosspec_t qs, const buffer_t * data, + uid_t uid, ipcp_msg_t * ret_msg) { - int fd; + int fd; + struct ssm_pool * pool = NULL; log_info("Allocating flow %d for %d to " HASH_FMT32 ".", flow_id, pid, HASH_VAL32(dst)); - if (ipcpi.ops->ipcp_flow_alloc == NULL) { - log_err("Flow allocation unsupported."); + if (ipcpd.ops->ipcp_flow_alloc == NULL) { + log_err("Flow allocation failed: operation unsupported."); ret_msg->result = -ENOTSUP; - goto finish; + return; } if (ipcp_get_state() != IPCP_OPERATIONAL) { - log_err("IPCP in wrong state."); + 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; - goto finish; + return; } fd = np1_flow_alloc(pid, flow_id); @@ -524,143 +803,240 @@ static void do_flow_alloc(pid_t pid, log_err("Failed allocating n + 1 fd on flow_id %d: %d", flow_id, fd); ret_msg->result = -EFLOWDOWN; - goto finish; + 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; + } } - ret_msg->result = ipcpi.ops->ipcp_flow_alloc(fd, dst, qs, data); - finish: - log_info("Finished allocating flow %d to " HASH_FMT32 ": %d.", - flow_id, HASH_VAL32(dst), ret_msg->result); + 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, - qosspec_t qs, + uid_t uid, ipcp_msg_t * ret_msg) { - int fd; + int fd; + struct ssm_pool * pool = NULL; log_info("Joining layer " HASH_FMT32 ".", HASH_VAL32(dst)); - if (ipcpi.ops->ipcp_flow_join == NULL) { - log_err("Broadcast unsupported."); + if (ipcpd.ops->ipcp_flow_join == NULL) { + log_err("Failed to join: operation unsupported."); ret_msg->result = -ENOTSUP; - goto finish; + return; } if (ipcp_get_state() != IPCP_OPERATIONAL) { - log_err("IPCP in wrong state."); + 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; - goto finish; + 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; - goto finish; + return; } - ret_msg->result = ipcpi.ops->ipcp_flow_join(fd, dst, qs); - finish: + 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)); } +/* + * The IRMd says whether the flow is on a PoA, as it may not be known + * here yet. PoA flows answer without the IPCP type's flow machinery. + */ static void do_flow_alloc_resp(int resp, int flow_id, + uid_t uid, + bool is_poa, const buffer_t * data, ipcp_msg_t * ret_msg) { - int fd = -1; + int fd = -1; + struct ssm_pool * pool = NULL; log_info("Responding %d to alloc on flow_id %d.", resp, flow_id); - if (ipcpi.ops->ipcp_flow_alloc_resp == NULL) { - log_err("Flow_alloc_resp unsupported."); + 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; + } + + if (is_poa) { + ret_msg->result = poa_flow_alloc_resp(flow_id, resp, data); + log_info("Finished responding %d on PoA flow %d.", + ret_msg->result, flow_id); + return; + } + + if (ipcpd.ops->ipcp_flow_alloc_resp == NULL) { + log_err("Failed to respond on flow %d: operation unsupported.", + flow_id); ret_msg->result = -ENOTSUP; - goto finish; + return; } - if (ipcp_get_state() != IPCP_OPERATIONAL) { - log_err("IPCP in wrong state."); - ret_msg->result = -EIPCPSTATE; - goto finish; + 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 (resp == 0) { - fd = np1_flow_resp(flow_id); - if (fd < 0) { - log_warn("Flow_id %d is not known.", flow_id); - ret_msg->result = -1; - goto finish; + 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; } } - ret_msg->result = ipcpi.ops->ipcp_flow_alloc_resp(fd, resp, data); - finish: - log_info("Finished responding to allocation request: %d", + 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, + bool is_poa, ipcp_msg_t * ret_msg) { int fd; log_info("Deallocating flow %d.", flow_id); - if (ipcpi.ops->ipcp_flow_dealloc == NULL) { - log_err("Flow deallocation unsupported."); + 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; + } + + if (is_poa) { + ret_msg->result = poa_flow_dealloc(flow_id); + + log_info("Finished deallocating PoA flow %d.", flow_id); + return; + } + + if (ipcpd.ops->ipcp_flow_dealloc == NULL) { + log_err("Failed to dealloc: operation unsupported."); + ret_msg->result = -ENOTSUP; - goto finish; + 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 do_flow_update(int flow_id, + const buffer_t * data, + bool is_poa, + ipcp_msg_t * ret_msg) +{ + int fd; + if (ipcp_get_state() != IPCP_OPERATIONAL) { - log_err("IPCP in wrong state."); ret_msg->result = -EIPCPSTATE; - goto finish; + return; } - fd = np1_flow_dealloc(flow_id, timeo_sec); + if (is_poa) { + ret_msg->result = poa_flow_update(flow_id, data); + return; + } + + if (ipcpd.ops->ipcp_flow_update == NULL) { + log_err("Failed to update flow: operation unsupported."); + + ret_msg->result = -ENOTSUP; + return; + } + + fd = np1_flow_fd(flow_id); if (fd < 0) { - log_warn("Could not deallocate flow_id %d.", flow_id); + log_warn("Flow update for unknown flow_id %d.", flow_id); ret_msg->result = -1; - goto finish; + return; } - ret_msg->result = ipcpi.ops->ipcp_flow_dealloc(fd); - finish: - log_info("Finished deallocating flow %d: %d.", - flow_id, ret_msg->result); + ret_msg->result = ipcpd.ops->ipcp_flow_update(fd, data); } static void * mainloop(void * o) { - int sfd; - buffer_t buffer; - ipcp_msg_t * msg; + int sfd; + buffer_t buffer; + ipcp_msg_t * msg; (void) o; while (true) { - ipcp_msg_t ret_msg = IPCP_MSG__INIT; - qosspec_t qs; - struct cmd * cmd; - buffer_t data; + 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); @@ -671,11 +1047,12 @@ static void * mainloop(void * o) free(cmd); if (msg == NULL) { + log_err("Failed to unpack command message."); close(sfd); continue; } - tpm_dec(ipcpi.tpm); + tpm_begin_work(ipcpd.tpm); pthread_cleanup_push(__cleanup_close_ptr, &sfd); pthread_cleanup_push(free_msg, msg); @@ -687,11 +1064,23 @@ static void * mainloop(void * o) do_bootstrap(msg->conf, &ret_msg); break; case IPCP_MSG_CODE__IPCP_ENROLL: - do_enroll(msg->dst, &ret_msg); + do_enroll(msg->dst, msg->peer, &ret_msg); + break; + case IPCP_MSG_CODE__IPCP_ATTACH: + assert(HAS_POA); + do_attach(msg->poa, &ret_msg); + break; + case IPCP_MSG_CODE__IPCP_DETACH: + assert(HAS_POA); + do_detach(msg->poa, &ret_msg); + break; + case IPCP_MSG_CODE__IPCP_LIST_POAS: + do_list_poas(&ret_msg); break; case IPCP_MSG_CODE__IPCP_CONNECT: qs = qos_spec_msg_to_s(msg->qosspec); - do_connect(msg->dst, msg->comp, qs, &ret_msg); + do_connect(msg->dst, msg->comp, qs, msg->peer, + &ret_msg); break; case IPCP_MSG_CODE__IPCP_DISCONNECT: do_disconnect(msg->dst, msg->comp, &ret_msg); @@ -717,13 +1106,12 @@ static void * mainloop(void * o) qs = qos_spec_msg_to_s(msg->qosspec); do_flow_alloc(msg->pid, msg->flow_id, msg->hash.data, qs, - &data, &ret_msg); + &data, msg->uid, &ret_msg); break; case IPCP_MSG_CODE__IPCP_FLOW_JOIN: assert(msg->hash.len == ipcp_dir_hash_len()); - qs = qos_spec_msg_to_s(msg->qosspec); do_flow_join(msg->pid, msg->flow_id, - msg->hash.data, qs, &ret_msg); + msg->hash.data, msg->uid, &ret_msg); break; case IPCP_MSG_CODE__IPCP_FLOW_ALLOC_RESP: assert(msg->pk.len > 0 ? msg->pk.data != NULL @@ -731,10 +1119,20 @@ static void * mainloop(void * o) data.len = msg->pk.len; data.data = msg->pk.data; do_flow_alloc_resp(msg->response, msg->flow_id, + msg->uid, msg->is_poa, &data, &ret_msg); break; case IPCP_MSG_CODE__IPCP_FLOW_DEALLOC: - do_flow_dealloc(msg->flow_id, msg->timeo_sec, &ret_msg); + do_flow_dealloc(msg->flow_id, msg->timeo_sec, + msg->is_poa, &ret_msg); + break; + case IPCP_MSG_CODE__IPCP_FLOW_UPDATE: + assert(msg->pk.len > 0 ? msg->pk.data != NULL + : msg->pk.data == NULL); + data.len = msg->pk.len; + data.data = msg->pk.data; + do_flow_update(msg->flow_id, &data, msg->is_poa, + &ret_msg); break; default: ret_msg.result = -1; @@ -749,7 +1147,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; } @@ -757,7 +1155,7 @@ 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; } @@ -766,16 +1164,16 @@ static void * mainloop(void * o) if (ret_msg.layer_info != NULL) layer_info_msg__free_unpacked(ret_msg.layer_info, NULL); - pthread_cleanup_push(__cleanup_close_ptr, &sfd); 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"); - pthread_cleanup_pop(true); - 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; @@ -794,10 +1192,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) @@ -813,71 +1211,69 @@ 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; } @@ -887,49 +1283,62 @@ int ipcp_init(int argc, goto fail_rib_reg; } - ipcpi.tpm = tpm_create(IPCP_MIN_THREADS, IPCP_ADD_THREADS, + if (poa_init(ipcpd.name) < 0) { + log_err("Failed to initialize PoAs."); + goto fail_poa_init; + } + + list_head_init(&ipcpd.cmds); + + ipcpd.tpm = tpm_create(IPCP_MIN_THREADS, IPCP_ADD_THREADS, mainloop, NULL); - if (ipcpi.tpm == NULL) { + if (ipcpd.tpm == NULL) { log_err("Failed to create threadpool manager."); goto fail_tpm_create; } - list_head_init(&ipcpi.cmds); + ipcpd.alloc_id = -1; - ipcpi.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: + poa_fini(); + fail_poa_init: 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; } +/* Enrolment runs over a PoA, so poa_start() precedes any RPC. */ int ipcp_start(void) { sigset_t sigset; @@ -944,20 +1353,27 @@ int ipcp_start(void) pthread_sigmask(SIG_BLOCK, &sigset, NULL); info.pid = getpid(); - info.type = ipcpi.type; - strcpy(info.name, ipcpi.name); - info.state = IPCP_OPERATIONAL; + info.type = ipcpd.type; + strcpy(info.name, ipcpd.name); + info.state = IPCP_BOOT; + + ipcp_set_state(IPCP_BOOT); - if (tpm_start(ipcpi.tpm)) + if (poa_start() < 0) { + log_err("Failed to start PoAs."); + goto fail_poa_start; + } + + 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."); goto fail_acceptor; } - info.state = IPCP_OPERATIONAL; - if (ipcp_create_r(&info)) { log_err("Failed to notify IRMd we are initialized."); goto fail_create_r; @@ -966,14 +1382,15 @@ int ipcp_start(void) return 0; fail_create_r: - pthread_cancel(ipcpi.acceptor); - pthread_join(ipcpi.acceptor, NULL); + pthread_cancel(ipcpd.acceptor); + pthread_join(ipcpd.acceptor, NULL); fail_acceptor: - tpm_stop(ipcpi.tpm); + tpm_stop(ipcpd.tpm); fail_tpm_start: - tpm_destroy(ipcpi.tpm); - ipcp_set_state(IPCP_NULL); - info.state = IPCP_NULL; + poa_stop(); + fail_poa_start: + tpm_destroy(ipcpd.tpm); + ipcp_set_state(IPCP_INIT); ipcp_create_r(&info); return -1; } @@ -993,7 +1410,7 @@ void ipcp_sigwait(void) 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) { @@ -1007,7 +1424,7 @@ void ipcp_sigwait(void) #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: @@ -1017,9 +1434,9 @@ void ipcp_sigwait(void) 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); @@ -1038,58 +1455,66 @@ void ipcp_stop(void) { log_info("IPCP %d shutting down.", getpid()); - pthread_cancel(ipcpi.acceptor); - pthread_join(ipcpi.acceptor, NULL); + pthread_cancel(ipcpd.acceptor); + pthread_join(ipcpd.acceptor, NULL); + + tpm_stop(ipcpd.tpm); + + poa_stop(); - tpm_stop(ipcpi.tpm); + ipcp_set_state(IPCP_INIT); } void ipcp_fini(void) { - tpm_destroy(ipcpi.tpm); + tpm_destroy(ipcpd.tpm); + + poa_fini(); 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(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; } diff --git a/src/ipcpd/ipcp.h b/src/ipcpd/ipcp.h index aab490c7..e0aab291 100644 --- a/src/ipcpd/ipcp.h +++ b/src/ipcpd/ipcp.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * IPC process structure * @@ -34,15 +34,20 @@ #include <pthread.h> #include <time.h> +#define ipcp_dir_hash_strlen() (ipcp_dir_hash_len() * 2) + +/* Attach or release one flow PoA on a unicast or broadcast IPCP. */ 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); + int (* ipcp_enroll)(const char * dst, + const struct poa_addr * addr, + struct layer_info * info); - int (* ipcp_connect)(const char * dst, - const char * component, - qosspec_t qs); + int (* ipcp_connect)(const char * dst, + const char * component, + qosspec_t qs, + const struct poa_addr * addr); int (* ipcp_disconnect)(const char * dst, const char * component); @@ -59,52 +64,17 @@ struct ipcp_ops { 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 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[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; -} ipcpi; + int (* ipcp_flow_update)(int fd, + const buffer_t * data); +}; int ipcp_init(int argc, char ** argv, @@ -119,22 +89,32 @@ 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_parse_arg(int argc, - char * argv[]); - /* Helper functions to handle races during flow allocation */ int ipcp_wait_flow_req_arr(const uint8_t * dst, qosspec_t qs, time_t mpl, + uint32_t mtu, const buffer_t * data); int ipcp_wait_flow_resp(const int fd); + /* 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 10fd0120..af433d01 100644 --- a/src/ipcpd/local/CMakeLists.txt +++ b/src/ipcpd/local/CMakeLists.txt @@ -1,34 +1,18 @@ -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}) +add_executable(${IPCP_LOCAL_TARGET} + main.c + reg.c + ${IPCP_SOURCES} +) -include_directories(${CURRENT_SOURCE_PARENT_DIR}) -include_directories(${CURRENT_BINARY_PARENT_DIR}) +target_include_directories(${IPCP_LOCAL_TARGET} PRIVATE ${IPCP_INCLUDE_DIRS}) +target_link_libraries(${IPCP_LOCAL_TARGET} PRIVATE ouroboros-dev) -include_directories(${CMAKE_SOURCE_DIR}/include) -include_directories(${CMAKE_BINARY_DIR}/include) +ouroboros_target_debug_definitions(${IPCP_LOCAL_TARGET}) -set(IPCP_LOCAL_TARGET ipcpd-local CACHE INTERNAL "") -set(IPCP_LOCAL_MPL 2 CACHE STRING - "Default maximum packet lifetime for the Ethernet IPCPs, in seconds") +if(IPCP_LOCAL_POLLING) + target_compile_definitions(${IPCP_LOCAL_TARGET} PRIVATE CONFIG_IPCP_LOCAL_POLLING) +endif() -set(LOCAL_SOURCES - # Add source files here - ${CMAKE_CURRENT_SOURCE_DIR}/main.c) - -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 160e07e0..69eac8a6 100644 --- a/src/ipcpd/local/main.c +++ b/src/ipcpd/local/main.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Local IPC process * @@ -38,9 +38,11 @@ #include <ouroboros/ipcp.h> #include <ouroboros/ipcp-dev.h> #include <ouroboros/local-dev.h> +#include <ouroboros/np1_flow.h> #include "ipcp.h" -#include "shim-data.h" +#include "np1.h" +#include "reg.h" #include <string.h> #include <stdlib.h> @@ -50,17 +52,15 @@ #define THIS_TYPE IPCP_LOCAL -struct ipcp ipcpi; - struct { - struct shim_data * shim_data; + struct reg * reg; - int in_out[SYS_MAX_FLOWS]; - fset_t * flows; - fqueue_t * fq; + int in_out[SYS_MAX_FLOWS]; + fset_t * flows; + fqueue_t * fq; - pthread_rwlock_t lock; - pthread_t packet_loop; + pthread_rwlock_t lock; + pthread_t packet_loop; } local_data; static int local_data_init(void) @@ -77,9 +77,9 @@ static int local_data_init(void) if (local_data.fq == NULL) goto fail_fqueue; - local_data.shim_data = shim_data_create(); - if (local_data.shim_data == NULL) - goto fail_shim_data; + local_data.reg = reg_create(); + if (local_data.reg == NULL) + goto fail_reg; if (pthread_rwlock_init(&local_data.lock, NULL) < 0) goto fail_rwlock_init; @@ -87,8 +87,8 @@ static int local_data_init(void) return 0; fail_rwlock_init: - shim_data_destroy(local_data.shim_data); - fail_shim_data: + reg_destroy(local_data.reg); + fail_reg: fqueue_destroy(local_data.fq); fail_fqueue: fset_destroy(local_data.flows); @@ -98,54 +98,61 @@ static int local_data_init(void) static void local_data_fini(void){ pthread_rwlock_destroy(&local_data.lock); - shim_data_destroy(local_data.shim_data); + reg_destroy(local_data.reg); fqueue_destroy(local_data.fq); fset_destroy(local_data.flows); } 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 local_ipcp_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 = (enum hash_algo) conf->layer_info.dir_hash_algo; - strcpy(ipcpi.layer_name,conf->layer_info.name); + (void) conf; if (pthread_create(&local_data.packet_loop, NULL, local_ipcp_packet_loop, NULL)) { @@ -159,7 +166,7 @@ static int local_ipcp_bootstrap(const struct ipcp_config * conf) static int local_ipcp_reg(const uint8_t * hash) { - if (shim_data_reg_add_entry(local_data.shim_data, hash)) { + if (reg_add(local_data.reg, hash) < 0) { log_err("Failed to add " HASH_FMT32 " to local registry.", HASH_VAL32(hash)); return -1; @@ -170,7 +177,7 @@ static int local_ipcp_reg(const uint8_t * hash) static int local_ipcp_unreg(const uint8_t * hash) { - shim_data_reg_del_entry(local_data.shim_data, hash); + reg_del(local_data.reg, hash); log_info("Unregistered " HASH_FMT32 ".", HASH_VAL32(hash)); @@ -181,7 +188,7 @@ static int local_ipcp_query(const uint8_t * hash) { int ret; - ret = (shim_data_reg_has(local_data.shim_data, hash) ? 0 : -1); + ret = (reg_has(local_data.reg, hash) ? 0 : -1); return ret; } @@ -197,7 +204,8 @@ static int local_ipcp_flow_alloc(int fd, HASH_VAL32(dst), fd); assert(dst); - out_fd = ipcp_wait_flow_req_arr(dst, qs, IPCP_LOCAL_MPL, data); + out_fd = ipcp_wait_flow_req_arr(dst, qs, IPCP_LOCAL_MPL, + IPCP_LOCAL_MTU, data); if (out_fd < 0) { log_dbg("Flow allocation failed: %d", out_fd); return -1; @@ -230,15 +238,6 @@ static int local_ipcp_flow_alloc_resp(int fd, return -1; } - 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; - pthread_rwlock_unlock(&local_data.lock); - return 0; - } - pthread_rwlock_rdlock(&local_data.lock); out_fd = local_data.in_out[fd]; @@ -257,9 +256,17 @@ static int local_ipcp_flow_alloc_resp(int fd, return -1; } + if (response < 0) { + ipcp_flow_alloc_reply(out_fd, response, mpl, + IPCP_LOCAL_MTU, data); + log_info("Flow allocation rejected, fds (%d, %d).", out_fd, fd); + return 0; + } + fset_add(local_data.flows, fd); - if (ipcp_flow_alloc_reply(out_fd, response, mpl, data) < 0) { + if (ipcp_flow_alloc_reply(out_fd, response, mpl, + IPCP_LOCAL_MTU, data) < 0) { log_err("Failed to reply to allocation"); fset_del(local_data.flows, fd); return -1; @@ -291,6 +298,38 @@ static int local_ipcp_flow_dealloc(int fd) return 0; } +/* Loopback relay: deliver the update back to the peer end (same IRMd). */ +static int local_ipcp_flow_update(int fd, + const buffer_t * data) +{ + int out_fd; + int out_flow_id; + + 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("Flow update on fd %d with no peer.", fd); + return -1; + } + + out_flow_id = np1_flow_id(out_fd); + if (out_flow_id < 0) { + log_err("No flow_id for peer fd %d.", out_fd); + return -1; + } + + if (ipcp_flow_update_arr(out_flow_id, data) < 0) { + log_err("Failed to relay flow update to fd %d.", out_fd); + return -1; + } + + return 0; +} + static struct ipcp_ops local_ops = { .ipcp_bootstrap = local_ipcp_bootstrap, .ipcp_enroll = NULL, @@ -302,7 +341,8 @@ static struct ipcp_ops local_ops = { .ipcp_flow_alloc = local_ipcp_flow_alloc, .ipcp_flow_join = NULL, .ipcp_flow_alloc_resp = local_ipcp_flow_alloc_resp, - .ipcp_flow_dealloc = local_ipcp_flow_dealloc + .ipcp_flow_dealloc = local_ipcp_flow_dealloc, + .ipcp_flow_update = local_ipcp_flow_update }; int main(int argc, diff --git a/src/ipcpd/local/reg.c b/src/ipcpd/local/reg.c new file mode 100644 index 00000000..36f19b16 --- /dev/null +++ b/src/ipcpd/local/reg.c @@ -0,0 +1,217 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Names registered with the local IPCP + * + * 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 + +#define OUROBOROS_PREFIX "local-reg" + +#include <ouroboros/hash.h> +#include <ouroboros/list.h> +#include <ouroboros/logs.h> + +#include "reg.h" +#include "ipcp.h" + +#include <assert.h> +#include <pthread.h> +#include <stdlib.h> +#include <string.h> + +struct reg_entry { + struct list_head list; + uint8_t * hash; +}; + +struct reg { + struct list_head names; + pthread_rwlock_t lock; +}; + +static struct reg_entry * reg_entry_create(uint8_t * hash) +{ + struct reg_entry * entry; + + entry = malloc(sizeof(*entry)); + if (entry == NULL) + return NULL; + + list_head_init(&entry->list); + + entry->hash = hash; + + return entry; +} + +static void reg_entry_destroy(struct reg_entry * entry) +{ + assert(entry); + + free(entry->hash); + free(entry); +} + +/* Call with the lock held. */ +static struct reg_entry * reg_find(struct reg * reg, + const uint8_t * hash) +{ + struct list_head * p; + + list_for_each(p, ®->names) { + struct reg_entry * e; + + e = list_entry(p, struct reg_entry, list); + if (memcmp(e->hash, hash, ipcp_dir_hash_len()) == 0) + return e; + } + + return NULL; +} + +struct reg * reg_create(void) +{ + struct reg * reg; + + reg = malloc(sizeof(*reg)); + if (reg == NULL) + goto fail_malloc; + + list_head_init(®->names); + + if (pthread_rwlock_init(®->lock, NULL) < 0) + goto fail_lock; + + return reg; + + fail_lock: + free(reg); + fail_malloc: + return NULL; +} + +void reg_destroy(struct reg * reg) +{ + if (reg == NULL) + return; + + pthread_rwlock_wrlock(®->lock); + + while (!list_is_empty(®->names)) { + struct reg_entry * e; + + e = list_first_entry(®->names, struct reg_entry, list); + + list_del(&e->list); + + reg_entry_destroy(e); + } + + pthread_rwlock_unlock(®->lock); + + pthread_rwlock_destroy(®->lock); + + free(reg); +} + +int reg_add(struct reg * reg, + const uint8_t * hash) +{ + struct reg_entry * entry; + uint8_t * dup; + + assert(reg); + assert(hash); + + pthread_rwlock_wrlock(®->lock); + + if (reg_find(reg, hash) != NULL) { + pthread_rwlock_unlock(®->lock); + log_dbg(HASH_FMT32 " was already registered.", + HASH_VAL32(hash)); + return 0; + } + + dup = ipcp_hash_dup(hash); + if (dup == NULL) + goto fail; + + entry = reg_entry_create(dup); + if (entry == NULL) { + free(dup); + goto fail; + } + + list_add(&entry->list, ®->names); + + pthread_rwlock_unlock(®->lock); + + return 0; + + fail: + pthread_rwlock_unlock(®->lock); + return -1; +} + +int reg_del(struct reg * reg, + const uint8_t * hash) +{ + struct reg_entry * e; + + if (reg == NULL) + return -1; + + pthread_rwlock_wrlock(®->lock); + + e = reg_find(reg, hash); + if (e == NULL) { + pthread_rwlock_unlock(®->lock); + return 0; /* nothing to do */ + } + + list_del(&e->list); + + pthread_rwlock_unlock(®->lock); + + reg_entry_destroy(e); + + return 0; +} + +bool reg_has(struct reg * reg, + const uint8_t * hash) +{ + bool ret; + + assert(reg); + assert(hash); + + pthread_rwlock_rdlock(®->lock); + + ret = reg_find(reg, hash) != NULL; + + pthread_rwlock_unlock(®->lock); + + return ret; +} diff --git a/src/ipcpd/local/reg.h b/src/ipcpd/local/reg.h new file mode 100644 index 00000000..2c6142bb --- /dev/null +++ b/src/ipcpd/local/reg.h @@ -0,0 +1,45 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Names registered with the local IPCP + * + * 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_LOCAL_REG_H +#define OUROBOROS_IPCPD_LOCAL_REG_H + +#include <stdbool.h> +#include <stdint.h> + +/* The hashes of the names registered with this IPCP. */ +struct reg; + +struct reg * reg_create(void); + +void reg_destroy(struct reg * reg); + +int reg_add(struct reg * reg, + const uint8_t * hash); + +int reg_del(struct reg * reg, + const uint8_t * hash); + +bool reg_has(struct reg * reg, + const uint8_t * hash); + +#endif /* OUROBOROS_IPCPD_LOCAL_REG_H */ diff --git a/src/ipcpd/eth/llc.c b/src/ipcpd/np1.h index c900dcab..b7792cb9 100644 --- a/src/ipcpd/eth/llc.c +++ b/src/ipcpd/np1.h @@ -1,7 +1,7 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * - * IPC processes over Ethernet - LLC + * N+1 flow pool tracking for IPCPs * * Dimitri Staessens <dimitri@ouroboros.rocks> * Sander Vrijders <sander@ouroboros.rocks> @@ -20,7 +20,22 @@ * Foundation, Inc., http://www.fsf.org/about/contact/. */ -#define BUILD_ETH_LLC -#define OUROBOROS_PREFIX "ipcpd/eth-llc" +#ifndef OUROBOROS_IPCPD_NP1_H +#define OUROBOROS_IPCPD_NP1_H -#include "eth.c" +#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 deleted file mode 100644 index 1fac63ac..00000000 --- a/src/ipcpd/shim-data.c +++ /dev/null @@ -1,584 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2024 - * - * IPC process 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/. - */ - -#if defined(__linux__) || defined(__CYGWIN__) -#define _DEFAULT_SOURCE -#else -#define _POSIX_C_SOURCE 200112L -#endif - -#include "config.h" - -#define OUROBOROS_PREFIX "shim-data" - -#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 <assert.h> -#include <stdlib.h> -#include <string.h> - -struct reg_entry { - struct list_head list; - uint8_t * hash; -}; - -struct dir_entry { - struct list_head list; - uint8_t * hash; - uint64_t addr; -}; - -static void destroy_dir_query(struct dir_query * query) -{ - assert(query); - - pthread_mutex_lock(&query->lock); - - switch (query->state) { - case QUERY_INIT: - query->state = QUERY_DONE; - break; - case QUERY_PENDING: - query->state = QUERY_DESTROY; - pthread_cond_broadcast(&query->cond); - break; - case QUERY_RESPONSE: - case QUERY_DONE: - break; - case QUERY_DESTROY: - pthread_mutex_unlock(&query->lock); - return; - default: - assert(false); - return; - } - - while (query->state != QUERY_DONE) - pthread_cond_wait(&query->cond, &query->lock); - - pthread_mutex_unlock(&query->lock); - - pthread_cond_destroy(&query->cond); - pthread_mutex_destroy(&query->lock); - - free(query->hash); - free(query); -} - -static struct reg_entry * reg_entry_create(uint8_t * hash) -{ - struct reg_entry * entry = malloc(sizeof(*entry)); - if (entry == NULL) - return NULL; - - assert(hash); - - entry->hash = hash; - - return entry; -} - -static void reg_entry_destroy(struct reg_entry * entry) -{ - assert(entry); - - if (entry->hash != NULL) - free(entry->hash); - - free(entry); -} - -static struct dir_entry * dir_entry_create(uint8_t * hash, - uint64_t addr) -{ - struct dir_entry * entry = malloc(sizeof(*entry)); - if (entry == NULL) - return NULL; - - assert(hash); - - entry->addr = addr; - entry->hash = hash; - - return entry; -} - -static void dir_entry_destroy(struct dir_entry * entry) -{ - assert(entry); - - if (entry->hash != NULL) - free(entry->hash); - - free(entry); -} - -struct shim_data * shim_data_create(void) -{ - struct shim_data * sd; - - sd = malloc(sizeof(*sd)); - if (sd == NULL) - return NULL; - - /* init the lists */ - list_head_init(&sd->registry); - list_head_init(&sd->directory); - list_head_init(&sd->dir_queries); - - /* init the locks */ - 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) -{ - struct list_head * h; - struct list_head * t; - - assert(data); - - list_for_each_safe(h, t, &data->registry) { - struct reg_entry * e = list_entry(h, struct reg_entry, list); - list_del(&e->list); - reg_entry_destroy(e); - } -} - -static void clear_directory(struct shim_data * data) -{ - struct list_head * h; - struct list_head * t; - - assert(data); - - list_for_each_safe(h, t, &data->directory) { - struct dir_entry * e = list_entry(h, struct dir_entry, list); - list_del(&e->list); - dir_entry_destroy(e); - } -} - -static void clear_dir_queries(struct shim_data * data) -{ - struct list_head * h; - struct list_head * t; - - assert(data); - - list_for_each_safe(h, t, &data->dir_queries) { - struct dir_query * e = list_entry(h, struct dir_query, next); - list_del(&e->next); - destroy_dir_query(e); - } -} - -void shim_data_destroy(struct shim_data * data) -{ - if (data == NULL) - return; - - /* clear the lists */ - pthread_rwlock_wrlock(&data->reg_lock); - clear_registry(data); - pthread_rwlock_unlock(&data->reg_lock); - - pthread_rwlock_wrlock(&data->dir_lock); - clear_directory(data); - pthread_rwlock_unlock(&data->dir_lock); - - pthread_mutex_lock(&data->dir_queries_lock); - clear_dir_queries(data); - pthread_mutex_unlock(&data->dir_queries_lock); - - pthread_rwlock_destroy(&data->dir_lock); - pthread_rwlock_destroy(&data->reg_lock); - pthread_mutex_destroy(&data->dir_queries_lock); - - free(data); -} - -static struct reg_entry * find_reg_entry_by_hash(struct shim_data * data, - const uint8_t * hash) -{ - struct list_head * h; - - assert(data); - assert(hash); - - list_for_each(h, &data->registry) { - struct reg_entry * e = list_entry(h, struct reg_entry, list); - if (!memcmp(e->hash, hash, ipcp_dir_hash_len())) - return e; - } - - return NULL; -} - -static struct dir_entry * find_dir_entry(struct shim_data * data, - const uint8_t * hash, - uint64_t 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())) - return e; - } - - return NULL; -} - -static struct dir_entry * find_dir_entry_any(struct shim_data * data, - const uint8_t * hash) -{ - struct list_head * h; - list_for_each(h, &data->directory) { - struct dir_entry * e = list_entry(h, struct dir_entry, list); - if (!memcmp(e->hash, hash, ipcp_dir_hash_len())) - return e; - } - - return NULL; -} - -int shim_data_reg_add_entry(struct shim_data * data, - const uint8_t * hash) -{ - struct reg_entry * entry; - uint8_t * hash_dup; - - assert(data); - assert(hash); - - pthread_rwlock_wrlock(&data->reg_lock); - - if (find_reg_entry_by_hash(data, hash)) { - pthread_rwlock_unlock(&data->reg_lock); - log_dbg(HASH_FMT32 " was already in the directory.", - HASH_VAL32(hash)); - return 0; - } - - hash_dup = ipcp_hash_dup(hash); - if (hash_dup == NULL) { - pthread_rwlock_unlock(&data->reg_lock); - return -1; - } - - entry = reg_entry_create(hash_dup); - if (entry == NULL) { - pthread_rwlock_unlock(&data->reg_lock); - return -1; - } - - list_add(&entry->list, &data->registry); - - pthread_rwlock_unlock(&data->reg_lock); - - return 0; -} - -int shim_data_reg_del_entry(struct shim_data * data, - const uint8_t * hash) -{ - struct reg_entry * e; - if (data == NULL) - return -1; - - pthread_rwlock_wrlock(&data->reg_lock); - - e = find_reg_entry_by_hash(data, hash); - if (e == NULL) { - pthread_rwlock_unlock(&data->reg_lock); - return 0; /* nothing to do */ - } - - list_del(&e->list); - - pthread_rwlock_unlock(&data->reg_lock); - - reg_entry_destroy(e); - - return 0; -} - -bool shim_data_reg_has(struct shim_data * data, - const uint8_t * hash) -{ - bool ret = false; - - assert(data); - assert(hash); - - pthread_rwlock_rdlock(&data->reg_lock); - - ret = (find_reg_entry_by_hash(data, hash) != NULL); - - pthread_rwlock_unlock(&data->reg_lock); - - return ret; -} - -int shim_data_dir_add_entry(struct shim_data * data, - const uint8_t * hash, - uint64_t addr) -{ - struct dir_entry * entry; - uint8_t * entry_hash; - - assert(data); - assert(hash); - - pthread_rwlock_wrlock(&data->dir_lock); - - if (find_dir_entry(data, hash, addr) != NULL) { - pthread_rwlock_unlock(&data->dir_lock); - return -1; - } - - entry_hash = ipcp_hash_dup(hash); - if (entry_hash == NULL) { - pthread_rwlock_unlock(&data->dir_lock); - return -1; - } - - entry = dir_entry_create(entry_hash, addr); - if (entry == NULL) { - pthread_rwlock_unlock(&data->dir_lock); - return -1; - } - - list_add(&entry->list,&data->directory); - - pthread_rwlock_unlock(&data->dir_lock); - - return 0; -} - -int shim_data_dir_del_entry(struct shim_data * data, - const uint8_t * hash, - uint64_t addr) -{ - struct dir_entry * e; - if (data == NULL) - return -1; - - pthread_rwlock_wrlock(&data->dir_lock); - - e = find_dir_entry(data, hash, addr); - if (e == NULL) { - pthread_rwlock_unlock(&data->dir_lock); - return 0; /* nothing to do */ - } - - list_del(&e->list); - - pthread_rwlock_unlock(&data->dir_lock); - - dir_entry_destroy(e); - - return 0; -} - -bool shim_data_dir_has(struct shim_data * data, - const uint8_t * hash) -{ - bool ret = false; - - pthread_rwlock_rdlock(&data->dir_lock); - - ret = (find_dir_entry_any(data, hash) != NULL); - - pthread_rwlock_unlock(&data->dir_lock); - - return ret; -} - -uint64_t shim_data_dir_get_addr(struct shim_data * data, - const uint8_t * hash) -{ - struct dir_entry * entry; - uint64_t addr; - - pthread_rwlock_rdlock(&data->dir_lock); - - entry = find_dir_entry_any(data, hash); - if (entry == NULL) { - pthread_rwlock_unlock(&data->dir_lock); - log_warn("No address for " HASH_FMT32 ".", HASH_VAL32(hash)); - return 0; /* undefined behaviour, 0 may be a valid address */ - } - - addr = entry->addr; - - pthread_rwlock_unlock(&data->dir_lock); - - return addr; -} - -struct dir_query * shim_data_dir_query_create(struct shim_data * data, - const uint8_t * hash) -{ - struct dir_query * query; - pthread_condattr_t cattr; - - query = malloc(sizeof(*query)); - if (query == NULL) - return NULL; - - query->hash = ipcp_hash_dup(hash); - if (query->hash == NULL) { - free(query); - return NULL; - } - - query->state = QUERY_INIT; - - pthread_condattr_init(&cattr); -#ifndef __APPLE__ - pthread_condattr_setclock(&cattr, PTHREAD_COND_CLOCK); -#endif - pthread_cond_init(&query->cond, &cattr); - pthread_mutex_init(&query->lock, NULL); - - list_head_init(&query->next); - - pthread_mutex_lock(&data->dir_queries_lock); - list_add(&query->next, &data->dir_queries); - pthread_mutex_unlock(&data->dir_queries_lock); - - return query; -} - -void shim_data_dir_query_respond(struct shim_data * data, - const uint8_t * hash) -{ - struct dir_query * e = NULL; - struct list_head * pos; - bool found = false; - - pthread_mutex_lock(&data->dir_queries_lock); - - list_for_each(pos, &data->dir_queries) { - e = list_entry(pos, struct dir_query, next); - - if (memcmp(e->hash, hash, ipcp_dir_hash_len()) == 0) { - found = true; - break; - } - } - - if (!found) { - pthread_mutex_unlock(&data->dir_queries_lock); - return; - } - - pthread_mutex_lock(&e->lock); - - if (e->state != QUERY_PENDING) { - pthread_mutex_unlock(&e->lock); - pthread_mutex_unlock(&data->dir_queries_lock); - return; - } - - e->state = QUERY_RESPONSE; - pthread_cond_broadcast(&e->cond); - - while (e->state == QUERY_RESPONSE) - pthread_cond_wait(&e->cond, &e->lock); - - pthread_mutex_unlock(&e->lock); - - pthread_mutex_unlock(&data->dir_queries_lock); -} - -void shim_data_dir_query_destroy(struct shim_data * data, - struct dir_query * query) -{ - pthread_mutex_lock(&data->dir_queries_lock); - - list_del(&query->next); - destroy_dir_query(query); - - pthread_mutex_unlock(&data->dir_queries_lock); -} - -int shim_data_dir_query_wait(struct dir_query * query, - const struct timespec * timeout) -{ - struct timespec abstime; - int ret = 0; - - assert(query); - assert(timeout); - - clock_gettime(PTHREAD_COND_CLOCK, &abstime); - ts_add(&abstime, timeout, &abstime); - - pthread_mutex_lock(&query->lock); - - if (query->state != QUERY_INIT) { - pthread_mutex_unlock(&query->lock); - return -EINVAL; - } - - query->state = QUERY_PENDING; - - while (query->state == QUERY_PENDING && ret != -ETIMEDOUT) - ret = -pthread_cond_timedwait(&query->cond, - &query->lock, - &abstime); - - if (query->state == QUERY_DESTROY) - ret = -1; - - query->state = QUERY_DONE; - pthread_cond_broadcast(&query->cond); - - pthread_mutex_unlock(&query->lock); - - return ret; -} diff --git a/src/ipcpd/shim-data.h b/src/ipcpd/shim-data.h deleted file mode 100644 index 372b4ea7..00000000 --- a/src/ipcpd/shim-data.h +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2024 - * - * Utitilies for building IPC 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_IPCPD_IPCP_DATA_H -#define OUROBOROS_IPCPD_IPCP_DATA_H - -#include <ouroboros/list.h> - -#include <sys/types.h> -#include <pthread.h> -#include <stdint.h> - -enum dir_query_state { - QUERY_INIT = 0, - QUERY_PENDING, - QUERY_RESPONSE, - QUERY_DONE, - QUERY_DESTROY -}; - -struct dir_query { - struct list_head next; - uint8_t * hash; - enum dir_query_state state; - - pthread_mutex_t lock; - pthread_cond_t cond; -}; - -struct shim_data { - struct list_head registry; - pthread_rwlock_t reg_lock; - - struct list_head directory; - pthread_rwlock_t dir_lock; - - struct list_head dir_queries; - pthread_mutex_t dir_queries_lock; -}; - -struct shim_data * shim_data_create(void); - -void shim_data_destroy(struct shim_data * data); - -int shim_data_reg_add_entry(struct shim_data * data, - const uint8_t * hash); - -int shim_data_reg_del_entry(struct shim_data * data, - const uint8_t * hash); - -bool shim_data_reg_has(struct shim_data * data, - const uint8_t * hash); - -int shim_data_dir_add_entry(struct shim_data * data, - const uint8_t * hash, - uint64_t addr); - -int shim_data_dir_del_entry(struct shim_data * data, - const uint8_t * hash, - uint64_t 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, - const uint8_t * hash); - -struct dir_query * shim_data_dir_query_create(struct shim_data * data, - const uint8_t * hash); - -void shim_data_dir_query_destroy(struct shim_data * data, - struct dir_query * query); - -void shim_data_dir_query_respond(struct shim_data * data, - const uint8_t * hash); - -int shim_data_dir_query_wait(struct dir_query * query, - const struct timespec * timeout); -#endif /* OUROBOROS_IPCPD_SHIM_DATA_H */ diff --git a/src/ipcpd/udp/CMakeLists.txt b/src/ipcpd/udp/CMakeLists.txt deleted file mode 100644 index 8ae5518e..00000000 --- a/src/ipcpd/udp/CMakeLists.txt +++ /dev/null @@ -1,69 +0,0 @@ -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) - -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") -set(IPCP_UDP_MPL 60 CACHE STRING - "Default maximum packet lifetime for the UDP IPCP, in seconds") - -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}) diff --git a/src/ipcpd/udp/main.c b/src/ipcpd/udp/main.c deleted file mode 100644 index 2e8d84ce..00000000 --- a/src/ipcpd/udp/main.c +++ /dev/null @@ -1,1152 +0,0 @@ -/* - * Ouroboros - Copyright (C) 2016 - 2024 - * - * IPC process over UDP - * - * 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" - -#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 "shim-data.h" - -#include <string.h> -#include <sys/socket.h> -#include <sys/select.h> -#include <arpa/inet.h> -#include <netdb.h> -#include <netinet/in.h> -#include <signal.h> -#include <stdlib.h> -#include <sys/wait.h> -#include <fcntl.h> - -#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 DNS_TTL 86400 - -#define SADDR ((struct sockaddr *) &udp_data.s_saddr) -#define SADDR_SIZE (sizeof(udp_data.s_saddr)) -#define LOCAL_IP (udp_data.s_saddr.sin_addr.s_addr) - -#define MGMT_EID 0 -#define MGMT_FRAME_SIZE (sizeof(struct mgmt_msg)) -#define MGMT_FRAME_BUF_SIZE 2048 - -#ifdef __linux__ -#define SENDTO_FLAGS MSG_CONFIRM -#else -#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; - uint64_t bandwidth; - uint32_t delay; - uint32_t loss; - uint32_t ber; - uint32_t max_gap; - uint32_t timeout; - uint16_t cypher_s; - -} __attribute__((packed)); - -struct mgmt_frame { - struct list_head next; - struct sockaddr_in r_saddr; - uint8_t buf[MGMT_FRAME_BUF_SIZE]; - size_t len; -}; - -/* UDP flow */ -struct uf { - int d_eid; - struct sockaddr_in r_saddr; -}; - -struct { - struct shim_data * shim_data; - - uint32_t dns_addr; - - struct sockaddr_in s_saddr; - int s_fd; - - fset_t * np1_flows; - struct uf fd_to_uf[SYS_MAX_FLOWS]; - pthread_rwlock_t flows_lock; - - pthread_t packet_writer[IPCP_UDP_WR_THR]; - pthread_t packet_reader[IPCP_UDP_RD_THR]; - - /* Handle mgmt frames in a different thread */ - pthread_t mgmt_handler; - pthread_mutex_t mgmt_lock; - pthread_cond_t mgmt_cond; - struct list_head mgmt_frames; -} udp_data; - -static int udp_data_init(void) -{ - int i; - pthread_condattr_t cattr; - - if (pthread_rwlock_init(&udp_data.flows_lock, NULL)) - goto fail_rwlock_init; - - 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)) - goto fail_mgmt_lock; - - for (i = 0; i < SYS_MAX_FLOWS; ++i) - udp_data.fd_to_uf[i].d_eid = -1; - - udp_data.np1_flows = fset_create(); - if (udp_data.np1_flows == NULL) - goto fail_fset; - - udp_data.shim_data = shim_data_create(); - 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: - pthread_mutex_destroy(&udp_data.mgmt_lock); - 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; -} - -static void udp_data_fini(void) -{ - shim_data_destroy(udp_data.shim_data); - - fset_destroy(udp_data.np1_flows); - - pthread_rwlock_destroy(&udp_data.flows_lock); - pthread_cond_destroy(&udp_data.mgmt_cond); - pthread_mutex_destroy(&udp_data.mgmt_lock); -} - -static int udp_ipcp_port_alloc(const struct sockaddr_in * 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(data->len > 0 ? data->data != NULL : data->data == NULL); - - len = sizeof(*msg) + ipcp_dir_hash_len(); - - buf = malloc(len + data->len); - if (buf == NULL) - return -1; - - msg = (struct mgmt_msg *) buf; - msg->eid = hton32(MGMT_EID); - msg->code = FLOW_REQ; - msg->s_eid = hton32(s_eid); - msg->delay = hton32(qs.delay); - msg->bandwidth = hton64(qs.bandwidth); - msg->availability = qs.availability; - msg->loss = hton32(qs.loss); - 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()); - if (data->len > 0) - memcpy(buf + len, data->data, data->len); - - if (sendto(udp_data.s_fd, msg, len + data->len, - SENDTO_FLAGS, - (const struct sockaddr *) r_saddr, sizeof(*r_saddr)) < 0) { - free(buf); - return -1; - } - - free(buf); - - return 0; -} - -static int udp_ipcp_port_alloc_resp(const struct sockaddr_in * r_saddr, - uint32_t s_eid, - uint32_t d_eid, - int8_t response, - const buffer_t * data) -{ - struct mgmt_msg * msg; - - msg = malloc(sizeof(*msg) + data->len); - if (msg == NULL) - return -1; - - msg->eid = hton32(MGMT_EID); - msg->code = FLOW_REPLY; - msg->s_eid = hton32(s_eid); - msg->d_eid = hton32(d_eid); - msg->response = response; - - if (data->len > 0) - memcpy(msg + 1, data->data, data->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); - return -1; - } - - free(msg); - - return 0; -} - -static int udp_ipcp_port_req(struct sockaddr_in * c_saddr, - int d_eid, - const uint8_t * dst, - qosspec_t qs, - const buffer_t * data) -{ - int fd; - - fd = ipcp_wait_flow_req_arr(dst, qs, IPCP_UDP_MPL, data); - if (fd < 0) { - log_err("Could not get new flow from IRMd."); - return -1; - } - - pthread_rwlock_wrlock(&udp_data.flows_lock); - - udp_data.fd_to_uf[fd].r_saddr = *c_saddr; - udp_data.fd_to_uf[fd].d_eid = d_eid; - - pthread_rwlock_unlock(&udp_data.flows_lock); - - log_dbg("Pending allocation request, fd %d, remote eid %d.", - fd, d_eid); - - return 0; -} - -static int udp_ipcp_port_alloc_reply(const struct sockaddr_in * saddr, - uint32_t s_eid, - uint32_t d_eid, - int8_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))) { - pthread_rwlock_unlock(&udp_data.flows_lock); - log_err("Flow allocation reply for %u from wrong source.", - s_eid); - return -1; - } - - if (response == 0) - udp_data.fd_to_uf[s_eid].d_eid = d_eid; - - pthread_rwlock_unlock(&udp_data.flows_lock); - - if (ipcp_flow_alloc_reply(s_eid, response, mpl, data) < 0) { - log_err("Failed to reply to flow allocation."); - return -1; - } - - log_dbg("Flow allocation completed on eids (%d, %d).", - s_eid, d_eid); - - return 0; -} - -static int udp_ipcp_mgmt_frame(const uint8_t * buf, - size_t len, - struct sockaddr_in c_saddr) -{ - struct mgmt_msg * msg; - size_t msg_len; - qosspec_t qs; - buffer_t data; - - msg = (struct mgmt_msg *) buf; - - switch (msg->code) { - case FLOW_REQ: - msg_len = sizeof(*msg) + ipcp_dir_hash_len(); - - 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; - 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); - qs.timeout = ntoh32(msg->timeout); - - return udp_ipcp_port_req(&c_saddr, ntoh32(msg->s_eid), - (uint8_t *) (msg + 1), qs, - &data); - case FLOW_REPLY: - assert(len >= sizeof(*msg)); - - 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, - &data); - default: - log_err("Unknown message received %d.", msg->code); - return -1; - } -} - -static void * udp_ipcp_mgmt_handler(void * o) -{ - (void) o; - - pthread_cleanup_push(__cleanup_mutex_unlock, &udp_data.mgmt_lock); - - while (true) { - struct mgmt_frame * frame; - - pthread_mutex_lock(&udp_data.mgmt_lock); - - while (list_is_empty(&udp_data.mgmt_frames)) - pthread_cond_wait(&udp_data.mgmt_cond, - &udp_data.mgmt_lock); - - frame = list_first_entry((&udp_data.mgmt_frames), - struct mgmt_frame, next); - assert(frame != NULL); - list_del(&frame->next); - - pthread_mutex_unlock(&udp_data.mgmt_lock); - - udp_ipcp_mgmt_frame(frame->buf, frame->len, frame->r_saddr); - - free(frame); - } - - pthread_cleanup_pop(false); - - return (void *) 0; -} - -static void * udp_ipcp_packet_reader(void * o) -{ - uint8_t buf[IPCP_UDP_MAX_PACKET_SIZE]; - uint8_t * data; - ssize_t n; - uint32_t eid; - uint32_t * eid_p; - - (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 shm_du_buff * sdb; - uint8_t * head; - - len = sizeof(r_saddr); - - n = recvfrom(udp_data.s_fd, buf, IPCP_UDP_MAX_PACKET_SIZE, 0, - (struct sockaddr *) &r_saddr, &len); - if (n < 0) - continue; - - if (n == 0) - log_dbg("Got a 0 frame."); - - if ((size_t) n < sizeof(eid)) { - log_dbg("Dropped bad frame."); - continue; - } - - eid = ntoh32(*eid_p); - - /* pass onto mgmt queue */ - if (eid == MGMT_EID) { - if ((size_t) n < MGMT_FRAME_SIZE) { - log_warn("Dropped runt mgmt frame."); - continue; - } - - frame = malloc(sizeof(*frame)); - if (frame == NULL) - continue; - - memcpy(frame->buf, buf, n); - memcpy(&frame->r_saddr, &r_saddr, sizeof(r_saddr)); - frame->len = n; - - pthread_mutex_lock(&udp_data.mgmt_lock); - list_add(&frame->next, &udp_data.mgmt_frames); - pthread_cond_signal(&udp_data.mgmt_cond); - pthread_mutex_unlock(&udp_data.mgmt_lock); - continue; - } - - n-= sizeof(eid); - - if (ipcp_sdb_reserve(&sdb, n)) - continue; - - head = shm_du_buff_head(sdb); - memcpy(head, data, n); - if (np1_flow_write(eid, sdb) < 0) - ipcp_sdb_release(sdb); - } - - return (void *) 0; -} - -static void cleanup_fqueue(void * fq) -{ - fqueue_destroy((fqueue_t *) fq); -} - -static void cleanup_sdb(void * sdb) -{ - ipcp_sdb_release((struct shm_du_buff *) sdb); -} - -static void * udp_ipcp_packet_writer(void * o) -{ - fqueue_t * fq; - - fq = fqueue_create(); - if (fq == NULL) - return (void *) -1; - - (void) o; - - ipcp_lock_to_core(); - - pthread_cleanup_push(cleanup_fqueue, fq); - - while (true) { - struct sockaddr_in saddr; - int eid; - int fd; - fevent(udp_data.np1_flows, fq, NULL); - while ((fd = fqueue_next(fq)) >= 0) { - struct shm_du_buff * sdb; - uint8_t * buf; - uint16_t len; - - if (fqueue_type(fq) != FLOW_PKT) - continue; - - if (np1_flow_read(fd, &sdb)) { - log_dbg("Bad read from fd %d.", fd); - continue; - } - - len = shm_du_buff_len(sdb); - if (len > IPCP_UDP_MAX_PACKET_SIZE) { - log_dbg("Packet length exceeds MTU."); - ipcp_sdb_release(sdb); - continue; - } - - buf = shm_du_buff_head_alloc(sdb, OUR_HEADER_LEN); - if (buf == NULL) { - log_dbg("Failed to allocate header."); - ipcp_sdb_release(sdb); - continue; - } - - pthread_rwlock_rdlock(&udp_data.flows_lock); - - eid = hton32(udp_data.fd_to_uf[fd].d_eid); - saddr = udp_data.fd_to_uf[fd].r_saddr; - - pthread_rwlock_unlock(&udp_data.flows_lock); - - memcpy(buf, &eid, sizeof(eid)); - - pthread_cleanup_push(cleanup_sdb, sdb); - - if (sendto(udp_data.s_fd, buf, len + OUR_HEADER_LEN, - SENDTO_FLAGS, - (const struct sockaddr *) &saddr, - sizeof(saddr)) < 0) - log_err("Failed to send packet."); - - pthread_cleanup_pop(true); - } - } - - pthread_cleanup_pop(true); - - return (void *) 1; -} - -static const char * inet4_ntop(const void * addr, - char * buf) -{ - return inet_ntop(AF_INET, addr, buf, INET_ADDRSTRLEN); -} - -static int udp_ipcp_bootstrap(const struct ipcp_config * conf) -{ - char ipstr[INET_ADDRSTRLEN]; - char dnsstr[INET_ADDRSTRLEN]; - int i = 1; - - assert(conf); - assert(conf->type == THIS_TYPE); - - ipcpi.dir_hash_algo = HASH_MD5; - strcpy(ipcpi.layer_name, conf->layer_info.name); - - if (inet4_ntop(&conf->udp.ip_addr, ipstr) == NULL) { - log_err("Failed to convert IP address."); - return -1; - } - - if (conf->udp.dns_addr != 0) { - if (inet4_ntop(&conf->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."); -#endif - } else { - strcpy(dnsstr, "not set"); - } - - /* UDP listen server */ - udp_data.s_fd = socket(AF_INET, 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->udp.ip_addr; - udp_data.s_saddr.sin_port = htons(conf->udp.port); - - if (bind(udp_data.s_fd, SADDR, SADDR_SIZE) < 0) { - log_err("Couldn't bind to %s:%d. %s.", - ipstr, conf->udp.port, strerror(errno)); - goto fail_bind; - } - - udp_data.dns_addr = conf->udp.dns_addr; - - if (pthread_create(&udp_data.mgmt_handler, NULL, - 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, - 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, - udp_ipcp_packet_writer, NULL)) { - log_err("Failed to create writer thread."); - goto fail_packet_writer; - } - } - - log_dbg("Bootstrapped IPCP over UDP with pid %d.", getpid()); - log_dbg("Bound to IP address %s.", ipstr); - log_dbg("Using port %u.", conf->udp.port); - if (conf->udp.dns_addr != 0) - 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]); - 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]); - pthread_join(udp_data.packet_reader[i], NULL); - } - pthread_cancel(udp_data.mgmt_handler); - pthread_join(udp_data.mgmt_handler, NULL); - fail_bind: - close(udp_data.s_fd); - fail_socket: - return -1; -} - -#ifdef HAVE_DDNS -/* FIXME: Dependency on nsupdate to be removed in the end */ -/* NOTE: Disgusted with this crap */ -static int ddns_send(char * cmd) -{ - 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: %s.", strerror(errno)); - return -1; - } - - pid = fork(); - if (pid == -1) { - log_err("Failed to fork: %s.", strerror(errno)); - close(pipe_fd[0]); - close(pipe_fd[1]); - return -1; - } - - if (pid == 0) { - 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: %s.", - strerror(errno)); - close(pipe_fd[1]); - return -1; - } - - waitpid(pid, &wstatus, 0); - if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 0) - log_dbg("Succesfully communicated with DNS server."); - else - log_err("Failed to register with DNS server."); - - close(pipe_fd[1]); - - return 0; -} - -static uint32_t ddns_resolve(char * name, - uint32_t 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 (inet4_ntop(&dns_addr, dnsstr) == NULL) - return 0; - - if (pipe(pipe_fd)) { - log_err("Failed to create pipe: %s.", strerror(errno)); - return 0; - } - - pid = fork(); - if (pid == -1) { - log_err("Failed to fork: %s.", strerror(errno)); - close(pipe_fd[0]); - close(pipe_fd[1]); - return -1; - } - - if (pid == 0) { - char * argv[] = {NSLOOKUP_EXEC, name, dnsstr, 0}; - char * envp[] = {0}; - - 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 - 1); - if (count <= 0) { - log_err("Failed to communicate with nslookup."); - close(pipe_fd[0]); - return 0; - } - - close(pipe_fd[0]); - - waitpid(pid, &wstatus, 0); - if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) == 0 && - count != IPCP_UDP_BUF_SIZE - 1) - log_dbg("Succesfully communicated with nslookup."); - else - log_err("Failed to resolve DNS address."); - - buf[count] = '\0'; - substr = strtok(buf, "\n"); - while (substr != NULL) { - substr2 = substr; - substr = strtok(NULL, "\n"); - } - - if (substr2 == NULL || strstr(substr2, addr_str) == NULL) { - log_err("Failed to resolve DNS address."); - return 0; - } - - if (inet_pton(AF_INET, substr2 + strlen(addr_str) + 1, &ip_addr) != 1) { - log_err("Failed to resolve DNS address."); - return 0; - } - - return ip_addr; -} -#endif - -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; -#endif - char * hashstr; - - hashstr = malloc(ipcp_dir_hash_strlen() + 1); - 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_FMT32 " to local registry.", - HASH_VAL32(hash)); - free(hashstr); - return -1; - } - -#ifdef HAVE_DDNS - /* register application with DNS server */ - - dns_addr = udp_data.dns_addr; - - if (dns_addr != 0) { - ip_addr = udp_data.s_saddr.sin_addr.s_addr; - - if (inet4_ntop(&ip_addr, ipstr) == NULL) { - log_err("Failed to convert IP address to string."); - free(hashstr); - return -1; - } - - if (inet4_ntop(&dns_addr, dnsstr) == NULL) { - log_err("Failed to convert DNS address to string."); - free(hashstr); - return -1; - } - - sprintf(cmd, "server %s\nupdate add %s %d A %s\nsend\nquit\n", - 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 - free(hashstr); - - return 0; -} - -static int udp_ipcp_unreg(const uint8_t * hash) -{ -#ifdef HAVE_DDNS - char dnsstr[INET_ADDRSTRLEN]; - /* max DNS name length + max IP length + max command length */ - char cmd[100]; - uint32_t dns_addr; -#endif - char * hashstr; - - assert(hash); - - hashstr = malloc(ipcp_dir_hash_strlen() + 1); - if (hashstr == NULL) { - log_err("Failed to malloc hashstr."); - return -1; - } - - ipcp_hash_str(hashstr, hash); - -#ifdef HAVE_DDNS - /* unregister application with DNS server */ - - dns_addr = udp_data.dns_addr; - - if (dns_addr != 0) { - if (inet4_ntop(&dns_addr, dnsstr) == NULL) { - log_err("Failed to convert DNS address to string."); - free(hashstr); - return -1; - } - sprintf(cmd, "server %s\nupdate delete %s A\nsend\nquit\n", - dnsstr, hashstr); - - ddns_send(cmd); - } -#endif - - shim_data_reg_del_entry(udp_data.shim_data, hash); - - free(hashstr); - - return 0; -} - -static int udp_ipcp_query(const uint8_t * hash) -{ - uint32_t ip_addr = 0; - char * hashstr; - struct hostent * h; -#ifdef HAVE_DDNS - uint32_t dns_addr = 0; -#endif - assert(hash); - - hashstr = malloc(ipcp_dir_hash_strlen() + 1); - if (hashstr == NULL) { - log_err("Failed to malloc hashstr."); - return -ENOMEM; - } - - ipcp_hash_str(hashstr, hash); - - if (shim_data_dir_has(udp_data.shim_data, hash)) { - free(hashstr); - return 0; - } - -#ifdef HAVE_DDNS - dns_addr = udp_data.dns_addr; - - if (dns_addr != 0) { - ip_addr = ddns_resolve(hashstr, dns_addr); - if (ip_addr == 0) { - log_err("Could not resolve %s.", hashstr); - free(hashstr); - return -1; - } - } else { -#endif - h = gethostbyname(hashstr); - if (h == NULL) { - log_err("Could not resolve %s.", hashstr); - free(hashstr); - return -1; - } - - ip_addr = *((uint32_t *) (h->h_addr_list[0])); -#ifdef HAVE_DDNS - } -#endif - - if (shim_data_dir_add_entry(udp_data.shim_data, hash, ip_addr)) { - log_err("Failed to add directory entry."); - free(hashstr); - return -1; - } - - free(hashstr); - - return 0; -} - -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]; - - (void) qs; - - assert(dst); - - if (!shim_data_dir_has(udp_data.shim_data, dst)) { - log_err("Could not resolve destination."); - return -1; - } - - ip_addr = (uint32_t) shim_data_dir_get_addr(udp_data.shim_data, dst); - - if (inet4_ntop(&ip_addr, ipstr) == NULL) { - log_err("Could not convert IP address."); - return -1; - } - - 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; - - if (udp_ipcp_port_alloc(&r_saddr, fd, dst, qs, data) < 0) { - log_err("Could not allocate port."); - return -1; - } - - pthread_rwlock_wrlock(&udp_data.flows_lock); - - udp_data.fd_to_uf[fd].d_eid = -1; - udp_data.fd_to_uf[fd].r_saddr = r_saddr; - - pthread_rwlock_unlock(&udp_data.flows_lock); - - fset_add(udp_data.np1_flows, fd); - - return 0; -} - -static int udp_ipcp_flow_alloc_resp(int fd, - int resp, - const buffer_t * data) -{ - struct sockaddr_in saddr; - int d_eid; - - if (ipcp_wait_flow_resp(fd) < 0) { - log_err("Failed to wait for flow response."); - return -1; - } - - pthread_rwlock_rdlock(&udp_data.flows_lock); - - saddr = udp_data.fd_to_uf[fd].r_saddr; - d_eid = udp_data.fd_to_uf[fd].d_eid; - - pthread_rwlock_unlock(&udp_data.flows_lock); - - 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; - } - - fset_add(udp_data.np1_flows, fd); - - return 0; -} - -static int udp_ipcp_flow_dealloc(int fd) -{ - ipcp_flow_fini(fd); - - fset_del(udp_data.np1_flows, fd); - - pthread_rwlock_wrlock(&udp_data.flows_lock); - - udp_data.fd_to_uf[fd].d_eid = -1; - memset(&udp_data.fd_to_uf[fd].r_saddr, 0, SADDR_SIZE); - - pthread_rwlock_unlock(&udp_data.flows_lock); - - ipcp_flow_dealloc(fd); - - return 0; -} - -static struct ipcp_ops udp_ops = { - .ipcp_bootstrap = udp_ipcp_bootstrap, - .ipcp_enroll = NULL, - .ipcp_connect = NULL, - .ipcp_disconnect = NULL, - .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 = udp_ipcp_flow_alloc_resp, - .ipcp_flow_dealloc = udp_ipcp_flow_dealloc -}; - -int main(int argc, - char * argv[]) -{ - int i; - - - if (udp_data_init() < 0) { - log_err("Failed to init udp data."); - goto fail_data_init; - } - - if (ipcp_init(argc, argv, &udp_ops, THIS_TYPE) < 0) { - log_err("Failed to initialize IPCP."); - goto fail_init; - } - - if (ipcp_start() < 0) { - log_err("Failed to start IPCP."); - goto fail_start; - } - - ipcp_sigwait(); - - if (ipcp_get_state() == IPCP_SHUTDOWN) { - 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_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); - } - - ipcp_stop(); - - ipcp_fini(); - - udp_data_fini(); - - exit(EXIT_SUCCESS); - - fail_start: - ipcp_fini(); - fail_init: - udp_data_fini(); - fail_data_init: - exit(EXIT_FAILURE); -} diff --git a/src/ipcpd/unicast/CMakeLists.txt b/src/ipcpd/unicast/CMakeLists.txt index ca742871..2373f877 100644 --- a/src/ipcpd/unicast/CMakeLists.txt +++ b/src/ipcpd/unicast/CMakeLists.txt @@ -1,40 +1,12 @@ -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}) +protobuf_generate_c(DHT_PROTO_SRCS DHT_PROTO_HDRS + "${CMAKE_CURRENT_SOURCE_DIR}/dir/dht.proto") -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_UNICAST_TARGET ipcpd-unicast CACHE INTERNAL "") -set(IPCP_UNICAST_MPL 60 CACHE STRING - "Default maximum packet lifetime for the unicast IPCP, in seconds") - -protobuf_generate_c(DHT_PROTO_SRCS DHT_PROTO_HDRS dir/dht.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 + cap.c connmgr.c dir.c dt.c @@ -43,7 +15,6 @@ set(SOURCE_FILES pff.c routing.c psched.c - # Add policies last addr-auth/flat.c ca/mb-ecn.c ca/nop.c @@ -54,22 +25,28 @@ set(SOURCE_FILES pff/pft.c routing/link-state.c routing/graph.c - ) +) -add_executable(ipcpd-unicast ${SOURCE_FILES} ${IPCP_SOURCES} ${COMMON_SOURCES} - ${DHT_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(ca/tests) add_subdirectory(dir/tests) -endif () + add_subdirectory(pff/tests) + add_subdirectory(routing/tests) + add_subdirectory(tests) +endif() diff --git a/src/ipcpd/unicast/addr-auth.c b/src/ipcpd/unicast/addr-auth.c index 908a4aa1..27671d09 100644 --- a/src/ipcpd/unicast/addr-auth.c +++ b/src/ipcpd/unicast/addr-auth.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Address authority * diff --git a/src/ipcpd/unicast/addr-auth.h b/src/ipcpd/unicast/addr-auth.h index e119dff3..65567dc3 100644 --- a/src/ipcpd/unicast/addr-auth.h +++ b/src/ipcpd/unicast/addr-auth.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * 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 c4562935..cf832279 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 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Policy for flat addresses in a distributed way * @@ -31,17 +31,18 @@ #include <ouroboros/logs.h> #include <ouroboros/random.h> +#include "addr-auth.h" #include "ipcp.h" #include "flat.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, @@ -57,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; } @@ -67,13 +77,5 @@ int flat_fini(void) uint64_t flat_address(void) { - uint32_t addr = INVALID_ADDRESS; - -#if defined (CONFIG_OUROBOROS_DEBUG) && defined (IPCP_DEBUG_LOCAL) - addr = getpid(); -#else - while (addr == INVALID_ADDRESS) - random_buffer(&addr,sizeof(addr)); -#endif - 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 d4b672c7..b5c7e525 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 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * 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 06b24cec..f5c4c611 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 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Address authority policy ops * diff --git a/src/ipcpd/unicast/addr-auth/pol.h b/src/ipcpd/unicast/addr-auth/pol.h index 844308c6..a33f1dd6 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 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Address Authority policies * diff --git a/src/ipcpd/unicast/ca.c b/src/ipcpd/unicast/ca.c index 287eaf41..290c817e 100644 --- a/src/ipcpd/unicast/ca.c +++ b/src/ipcpd/unicast/ca.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Congestion Avoidance * @@ -22,17 +22,48 @@ #define OUROBOROS_PREFIX "ca" +#include "config.h" + +#include <ouroboros/list.h> #include <ouroboros/logs.h> #include "ca.h" #include "ca/pol.h" +#include <pthread.h> +#include <stdlib.h> + +/* + * A ca_ctx holds congestion state for a (peer address, qos cube) PATH, + * not for a flow. In the default build the façade interns one ctx per + * (addr, qc) and shares it across every flow on that path; the policy + * runs on the shared ctx and cannot tell one flow from many. Per-flow + * ctx (IPCP_CA_PER_FLOW) is a testing build only: it skips interning so + * every flow gets its own ctx. + */ + +struct ca_ctx { + uint64_t addr; + qoscube_t qc; + size_t refs; + void * pol; /* policy ctx (ops->ctx_create result) */ + struct list_head next; +}; + struct { - struct ca_ops * ops; + struct ca_ops * ops; +#ifndef IPCP_CA_PER_FLOW + struct list_head buckets[CA_BUCKETS]; + pthread_mutex_t mtx; +#endif } ca; -int ca_init(enum pol_cong_avoid pol) +int ca_init(enum pol_cong_avoid pol, + uint32_t rtt_ms) { +#ifndef IPCP_CA_PER_FLOW + size_t i; +#endif switch(pol) { case CA_NONE: log_dbg("Disabling congestion control."); @@ -41,69 +72,196 @@ int ca_init(enum pol_cong_avoid pol) case CA_MB_ECN: log_dbg("Using multi-bit ECN."); ca.ops = &mb_ecn_ca_ops; + mb_ecn_init(rtt_ms); break; default: return -1; } +#ifndef IPCP_CA_PER_FLOW + for (i = 0; i < CA_BUCKETS; i++) + list_head_init(&ca.buckets[i]); + + if (pthread_mutex_init(&ca.mtx, NULL) != 0) + return -1; +#endif return 0; } - void ca_fini(void) { +#ifndef IPCP_CA_PER_FLOW + size_t i; + + /* Data path is stopped; drain any ctx a flow left interned. */ + for (i = 0; i < CA_BUCKETS; i++) { + struct list_head * p; + struct list_head * h; + + list_for_each_safe(p, h, &ca.buckets[i]) { + struct ca_ctx * ctx; + ctx = list_entry(p, struct ca_ctx, next); + list_del(&ctx->next); + ca.ops->ctx_destroy(ctx->pol); + free(ctx); + } + } + + pthread_mutex_destroy(&ca.mtx); +#endif ca.ops = NULL; } -void * ca_ctx_create(void) +#ifndef IPCP_CA_PER_FLOW +static size_t ca_bucket(uint64_t addr, + qoscube_t qc) { - return ca.ops->ctx_create(); + return (addr ^ (addr >> 32) ^ (uint64_t) qc) & (CA_BUCKETS - 1); } +#endif -void ca_ctx_destroy(void * ctx) +void * ca_ctx_get(uint64_t addr, + qoscube_t qc) { - return ca.ops->ctx_destroy(ctx); + struct ca_ctx * ctx; +#ifndef IPCP_CA_PER_FLOW + struct list_head * p; + size_t b = ca_bucket(addr, qc); + + pthread_mutex_lock(&ca.mtx); + + list_for_each(p, &ca.buckets[b]) { + ctx = list_entry(p, struct ca_ctx, next); + if (ctx->addr == addr && ctx->qc == qc) { + ctx->refs++; + pthread_mutex_unlock(&ca.mtx); + return ctx; + } + } +#endif + ctx = malloc(sizeof(*ctx)); + if (ctx == NULL) + goto fail_ctx; + + ctx->pol = ca.ops->ctx_create(); + if (ctx->pol == NULL) + goto fail_pol; + + ctx->addr = addr; + ctx->qc = qc; + ctx->refs = 1; + +#ifndef IPCP_CA_PER_FLOW + list_add(&ctx->next, &ca.buckets[b]); + + pthread_mutex_unlock(&ca.mtx); +#endif + return ctx; + fail_pol: + free(ctx); + fail_ctx: +#ifndef IPCP_CA_PER_FLOW + pthread_mutex_unlock(&ca.mtx); +#endif + return NULL; } -ca_wnd_t ca_ctx_update_snd(void * ctx, - size_t len) +void ca_ctx_put(void * _ctx) { - return ca.ops->ctx_update_snd(ctx, len); + struct ca_ctx * ctx = _ctx; + +#ifndef IPCP_CA_PER_FLOW + pthread_mutex_lock(&ca.mtx); + + if (--ctx->refs > 0) { + pthread_mutex_unlock(&ca.mtx); + return; + } + + list_del(&ctx->next); + + pthread_mutex_unlock(&ca.mtx); +#endif + ca.ops->ctx_destroy(ctx->pol); + + free(ctx); } -bool ca_ctx_update_rcv(void * ctx, +time_t ca_ctx_update_snd(void * _ctx, + size_t len, + uint8_t lecn, + uint64_t * ftag) +{ + struct ca_ctx * ctx = _ctx; + + return ca.ops->ctx_update_snd(ctx->pol, len, lecn, ctx->refs, ftag); +} + +bool ca_ctx_update_rcv(void * _ctx, size_t len, uint8_t ecn, - uint16_t * ece) + uint8_t cap, + uint16_t * ece, + uint8_t * fcap) { - return ca.ops->ctx_update_rcv(ctx, len, ecn, ece); + struct ca_ctx * ctx = _ctx; + + return ca.ops->ctx_update_rcv(ctx->pol, len, ecn, cap, ece, fcap); } -void ca_ctx_update_ece(void * ctx, - uint16_t ece) +void ca_ctx_update_ece(void * _ctx, + uint16_t ece, + uint8_t cap) { - return ca.ops->ctx_update_ece(ctx, ece); + struct ca_ctx * ctx = _ctx; + + return ca.ops->ctx_update_ece(ctx->pol, ece, cap); } -void ca_wnd_wait(ca_wnd_t wnd) +bool ca_ctx_hb_due(void * _ctx, + uint64_t now) { - return ca.ops->wnd_wait(wnd); + struct ca_ctx * ctx = _ctx; + + if (ca.ops->ctx_hb_due == NULL) + return false; + + return ca.ops->ctx_hb_due(ctx->pol, now); +} + +void ca_ctx_rtt(void * _ctx, + uint64_t now, + uint64_t rtt) +{ + struct ca_ctx * ctx = _ctx; + + if (ca.ops->ctx_rtt == NULL) + return; + + ca.ops->ctx_rtt(ctx->pol, now, rtt); } -int ca_calc_ecn(int fd, +int ca_calc_ecn(size_t queued, uint8_t * ecn, qoscube_t qc, - size_t len) + size_t mean) { - return ca.ops->calc_ecn(fd, ecn, qc, len); + return ca.ops->calc_ecn(queued, ecn, qc, mean); } -ssize_t ca_print_stats(void * ctx, +bool ca_marks_ecn(void) +{ + return ca.ops->marks_ecn; +} + +ssize_t ca_print_stats(void * _ctx, char * buf, size_t len) { + struct ca_ctx * ctx = _ctx; + if (ca.ops->print_stats == NULL) return 0; - return ca.ops->print_stats(ctx, buf, len); + return ca.ops->print_stats(ctx->pol, buf, len); } diff --git a/src/ipcpd/unicast/ca.h b/src/ipcpd/unicast/ca.h index ea803e17..188fb08a 100644 --- a/src/ipcpd/unicast/ca.h +++ b/src/ipcpd/unicast/ca.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Congestion avoidance * @@ -29,37 +29,54 @@ #include <stdbool.h> #include <sys/types.h> -typedef union { - time_t wait; -} ca_wnd_t; +/* Buffer a policy's ca_print_stats output must fit in. */ +#define CA_STATS_STRLEN 2048 -int ca_init(enum pol_cong_avoid ca); +int ca_init(enum pol_cong_avoid ca, + uint32_t rtt_ms); void ca_fini(void); /* OPS */ -void * ca_ctx_create(void); +void * ca_ctx_get(uint64_t addr, + qoscube_t qc); -void ca_ctx_destroy(void * ctx); +void ca_ctx_put(void * ctx); -ca_wnd_t ca_ctx_update_snd(void * ctx, - size_t len); +time_t ca_ctx_update_snd(void * ctx, + size_t len, + uint8_t lecn, + uint64_t * ftag); bool ca_ctx_update_rcv(void * ctx, size_t len, uint8_t ecn, - uint16_t * ece); + uint8_t cap, + uint16_t * ece, + uint8_t * fcap); void ca_ctx_update_ece(void * ctx, - uint16_t ece); + uint16_t ece, + uint8_t cap); + +bool ca_ctx_hb_due(void * ctx, + uint64_t now); -void ca_wnd_wait(ca_wnd_t wnd); +void ca_ctx_rtt(void * ctx, + uint64_t now, + uint64_t rtt); -int ca_calc_ecn(int fd, +/* + * Marks congestion from the egress queue. Both queued and mean are + * in bytes, so their ratio is the queue depth in packets. + */ +int ca_calc_ecn(size_t queued, uint8_t * ecn, qoscube_t qc, - size_t len); + size_t mean); + +bool ca_marks_ecn(void); ssize_t ca_print_stats(void * ctx, char * buf, diff --git a/src/ipcpd/unicast/ca/mb-ecn.c b/src/ipcpd/unicast/ca/mb-ecn.c index d9a204b0..59f1cae5 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 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Multi-bit ECN Congestion Avoidance * @@ -28,9 +28,10 @@ #include "config.h" -#include <ouroboros/ipcp-dev.h> #include <ouroboros/time.h> +#include <ouroboros/utils.h> +#include "cap.h" #include "mb-ecn.h" #include <inttypes.h> @@ -38,47 +39,284 @@ #include <string.h> #include <stdio.h> -/* congestion avoidance constants */ -#define CA_SHFT 5 /* Average over 32 pkts */ -#define CA_WND (1 << CA_SHFT) /* 32 pkts receiver wnd */ -#define CA_UPD (1 << (CA_SHFT - 2)) /* Update snd every 8 pkt */ -#define CA_SLOT 24 /* Initial slot = 16 ms */ -#define CA_INC 1UL << 16 /* ~4MiB/s^2 additive inc */ -#define CA_IWL 1UL << 16 /* Initial limit ~4MiB/s */ -#define CA_MINPS 8 /* Mimimum pkts / slot */ -#define CA_MAXPS 64 /* Maximum pkts / slot */ -#define ECN_Q_SHFT 4 -#define ts_to_ns(ts) ((size_t) ts.tv_sec * BILLION + ts.tv_nsec) +/* + * Multi-bit ECN congestion avoidance: a rate-based controller. The + * sender paces a token bucket at a rate steered by graded ECN + * feedback, so the backoff is proportional to the congestion. A + * backlogged flow ramps in slow start to find the path capacity, + * then settles into AIMD around its fair share. There is no sliding + * window and no per-flow timer; the control runs on sends. + * + * Rate law, per control step of dt seconds (r bytes/s, m the mark + * in ece units, m_ref = CA_ECE_REF, ai the additive slope): + * + * slow start dr = r * dt / ss_tc + * increase dr = (ai + r / T_probe) * dt + * decrease dr = -r * (min(m, CA_ECE_MAX) / m_ref) * dt + L, + * cut capped at r/2 + * lead L = -dm * r / (m_ref * CA_MD_KD_DIV) + * + * dm is the mark's step since the last decrease, clamped to + * +-m_ref. On a rise L joins the cut before the r/2 cap; on a + * fall it returns after that cap, bounded on its own to + * +-r / CA_MD_KD_DIV, so a full cut is never handed back in one + * step. + * + * Every step scales by elapsed wall-clock time, not by packet + * count, so the per-second dynamics are RTT-independent. + * + * Pacer: a virtual clock vt advances at r; a packet's start tag is + * max(tag, vt) and it waits (tag - vt) / r. + * + * Receiver: ece is the time integral of ecn over a pricing window, + * ece = integral(ecn dt) / T. The window is a per-layer constant so + * every flow prices one bottleneck alike; it stretches only for a + * flow too slow to fill it with samples. + * + * Marking (mb_ecn_calc_ecn): ecn is the quarter-log2 of the queue + * measured in mark units U (U = CA_MARK_KNEE * mean), so the mark is + * a log-scale queue depth. Equilibrium is where increase balances + * decrease: + * + * ecn* = (m_ref / 32) * (ai * n / C + 1 / T_probe) = n + 2 + * + * for n backlogged flows, i.e. a standing queue of 2^((n+2)/4) * U. + * This is the zero-delay fixpoint; feedback delay raises the real + * standing queue above it. + */ + +/* ECE fixed point */ +#define CA_SHFT 5 /* ece fixed point: 32 * ecn */ + +/* Receiver averaging window */ +#define CA_TW (1ULL << 26) /* pricing window ~67 ms */ +#define CA_TW_MIN (4ULL * MILLION) /* pricing window floor 4 ms */ +#define CA_TW_RTT_MUL 2 /* T_w = 2 * layer RTT */ +#define CA_TW_ABSMAX (1ULL << 32) /* window ceiling ~4.3 s */ +/* Quiet horizon, in windows (1 << shift): gap restart and the TTLs. */ +#define CA_TW_GAP_SHFT 2 +#define CA_RX_WBYTES 16000ULL /* 16 pkts x 1000 B a window */ +#define CA_RX_WCLOSE (2 * CA_RX_WBYTES) /* byte-triggered early close */ +#define CA_TW_SM_SHFT 2 /* window EWMA weight 1/4 */ + +/* Congestion marking */ +#define CA_MARK_KNEE 1 /* mark onset (packets) */ + +/* Rate machine */ +#define CA_RATE_MIN (1ULL << 13) /* 8 KiB/s rate floor */ +#define CA_RATE_INIT (1ULL << 16) /* slow start seed 64 KiB/s */ +/* Rate cap; also keeps rate * dt and rate * rise below 2^64. */ +#define CA_RATE_MAX (1ULL << 37) +#define CA_INV_SHFT 32 /* reciprocal-rate fixp */ +#define CA_AI_RATE (1ULL << 17) /* 128 KiB/s^2 additive inc */ +#define CA_PROBE_TC (8ULL * BILLION) /* proportional probe TC 8s */ +#define CA_ECE_REF (16 << CA_SHFT) /* full congestion: ecn 16 */ +/* Decrease saturation, and the level below which the hold clears. */ +#define CA_ECE_MAX (2 * CA_ECE_REF) /* ecn 32 */ +#define CA_MD_KD_DIV 16 /* lead gain 1/16 */ + +/* Control cadence */ +#define CA_DT_CTRL (BILLION / 1000) /* min rate-update spacing */ +#define CA_DT_CAP (BILLION / 20) /* idle-resume Δt clamp 50ms */ +#define CA_IDLE_PKTS 4 /* idle: gap over 4 packets */ +/* Feedback staleness floor; ctx->ece_ttl rides above it by rate. */ +#define CA_ECE_TTL (1ULL << 28) /* ~268 ms */ + +/* Slow start */ +#define CA_SS_RTT_MUL 2 /* ss_tc = 2 * layer RTT */ +#define CA_SS_TC_MIN (BILLION / 1000) /* ramp floor 1 ms */ +#define CA_SS_TC_MAX (4ULL * BILLION) /* ramp ceiling 4 s */ +#define CA_RTT_SHFT 2 /* ss_tc EWMA weight 1/4 */ +#define CA_SS_TC_GRW 1 /* ramp climb cap 2x a sample */ +#define CA_SS_RTT_DEF 200 /* default layer RTT (ms) */ + +/* Heartbeat */ +#define CA_HB_MIN (40 * MILLION) /* heartbeat interval floor */ +#define CA_HB_LOSS 4 /* stale horizons -> restart */ + +/* Path capacity */ +#define CA_CAP_SHFT 5 /* floor = capacity / 32 */ +#define CA_CAP_SM_SHFT 1 /* capacity EWMA weight 1/2 */ +/* Outlives ece_ttl 16x: onset-fresh fcap re-seeds each episode. */ +#define CA_CAP_TTL_SHFT 4 +#define CA_RMIN_MAX (1ULL << 32) /* derived floor ceiling */ + +/* Sender utilisation */ +#define CA_SND_WIN (1ULL << 26) /* sender util window ~67 ms */ +#define CA_USE_NUM 3 /* backlogged: offered >= */ +#define CA_USE_DEN 4 /* 3/4 * window-start rate */ +#define CA_SND_DEC_SHFT 4 /* offered max-filter 1/16 */ +#define CA_SND_DEC_CAP 16 /* bound gapped-close decay */ +#define CA_SND_BYT_MAX (1ULL << 33) /* offered-byte saturation */ +#define CA_PAC_DEN 4 /* backlogged: 1/4 deferred */ + +/* + * Retuning invariants (pinned by the unit tests): + * - (1 << CA_TW_GAP_SHFT) * CA_TW > S * BILLION / CA_RATE_MIN, or + * a floor-rate flow's onset restart-loops (S ~ one MTU; both ns). + * - CA_RX_WBYTES * BILLION / CA_RATE_MIN < CA_TW_ABSMAX: the + * floor-rate window must clear the ceiling. + * - CA_TW < CA_RX_WBYTES * BILLION / CA_RATE_MIN: at the rate + * floor the sample budget, not the horizon, sizes the window. + * - CA_TW << CA_TW_GAP_SHFT <= CA_ECE_TTL: the estimator must + * not call a gap fresh that the sender still counts as live. + * - CA_ECE_TTL > S * BILLION / CA_RATE_MIN: the idle cap clears a + * floor-rate flow's inter-send gap, so pacing never reads as idle. + * - CA_DT_CAP < CA_ECE_TTL: the idle clamp needs the TTL above it, + * or every slow flow reads idle on every send. + * - CA_RATE_MAX * CA_DT_CAP, the folded lead * inv_rate at + * CA_RATE_MIN, and owed * BILLION (owed clamped in mb_ecn_snd) all + * keep the pacer arithmetic below 2^64. + * - CA_RATE_MIN <= CA_RATE_INIT and CA_RMIN_MAX < CA_RATE_MAX. + * - cap_enc(16 * mean) - cap_enc(mean) == CA_ECE_REF >> CA_SHFT: a + * queue of 16 packets is what reads as full congestion. + * - CA_MD_KD_DIV sets the lead gain. The term acts both ways (cut on + * a rise, give back on a fall), which cancels the DC bias a + * one-sided term would rectify into a standing rate difference + * between flows pricing one queue; that is what lets the gain run + * at 1/16 instead of the deadzone below 1/8. + * - T_w = clamp(CA_TW_RTT_MUL * RTT, CA_TW_MIN, CA_TW) scales only + * the receiver pricing window; CA_ECE_TTL, CA_SND_WIN, CA_DT_CAP + * and CA_DT_CTRL are absolute and must not be derived from it. + * - The gap-restart horizon is floored at CA_ECE_TTL, so a + * floor-rate flow's inter-packet gap never reads as an onset. + * - The ai_hold release threshold equals the decrease saturation + * clamp: a standing mark that is a legal equilibrium must be able + * to clear the hold. + * + * Structural invariants (not exercised by the unit tests): + * - CA_MARK_KNEE <= 4: the full decrease range must fit the ring + * (SSM_RBUFF_SIZE, not visible from this file). + * - ecn* = 2 + n holds for n <= 29 (the decrease clamp) and only + * with live capacity feedback. + */ struct mb_ecn_ctx { - uint16_t rx_ece; /* Level of congestion (upstream) */ - size_t rx_ctr; /* Receiver side packet counter */ - - uint16_t tx_ece; /* Level of congestion (downstream) */ - size_t tx_ctr; /* Sender side packet counter */ - size_t tx_wbc; /* Window byte count */ - size_t tx_wpc; /* Window packet count */ - size_t tx_wbl; /* Window byte limit */ - bool tx_cav; /* Congestion avoidance */ - size_t tx_mul; /* Slot size multiplier */ - size_t tx_inc; /* Additive increase */ - size_t tx_slot; + uint16_t rx_ece; /* smoothed congestion echo (32 * ecn) */ + uint64_t rx_acc; /* window integral of ecn * dt */ + uint64_t rx_byt; /* bytes arrived in current window */ + uint64_t rx_ts; /* last packet arrival (ns) */ + uint64_t rx_win; /* window start (ns) */ + uint64_t rx_tw; /* adaptive averaging window (ns) */ + uint8_t rx_cap; /* window bottleneck capacity code */ + + uint16_t tx_ece; /* congestion reported from downstream */ + uint16_t tx_ecp; /* previous tx_ece (rise detection) */ + uint8_t tx_loc; /* local first-hop ecn mark (fallback) */ + bool tx_cav; /* past slow start */ + bool ai_hold; /* freeze AI after loss until clear */ + uint64_t rate; /* paced send rate (bytes/s) */ + uint64_t rate_min; /* capacity-derived rate floor (B/s) */ + uint64_t ai_rate; /* additive-increase slope (B/s^2) */ + uint64_t ece_ttl; /* how long feedback stays valid (ns) */ + uint64_t ss_tc; /* slow-start time constant (ns) */ + uint64_t dec_acc; /* sub-ms decrease time carried (ns) */ + uint64_t inv_rate; /* fixed-point 1/rate for pacing */ + uint64_t vt; /* virtual service clock (bytes) */ + uint64_t lead; /* pacer lead of last send (bytes) */ + uint64_t last_ts; /* last clock advance (ns) */ + uint64_t last_ctrl; /* last rate update (ns) */ + uint64_t last_fb; /* last congestion feedback (ns) */ + uint64_t last_sig; /* last liveness signal, incl. hb (ns) */ + uint64_t n_fb; /* feedback updates received */ + uint64_t n_rtt; /* heartbeat RTT samples folded */ + uint64_t last_hb; /* last heartbeat emitted (ns) */ + uint64_t last_res; /* last resume from idle (ns) */ + uint64_t last_loc; /* last local mark seen (ns) */ + uint64_t last_cap; /* last capacity applied (ns) */ + + uint64_t snd_byt; /* bytes offered this window (capped) */ + size_t snd_flows; /* flows sharing the ctx, >= 1 */ + uint64_t snd_pac; /* bytes the pacer held back this win */ + uint64_t snd_win; /* utilisation window start (ns) */ + uint64_t snd_r0; /* rate at window start */ + uint64_t snd_rate; /* max-filter of offered rate (B/s) */ + bool backlogged; /* offered load keeps the pacer busy */ + bool src_limited; /* rate held at offered-load ceiling */ + bool started; /* a real send has occurred */ + + /* Diagnostics only, read by mb_ecn_print_stats. */ + uint8_t tx_cap; /* path capacity code fed back to us */ + uint64_t n_ctrl; /* control steps taken */ + uint64_t t_ctrl; /* wall time covered by steps (ns) */ + uint64_t t_bank; /* increase time banked in steps (ns) */ + uint64_t n_ttl; /* feedback aged out (TTL) */ + uint64_t n_cap; /* capacity updates applied */ + uint64_t n_loss; /* signal-loss cuts (collapse) */ + uint64_t ss_peak; /* peak rate in slow start (bytes/s) */ }; +/* Layer slow-start time constant (ns), from the declared RTT. */ +static uint64_t mb_ecn_ss_tc = (uint64_t) CA_SS_RTT_MUL * + CA_SS_RTT_DEF * MILLION; + +/* Layer pricing window (ns), from the declared RTT. */ +static uint64_t mb_ecn_tw = CA_TW; + struct ca_ops mb_ecn_ca_ops = { .ctx_create = mb_ecn_ctx_create, .ctx_destroy = mb_ecn_ctx_destroy, .ctx_update_snd = mb_ecn_ctx_update_snd, .ctx_update_rcv = mb_ecn_ctx_update_rcv, .ctx_update_ece = mb_ecn_ctx_update_ece, - .wnd_wait = mb_ecn_wnd_wait, + .ctx_hb_due = mb_ecn_ctx_hb_due, + .ctx_rtt = mb_ecn_ctx_rtt, .calc_ecn = mb_ecn_calc_ecn, + .marks_ecn = true, .print_stats = mb_ecn_print_stats }; +static uint64_t mb_ecn_rate_inv(uint64_t rate) +{ + return ((uint64_t) BILLION << CA_INV_SHFT) / rate; +} + +/* + * Feedback arrives once per receiver window, and the window tracks + * the flow's byte rate. Mirror it: age the signal out only past the + * quiet horizon at the current rate, floored for fast flows. + */ +static uint64_t mb_ecn_ece_ttl(uint64_t rate) +{ + uint64_t ttl; + + ttl = (1 << CA_TW_GAP_SHFT) * CA_RX_WBYTES * BILLION / rate; + + return ttl > (uint64_t) CA_ECE_TTL ? ttl : (uint64_t) CA_ECE_TTL; +} + +/* Derive the layer slow-start slope from the declared RTT (ms). */ +void mb_ecn_init(uint32_t rtt_ms) +{ + uint64_t tc; + uint64_t rtt; + uint64_t tw; + + if (rtt_ms == 0) /* unspecified: safe default */ + rtt_ms = CA_SS_RTT_DEF; + + tc = (uint64_t) CA_SS_RTT_MUL * rtt_ms * MILLION; + if (tc < (uint64_t) CA_SS_TC_MIN) + tc = CA_SS_TC_MIN; + + mb_ecn_ss_tc = tc; + + rtt = (uint64_t) rtt_ms * MILLION; + + tw = (uint64_t) CA_TW_RTT_MUL * rtt; + if (tw < CA_TW_MIN) + tw = CA_TW_MIN; + + if (tw > CA_TW) + tw = CA_TW; + + mb_ecn_tw = tw; +} + void * mb_ecn_ctx_create(void) { struct timespec now; + uint64_t t; struct mb_ecn_ctx * ctx; ctx = malloc(sizeof(*ctx)); @@ -89,10 +327,29 @@ void * mb_ecn_ctx_create(void) memset(ctx, 0, sizeof(*ctx)); - ctx->tx_mul = CA_SLOT; - ctx->tx_wbl = CA_IWL; - ctx->tx_inc = CA_INC; - ctx->tx_slot = ts_to_ns(now) >> ctx->tx_mul; + t = TS_TO_UINT64(now); + + ctx->rate = CA_RATE_INIT; + ctx->rate_min = CA_RATE_MIN; + ctx->ai_rate = CA_AI_RATE; + ctx->ss_tc = mb_ecn_ss_tc; + ctx->ece_ttl = mb_ecn_ece_ttl(CA_RATE_INIT); + ctx->inv_rate = mb_ecn_rate_inv(CA_RATE_INIT); + ctx->rx_ts = t; + ctx->rx_win = t; + ctx->rx_tw = mb_ecn_tw; + ctx->last_ts = t; + ctx->last_ctrl = t; + ctx->last_fb = t; + ctx->last_sig = t; + ctx->last_loc = t; + ctx->last_cap = t; + + /* snd_win/last_ts re-seeded lazily on the first real send. */ + ctx->snd_r0 = CA_RATE_INIT; + ctx->snd_rate = CA_RATE_INIT; + ctx->snd_flows = 1; + ctx->backlogged = true; return (void *) ctx; } @@ -102,158 +359,670 @@ void mb_ecn_ctx_destroy(void * ctx) free(ctx); } -#define _slot_after(new, old) ((int64_t) (old - new) < 0) +/* Local first-hop mark exits slow start and covers dead feedback. */ +static void mb_ecn_loc(struct mb_ecn_ctx * ctx, + uint8_t lecn, + uint64_t t) +{ + if (lecn == 0) + return; + + ctx->tx_loc = lecn; + ctx->tx_cav = true; + ctx->last_loc = t; +} + +/* Slow start: ramp only while backlogged. */ +static void mb_ecn_slow_start(struct mb_ecn_ctx * ctx, + uint64_t dta) +{ + if (ctx->backlogged) + ctx->rate += ctx->rate * dta / ctx->ss_tc; +} + +/* Additive increase plus a rate-independent proportional probe. */ +static void mb_ecn_increase(struct mb_ecn_ctx * ctx, + uint64_t dta) +{ + if (!ctx->backlogged) + return; + + /* After a loss, hold until a clean signal drains the queue. */ + if (ctx->ai_hold) + return; + + ctx->rate += ctx->ai_rate * dta / BILLION; + ctx->rate += ctx->rate * dta / CA_PROBE_TC; +} + +/* + * Multiplicative decrease: cut proportional to mark x elapsed time, + * plus a lead term on the mark's step, clamped and acting both ways. + */ +static void mb_ecn_decrease(struct mb_ecn_ctx * ctx, + uint64_t dtc) +{ + uint64_t dtm; + uint64_t mark; + uint64_t step; + uint64_t lead; + uint64_t cut; + uint16_t m; + bool up; + + m = ctx->tx_ece > 0 ? ctx->tx_ece + : (uint16_t) (ctx->tx_loc << CA_SHFT); + if (m == 0) { + ctx->dec_acc = 0; /* unmarked time is not banked */ + ctx->tx_ecp = 0; + return; + } + + mark = MIN(m, CA_ECE_MAX); + + /* Lead on the mark step; the clamp bounds it to rate/KD. */ + up = m > ctx->tx_ecp; + step = up ? m - ctx->tx_ecp : ctx->tx_ecp - m; + step = MIN(step, CA_ECE_REF); + lead = ctx->rate * step / (CA_ECE_REF * CA_MD_KD_DIV); + + cut = up ? lead : 0; + + /* + * Bank the remainder: at a 1 ms control cadence, truncating + * to whole milliseconds would drop up to half of every cut. + */ + ctx->dec_acc += dtc; + dtm = ctx->dec_acc / MILLION; + ctx->dec_acc -= dtm * MILLION; + if (mark * dtm >= CA_ECE_REF * 500) + cut += ctx->rate / 2; + else + cut += ctx->rate * mark * dtm / (CA_ECE_REF * 1000); + + if (cut > ctx->rate / 2) + cut = ctx->rate / 2; + + ctx->rate -= cut; + + if (!up) + ctx->rate += lead; + + ctx->tx_ecp = m; +} + +/* Offered-load ceiling backstop while source-limited. */ +static void mb_ecn_ceiling(struct mb_ecn_ctx * ctx) +{ + uint64_t hi; + + if (ctx->backlogged) { + ctx->src_limited = false; + return; + } + + /* Land on the backlog level; a ceiling above it never clears. */ + hi = ctx->snd_rate > CA_RATE_MAX / CA_USE_DEN * CA_USE_NUM + ? (uint64_t) CA_RATE_MAX + : ctx->snd_rate * CA_USE_DEN / CA_USE_NUM; + if (hi < CA_RATE_MIN) + hi = CA_RATE_MIN; + + ctx->src_limited = ctx->rate > hi; + if (ctx->src_limited) + ctx->rate = hi; +} + +static void mb_ecn_ctrl(struct mb_ecn_ctx * ctx, + uint64_t dtc) +{ + uint64_t dta; + uint64_t lo; + + /* AI and slow start bank at most CA_DT_CAP of idle time. */ + dta = MIN(dtc, (uint64_t) CA_DT_CAP); + + ctx->n_ctrl++; + ctx->t_ctrl += dtc; + ctx->t_bank += dta; + + if (ctx->tx_cav) { + mb_ecn_increase(ctx, dta); + mb_ecn_decrease(ctx, dtc); + } else { + mb_ecn_slow_start(ctx, dta); + } + + mb_ecn_ceiling(ctx); + + /* Capacity floor only while backlogged; else the absolute floor. */ + lo = ctx->backlogged ? ctx->rate_min : (uint64_t) CA_RATE_MIN; + if (ctx->rate < lo) + ctx->rate = lo; + + if (ctx->rate > CA_RATE_MAX) + ctx->rate = CA_RATE_MAX; + + ctx->inv_rate = mb_ecn_rate_inv(ctx->rate); + ctx->ece_ttl = mb_ecn_ece_ttl(ctx->rate); + + if (!ctx->tx_cav && ctx->rate > ctx->ss_peak) + ctx->ss_peak = ctx->rate; +} + +/* Fold offered into the max filter: rise at once, decay 1/16 per window. */ +static void mb_ecn_offered(struct mb_ecn_ctx * ctx, + uint64_t offered, + uint64_t elapsed) +{ + uint64_t n; + + if (offered >= ctx->snd_rate) { + ctx->snd_rate = offered; + return; + } + + n = MIN(elapsed / CA_SND_WIN, CA_SND_DEC_CAP); + while (n-- > 0 && ctx->snd_rate > offered) + ctx->snd_rate -= (ctx->snd_rate - offered) >> CA_SND_DEC_SHFT; +} + +/* Open a fresh utilisation window at t. */ +static void mb_ecn_win_open(struct mb_ecn_ctx * ctx, + uint64_t t) +{ + ctx->snd_win = t; + ctx->snd_byt = 0; + ctx->snd_pac = 0; + ctx->snd_r0 = ctx->rate; +} + +/* + * Note the flow count; a window spanning two populations measures + * neither, so a change opens a fresh one. + */ +static void mb_ecn_flows(struct mb_ecn_ctx * ctx, + size_t flows, + uint64_t t) +{ + size_t n = flows > 0 ? flows : 1; + + if (n == ctx->snd_flows) + return; + + ctx->snd_flows = n; + + mb_ecn_win_open(ctx, t); +} + +/* + * Close the utilisation window: set backlogged from the level test, + * fold offered into the max filter, then reset the window. + */ +static void mb_ecn_win(struct mb_ecn_ctx * ctx, + uint64_t t) +{ + uint64_t elapsed = t - ctx->snd_win; + uint64_t offered; + bool was = ctx->backlogged; + + /* + * snd_byt is the whole ctx's offered bytes but rate is what one + * flow may send, so share it out before either is compared. + */ + offered = ctx->snd_byt * BILLION / elapsed / ctx->snd_flows; + + /* + * Offered load is counted past the pacer, so it cannot tell a + * quiet source from one the pacer is holding back, and idle + * flows on the context drag it down. A window the pacer had to + * defer is rate-limited whatever the bytes say. + */ + ctx->backlogged = offered * CA_USE_DEN >= ctx->snd_r0 * CA_USE_NUM + || ctx->snd_pac * CA_PAC_DEN >= ctx->snd_byt; + + if (!was && ctx->backlogged) /* resume: fresh liveness baseline */ + ctx->last_res = t; + + mb_ecn_offered(ctx, offered, elapsed); + + if (ctx->backlogged) + ctx->src_limited = false; + + mb_ecn_win_open(ctx, t); +} + +/* Age out congestion, local-mark and capacity signals once stale. */ +/* Heartbeat interval: ~1 RTT, floored so fast links don't over-probe. */ +static uint64_t mb_ecn_t_hb(const struct mb_ecn_ctx * ctx) +{ + uint64_t t = ctx->ss_tc >> 1; + + return t > (uint64_t) CA_HB_MIN ? t : CA_HB_MIN; +} + +/* Feedback collapsed while backlogged: halve like an RTO, stay in AIMD. */ +static void mb_ecn_loss(struct mb_ecn_ctx * ctx, + uint64_t t) +{ + ctx->rate -= ctx->rate / 2; + if (ctx->rate < (uint64_t) CA_RATE_MIN) + ctx->rate = CA_RATE_MIN; + + ctx->inv_rate = mb_ecn_rate_inv(ctx->rate); + ctx->ece_ttl = mb_ecn_ece_ttl(ctx->rate); + ctx->last_sig = t; + ctx->ai_hold = true; + ctx->n_loss++; +} + +static void mb_ecn_age(struct mb_ecn_ctx * ctx, + uint64_t t) +{ + uint64_t ttl = ctx->ece_ttl; + uint64_t ref = ctx->last_sig > ctx->last_res + ? ctx->last_sig : ctx->last_res; + uint64_t gap = t - ref; + + /* + * Sustained silence while backlogged is feedback collapse: cut + * the rate in half and stay in AIMD, so a recovering flow climbs + * back additively instead of re-ramping. Repeated silence decays + * it geometrically toward the floor. + */ + if (ctx->backlogged && ctx->n_fb + ctx->n_rtt > 0 + && gap > (uint64_t) CA_HB_LOSS * ttl) { + mb_ecn_loss(ctx, t); + return; + } + + if (t - ctx->last_fb > ctx->ece_ttl) { + if (ctx->tx_ece > 0) + ctx->n_ttl++; + ctx->tx_ece = 0; + } + + if (t - ctx->last_loc > ctx->ece_ttl) + ctx->tx_loc = 0; + + /* Stale capacity: fall back to the compile-time defaults. */ + if (t - ctx->last_cap > ctx->ece_ttl << CA_CAP_TTL_SHFT) { + ctx->rate_min = CA_RATE_MIN; + ctx->ai_rate = CA_AI_RATE; + ctx->tx_cap = 0; + } +} + +/* Advance the virtual clock; a gap past CA_DT_CAP credits a burst. */ +static void mb_ecn_advance(struct mb_ecn_ctx * ctx, + uint64_t dt, + size_t len, + uint64_t ftag) +{ + uint64_t burst; + uint64_t owed; + + if (dt <= (uint64_t) CA_DT_CAP) { + ctx->vt += ctx->rate * dt / BILLION; + return; + } + + burst = ctx->rate * CA_DT_CAP / BILLION; + if (burst < (uint64_t) len) + burst = len; -ca_wnd_t mb_ecn_ctx_update_snd(void * _ctx, - size_t len) + owed = ftag > ctx->vt ? ftag - ctx->vt + burst : burst; + + /* Clamp so owed * BILLION cannot wrap (2^33 B backlog). */ + if (owed > (1ULL << 33)) + owed = 1ULL << 33; + + if (dt >= owed * BILLION / ctx->rate) + ctx->vt += owed; + else + ctx->vt += ctx->rate * dt / BILLION; +} + +static time_t mb_ecn_snd(struct mb_ecn_ctx * ctx, + size_t len, + uint64_t t, + uint64_t * ftag) +{ + uint64_t dt; + uint64_t dtc; + uint64_t idle; + uint64_t s; + + /* Lazy warm-up seed: packet #1 is never an idle resume. */ + if (!ctx->started) { + ctx->started = true; + ctx->last_ts = t; + ctx->last_res = t; + ctx->snd_win = t; + ctx->snd_r0 = ctx->rate; + } + + dt = t - ctx->last_ts; + ctx->last_ts = t; + + /* + * Idle gap clears backlog before aging: no false loss on resume. + * Measured against the pacer's own spacing, so a flow paced + * slower than CA_DT_CAP per packet does not read as idle on + * every send, and bounded by the staleness horizon. + */ + idle = CA_IDLE_PKTS * len * BILLION / ctx->rate; + idle = MAX(idle, (uint64_t) CA_DT_CAP); + idle = MIN(idle, (uint64_t) CA_ECE_TTL); + if (dt > idle) + ctx->backlogged = false; + + mb_ecn_age(ctx, t); + + /* Offered-load estimator: accumulate, gate growth, size ceiling. */ + ctx->snd_byt += len; + if (ctx->snd_byt > (uint64_t) CA_SND_BYT_MAX) + ctx->snd_byt = CA_SND_BYT_MAX; + + if (t - ctx->snd_win >= (uint64_t) CA_SND_WIN) + mb_ecn_win(ctx, t); + + /* Rate update before the vt advance: burst uses the clamped rate. */ + dtc = t - ctx->last_ctrl; + if (dtc >= (uint64_t) CA_DT_CTRL) { + ctx->last_ctrl = t; + mb_ecn_ctrl(ctx, dtc); + } + + mb_ecn_advance(ctx, dt, len, *ftag); + + /* SFQ start tag: behind the clock starts now, ahead waits. */ + s = *ftag > ctx->vt ? *ftag : ctx->vt; + *ftag = s + len; + + if (s > ctx->vt) + ctx->snd_pac += len; + + ctx->lead = s - ctx->vt; + + /* Reciprocal pacing; folded so any lead * rate stays in range. */ + if (s > ctx->vt) + return (time_t) ((ctx->lead * (ctx->inv_rate >> 16)) + >> (CA_INV_SHFT - 16)); + + return 0; +} + +time_t mb_ecn_ctx_update_snd(void * _ctx, + size_t len, + uint8_t lecn, + size_t flows, + uint64_t * ftag) { struct timespec now; - size_t slot; - ca_wnd_t wnd; + uint64_t t; struct mb_ecn_ctx * ctx = _ctx; clock_gettime(PTHREAD_COND_CLOCK, &now); - slot = ts_to_ns(now) >> ctx->tx_mul; + t = TS_TO_UINT64(now); - ctx->tx_ctr++; - ctx->tx_wpc++; - ctx->tx_wbc += len; + mb_ecn_flows(ctx, flows, t); - if (ctx->tx_ctr > CA_WND) - ctx->tx_ece = 0; + mb_ecn_loc(ctx, lecn, t); - if (_slot_after(slot, ctx->tx_slot)) { - bool carry = false; /* may carry over if window increases */ + return mb_ecn_snd(ctx, len, t, ftag); +} - ctx->tx_slot = slot; +/* Estimator idle, or a quiet gap past the horizon: restart fresh. */ +static bool mb_ecn_rcv_fresh(const struct mb_ecn_ctx * ctx, + uint64_t dt) +{ + uint64_t gap; - if (!ctx->tx_cav) { /* Slow start */ - if (ctx->tx_wbc > ctx->tx_wbl) - ctx->tx_wbl <<= 1; - } else { - if (ctx->tx_ece) /* Mult. Decrease */ - ctx->tx_wbl -= (ctx->tx_wbl * ctx->tx_ece) - >> (CA_SHFT + 8); - else /* Add. Increase */ - ctx->tx_wbl = ctx->tx_wbc + ctx->tx_inc; - } + if (ctx->rx_ece == 0 && ctx->rx_acc == 0) + return true; - /* Window scaling */ - if (ctx->tx_wpc < CA_MINPS) { - size_t fact = 0; /* factor to scale the window up */ - size_t pkts = ctx->tx_wpc; - while (pkts < CA_MINPS) { - pkts <<= 1; - fact++; - } - ctx->tx_mul += fact; - ctx->tx_slot >>= fact; - if ((ctx->tx_slot & ((1 << fact) - 1)) == 0) { - carry = true; - ctx->tx_slot += 1; - } - ctx->tx_wbl <<= fact; - ctx->tx_inc <<= fact; - } else if (ctx->tx_wpc > CA_MAXPS) { - size_t fact = 0; /* factor to scale the window down */ - size_t pkts = ctx->tx_wpc; - while (pkts > CA_MAXPS) { - pkts >>= 1; - fact++; - } - ctx->tx_mul -= fact; - ctx->tx_slot <<= fact; - ctx->tx_wbl >>= fact; - ctx->tx_inc >>= fact; - } else { - ctx->tx_slot = slot; - } + gap = ctx->rx_tw << CA_TW_GAP_SHFT; - if (!carry) { - ctx->tx_wbc = 0; - ctx->tx_wpc = 0; - } - } + return dt > MAX(gap, (uint64_t) CA_ECE_TTL); +} - if (ctx->tx_wbc > ctx->tx_wbl) - wnd.wait = ((ctx->tx_slot + 1) << ctx->tx_mul) - ts_to_ns(now); +/* + * Size the next averaging window to ~16 packets at this rate, floored + * at the price horizon: a flow fast enough to fill the horizon + * integrates over CA_TW, a slower one stretches for its samples. + */ +static void mb_ecn_resize(struct mb_ecn_ctx * ctx, + uint64_t win) +{ + uint64_t tw = CA_RX_WBYTES * win / ctx->rx_byt; + + if (tw > ctx->rx_tw) + ctx->rx_tw += (tw - ctx->rx_tw) >> CA_TW_SM_SHFT; else - wnd.wait = 0; + ctx->rx_tw -= (ctx->rx_tw - tw) >> CA_TW_SM_SHFT; - return wnd; + if (ctx->rx_tw < mb_ecn_tw) + ctx->rx_tw = mb_ecn_tw; + + if (ctx->rx_tw > CA_TW_ABSMAX) + ctx->rx_tw = CA_TW_ABSMAX; } -void mb_ecn_wnd_wait(ca_wnd_t wnd) +static bool mb_ecn_rcv(struct mb_ecn_ctx * ctx, + size_t len, + uint8_t ecn, + uint8_t cap, + uint16_t * ece, + uint8_t * fcap, + uint64_t t) { - if (wnd.wait > 0) { - struct timespec s = TIMESPEC_INIT_S(0); - if (wnd.wait > BILLION) /* Don't care throttling < 1s */ - s.tv_sec = 1; - else - s.tv_nsec = wnd.wait; + uint64_t dt; + uint64_t win; + + dt = t - ctx->rx_ts; + ctx->rx_ts = t; - nanosleep(&s, NULL); + if (ctx->rx_ece == 0 && ctx->rx_acc == 0 && ecn == 0) + return false; + + /* Onset, or ~4 windows of silence: emit fresh, undiluted. */ + if (mb_ecn_rcv_fresh(ctx, dt)) { + ctx->rx_win = t; + ctx->rx_acc = 0; + ctx->rx_byt = len; + ctx->rx_cap = cap; /* fresh, seeds the new window */ + ctx->rx_ece = (uint16_t) (ecn << CA_SHFT); + *ece = ctx->rx_ece; + *fcap = ctx->rx_cap; + return true; + } + + /* Dwell clamp: one packet weighs at most one window of mark. */ + ctx->rx_acc += ecn * MIN(dt, ctx->rx_tw); + ctx->rx_byt += len; + + ctx->rx_cap = cap_min(ctx->rx_cap, cap); + win = t - ctx->rx_win; + if (win < ctx->rx_tw) { + /* Early close once 2x target bytes arrive (speed-up). */ + if (ctx->rx_byt < CA_RX_WCLOSE || win < mb_ecn_tw) { + *ece = ctx->rx_ece; + return false; + } } + + /* Time-integral mean over the actual window elapsed (never rx_tw). */ + ctx->rx_ece = (uint16_t) ((ctx->rx_acc << CA_SHFT) / win); + + if (ctx->rx_byt > 0) + mb_ecn_resize(ctx, win); + + *fcap = ctx->rx_cap; + + ctx->rx_win = t; + ctx->rx_acc = 0; + ctx->rx_byt = 0; + ctx->rx_cap = 0; /* the next window starts unknown */ + + *ece = ctx->rx_ece; + + return true; } bool mb_ecn_ctx_update_rcv(void * _ctx, size_t len, uint8_t ecn, - uint16_t * ece) + uint8_t cap, + uint16_t * ece, + uint8_t * fcap) +{ + struct timespec now; + struct mb_ecn_ctx * ctx = _ctx; + + clock_gettime(PTHREAD_COND_CLOCK, &now); + + return mb_ecn_rcv(ctx, len, ecn, cap, ece, fcap, TS_TO_UINT64(now)); +} + +static void mb_ecn_ece(struct mb_ecn_ctx * ctx, + uint16_t ece, + uint8_t cap, + uint64_t t) { - struct mb_ecn_ctx* ctx = _ctx; - bool update; + uint64_t tgt; - (void) len; + ctx->tx_ece = ece; + ctx->tx_cav = true; /* closed-loop feedback: leave slow start */ - if ((ctx->rx_ece | ecn) == 0) - return false; + /* An unsaturated signal means the queue drained: resume. */ + if (ece < (uint16_t) CA_ECE_MAX) + ctx->ai_hold = false; - if (ecn == 0) { /* End of congestion */ - ctx->rx_ece >>= 2; - update = ctx->rx_ece == 0; - } else { - if (ctx->rx_ece == 0) { /* Start of congestion */ - ctx->rx_ece = ecn; - ctx->rx_ctr = 0; - update = true; - } else { /* Congestion update */ - ctx->rx_ece -= ctx->rx_ece >> CA_SHFT; - ctx->rx_ece += ecn; - update = (ctx->rx_ctr++ & (CA_UPD - 1)) == true; - } + ctx->last_fb = t; + ctx->last_sig = t; + ctx->n_fb++; + + /* Scale the floor and AI slope to the path bottleneck. */ + if (cap != 0) { + tgt = cap_dec(cap) >> CA_CAP_SHFT; + if (tgt < CA_RATE_MIN) + tgt = CA_RATE_MIN; + + if (tgt > CA_RMIN_MAX) + tgt = CA_RMIN_MAX; + + if (tgt > ctx->rate_min) + ctx->rate_min += (tgt - ctx->rate_min) + >> CA_CAP_SM_SHFT; + else + ctx->rate_min -= (ctx->rate_min - tgt) + >> CA_CAP_SM_SHFT; + + ctx->ai_rate = 2 * ctx->rate_min; + ctx->tx_cap = cap; + ctx->last_cap = t; + ctx->n_cap++; } - *ece = ctx->rx_ece; + /* Control from the feedback path: a starved sender recovers. */ + if (t - ctx->last_ctrl < (uint64_t) CA_DT_CTRL) + return; - return update; -} + mb_ecn_ctrl(ctx, t - ctx->last_ctrl); + ctx->last_ctrl = t; +} void mb_ecn_ctx_update_ece(void * _ctx, - uint16_t ece) + uint16_t ece, + uint8_t cap) { - struct mb_ecn_ctx* ctx = _ctx; + struct timespec now; + struct mb_ecn_ctx * ctx = _ctx; + + clock_gettime(PTHREAD_COND_CLOCK, &now); + + mb_ecn_ece(ctx, ece, cap, TS_TO_UINT64(now)); +} + +/* Due when the path stayed quiet for a heartbeat interval; arms the gap. */ +bool mb_ecn_ctx_hb_due(void * _ctx, + uint64_t now) +{ + struct mb_ecn_ctx * ctx = _ctx; + uint64_t t_hb = mb_ecn_t_hb(ctx); + uint64_t last; - ctx->tx_ece = ece; - ctx->tx_ctr = 0; - ctx->tx_cav = true; + last = ctx->last_sig > ctx->last_hb ? ctx->last_sig : ctx->last_hb; + if (now - last < t_hb) + return false; + + ctx->last_hb = now; + + return true; } -int mb_ecn_calc_ecn(int fd, +/* Fold a heartbeat RTT sample into the ramp clock; also counts as life. */ +void mb_ecn_ctx_rtt(void * _ctx, + uint64_t now, + uint64_t rtt) +{ + struct mb_ecn_ctx * ctx = _ctx; + uint64_t tgt; + + tgt = (uint64_t) CA_SS_RTT_MUL * rtt; + if (tgt < (uint64_t) CA_SS_TC_MIN) /* track the true RTT both */ + tgt = CA_SS_TC_MIN; /* ways: overshoot ~e^{1/2} */ + + if (tgt > (uint64_t) CA_SS_TC_MAX) /* at the real RTT, not the */ + tgt = CA_SS_TC_MAX; /* declared worst case */ + + /* + * A control packet stuck behind a stalled reader returns an RTT + * worth seconds on a path worth milliseconds. Cap how far one + * sample carries the ramp, so a stall costs a step and a rise + * that holds still arrives within a few samples. + */ + if (tgt > ctx->ss_tc << CA_SS_TC_GRW) + tgt = ctx->ss_tc << CA_SS_TC_GRW; + + ctx->ss_tc += (tgt >> CA_RTT_SHFT) - (ctx->ss_tc >> CA_RTT_SHFT); + + ctx->last_sig = now; /* liveness only: never ages the ece signal */ + ctx->n_rtt++; +} + +int mb_ecn_calc_ecn(size_t queued, uint8_t * ecn, qoscube_t qc, - size_t len) + size_t mean) { - size_t q; + uint64_t u; + int q; + uint8_t mark; - (void) len; (void) qc; - q = ipcp_flow_queued(fd); + if (queued == 0 || mean == 0) + return 0; + + u = (uint64_t) CA_MARK_KNEE * mean; + + /* + * Difference of two quarter-log2 codes is a log-scale ratio: + * the same queue in units of U marks the same on any link. + */ + q = (int) cap_enc(queued) - (int) cap_enc(u); + if (q <= 0) + return 0; - *ecn |= (uint8_t) (q >> ECN_Q_SHFT); + /* Saturate: a deeper queue must not wrap to a low mark. */ + mark = q > 255 ? (uint8_t) 255 : (uint8_t) q; + + if (mark > *ecn) + *ecn = mark; return 0; } @@ -262,35 +1031,71 @@ ssize_t mb_ecn_print_stats(void * _ctx, char * buf, size_t len) { - struct mb_ecn_ctx* ctx = _ctx; - char * regime; + struct mb_ecn_ctx * ctx = _ctx; + char * regime; + uint64_t rate; + uint64_t peak; + int code; + uint16_t m; - if (len < 1024) + if (len < CA_STATS_STRLEN) return 0; - if (!ctx->tx_cav) + /* No signal seen: the rate is unconstrained drift, not a target. */ + rate = ctx->tx_cav ? ctx->rate : 0; + peak = ctx->tx_cav ? ctx->ss_peak : 0; + + /* Match the controller: MD fires on m, incl. the local fallback. */ + m = ctx->tx_ece > 0 ? ctx->tx_ece + : (uint16_t) (ctx->tx_loc << CA_SHFT); + + if (!ctx->tx_cav) { regime = "Slow start"; - else if (ctx->tx_ece) - regime = "Multiplicative dec"; - else + code = 0; + } else if (ctx->ai_hold) { + regime = "Loss recovery"; + code = 4; + } else if (ctx->src_limited) { + regime = "Source limited"; + code = 3; + } else if (m > 0) { + regime = "Proportional dec"; + code = 2; + } else { regime = "Additive inc"; + code = 1; + } sprintf(buf, "Congestion avoidance algorithm: %20s\n" "Upstream congestion level: %20u\n" - "Upstream packet counter: %20zu\n" "Downstream congestion level: %20u\n" - "Downstream packet counter: %20zu\n" - "Congestion window size (ns): %20" PRIu64 "\n" - "Packets in this window: %20zu\n" - "Bytes in this window: %20zu\n" - "Max bytes in this window: %20zu\n" - "Current congestion regime: %20s\n", + "Paced rate (bytes/s): %20" PRIu64 "\n" + "Pacer lead (bytes): %20" PRIu64 "\n" + "Congestion regime (code): %20d\n" + "Current congestion regime: %20s\n" + "Control steps (count): %20" PRIu64 "\n" + "Control time elapsed (ns): %20" PRIu64 "\n" + "Control time banked (ns): %20" PRIu64 "\n" + "Feedback updates (count): %20" PRIu64 "\n" + "Feedback timeouts (count): %20" PRIu64 "\n" + "Path capacity (bytes/s): %20" PRIu64 "\n" + "Capacity rate floor (bytes/s): %20" PRIu64 "\n" + "Capacity updates (count): %20" PRIu64 "\n" + "Slow start peak rate (bytes/s): %20" PRIu64 "\n" + "Signal-loss cuts (count): %20" PRIu64 "\n" + "Heartbeat RTT samples (count): %20" PRIu64 "\n" + "Ramp time constant (ns): %20" PRIu64 "\n", "Multi-bit ECN", - ctx->tx_ece, ctx->tx_ctr, - ctx->rx_ece, ctx->rx_ctr, (uint64_t) (1ULL << ctx->tx_mul), - ctx->tx_wpc, ctx->tx_wbc, ctx->tx_wbl, - regime); + ctx->tx_ece, + ctx->rx_ece, + rate, ctx->lead, code, + regime, + ctx->n_ctrl, ctx->t_ctrl, ctx->t_bank, + ctx->n_fb, ctx->n_ttl, + cap_dec(ctx->tx_cap), ctx->rate_min, ctx->n_cap, + peak, + ctx->n_loss, ctx->n_rtt, ctx->ss_tc); return strlen(buf); } diff --git a/src/ipcpd/unicast/ca/mb-ecn.h b/src/ipcpd/unicast/ca/mb-ecn.h index 9a2c8b49..08bb542d 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 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Multi-bit ECN Congestion Avoidance * @@ -25,27 +25,40 @@ #include "ops.h" +void mb_ecn_init(uint32_t rtt_ms); + void * mb_ecn_ctx_create(void); void mb_ecn_ctx_destroy(void * ctx); -ca_wnd_t mb_ecn_ctx_update_snd(void * ctx, - size_t len); +time_t mb_ecn_ctx_update_snd(void * ctx, + size_t len, + uint8_t lecn, + size_t flows, + uint64_t * ftag); bool mb_ecn_ctx_update_rcv(void * ctx, size_t len, uint8_t ecn, - uint16_t * ece); + uint8_t cap, + uint16_t * ece, + uint8_t * fcap); void mb_ecn_ctx_update_ece(void * ctx, - uint16_t ece); + uint16_t ece, + uint8_t cap); + +bool mb_ecn_ctx_hb_due(void * ctx, + uint64_t now); -void mb_ecn_wnd_wait(ca_wnd_t wnd); +void mb_ecn_ctx_rtt(void * ctx, + uint64_t now, + uint64_t rtt); -int mb_ecn_calc_ecn(int fd, +int mb_ecn_calc_ecn(size_t queued, uint8_t * ecn, qoscube_t qc, - size_t len); + size_t mean); ssize_t mb_ecn_print_stats(void * ctx, char * buf, diff --git a/src/ipcpd/unicast/ca/nop.c b/src/ipcpd/unicast/ca/nop.c index 617fc15b..7a2f72db 100644 --- a/src/ipcpd/unicast/ca/nop.c +++ b/src/ipcpd/unicast/ca/nop.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Dummy Congestion Avoidance * @@ -30,8 +30,8 @@ struct ca_ops nop_ca_ops = { .ctx_update_snd = nop_ctx_update_snd, .ctx_update_rcv = nop_ctx_update_rcv, .ctx_update_ece = nop_ctx_update_ece, - .wnd_wait = nop_wnd_wait, .calc_ecn = nop_calc_ecn, + .marks_ecn = false, .print_stats = NULL }; @@ -45,52 +45,55 @@ void nop_ctx_destroy(void * ctx) (void) ctx; } -ca_wnd_t nop_ctx_update_snd(void * ctx, - size_t len) +time_t nop_ctx_update_snd(void * ctx, + size_t len, + uint8_t lecn, + size_t flows, + uint64_t * ftag) { - ca_wnd_t wnd; - (void) ctx; (void) len; + (void) lecn; + (void) flows; + (void) ftag; - memset(&wnd, 0, sizeof(wnd)); - - return wnd; -} - -void nop_wnd_wait(ca_wnd_t wnd) -{ - (void) wnd; + return 0; } bool nop_ctx_update_rcv(void * ctx, size_t len, uint8_t ecn, - uint16_t * ece) + uint8_t cap, + uint16_t * ece, + uint8_t * fcap) { (void) ctx; (void) len; (void) ecn; + (void) cap; (void) ece; + (void) fcap; return false; } void nop_ctx_update_ece(void * ctx, - uint16_t ece) + uint16_t ece, + uint8_t cap) { (void) ctx; (void) ece; + (void) cap; } -int nop_calc_ecn(int fd, +int nop_calc_ecn(size_t queued, uint8_t * ecn, qoscube_t qc, - size_t len) + size_t mean) { - (void) fd; - (void) len; + (void) queued; + (void) mean; (void) ecn; (void) qc; diff --git a/src/ipcpd/unicast/ca/nop.h b/src/ipcpd/unicast/ca/nop.h index 248b198d..386a5310 100644 --- a/src/ipcpd/unicast/ca/nop.h +++ b/src/ipcpd/unicast/ca/nop.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Dummy Congestion Avoidance * @@ -29,23 +29,27 @@ void * nop_ctx_create(void); void nop_ctx_destroy(void * ctx); -ca_wnd_t nop_ctx_update_snd(void * ctx, - size_t len); +time_t nop_ctx_update_snd(void * ctx, + size_t len, + uint8_t lecn, + size_t flows, + uint64_t * ftag); bool nop_ctx_update_rcv(void * ctx, size_t len, uint8_t ecn, - uint16_t * ece); + uint8_t cap, + uint16_t * ece, + uint8_t * fcap); void nop_ctx_update_ece(void * ctx, - uint16_t ece); - -void nop_wnd_wait(ca_wnd_t wnd); + uint16_t ece, + uint8_t cap); -int nop_calc_ecn(int fd, +int nop_calc_ecn(size_t queued, uint8_t * ecn, qoscube_t qc, - size_t len); + size_t mean); extern struct ca_ops nop_ca_ops; diff --git a/src/ipcpd/unicast/ca/ops.h b/src/ipcpd/unicast/ca/ops.h index 3a7b7248..835fe0c5 100644 --- a/src/ipcpd/unicast/ca/ops.h +++ b/src/ipcpd/unicast/ca/ops.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Congestion avoidance policy ops * @@ -30,23 +30,39 @@ struct ca_ops { void (* ctx_destroy)(void * ctx); - ca_wnd_t (* ctx_update_snd)(void * ctx, - size_t len); + time_t (* ctx_update_snd)(void * ctx, + size_t len, + uint8_t lecn, + size_t flows, + uint64_t * ftag); bool (* ctx_update_rcv)(void * ctx, size_t len, uint8_t ecn, - uint16_t * ece); + uint8_t cap, + uint16_t * ece, + uint8_t * fcap); void (* ctx_update_ece)(void * ctx, - uint16_t ece); + uint16_t ece, + uint8_t cap); + + /* Optional, can be NULL: heartbeat pacing + RTT feedback. */ + bool (* ctx_hb_due)(void * ctx, + uint64_t now); - void (* wnd_wait)(ca_wnd_t wnd); + void (* ctx_rtt)(void * ctx, + uint64_t now, + uint64_t rtt); - int (* calc_ecn)(int fd, + /* queued and mean are bytes; their ratio is packets. */ + int (* calc_ecn)(size_t queued, uint8_t * ecn, qoscube_t qc, - size_t len); + size_t mean); + + /* True if calc_ecn inspects the queue; gates the lookup. */ + bool marks_ecn; /* Optional, can be NULL */ ssize_t (* print_stats)(void * ctx, diff --git a/src/ipcpd/unicast/ca/pol.h b/src/ipcpd/unicast/ca/pol.h index db0a1a11..bfb9cc2d 100644 --- a/src/ipcpd/unicast/ca/pol.h +++ b/src/ipcpd/unicast/ca/pol.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Congestion avoidance policies * diff --git a/src/ipcpd/unicast/ca/tests/CMakeLists.txt b/src/ipcpd/unicast/ca/tests/CMakeLists.txt new file mode 100644 index 00000000..20e2349d --- /dev/null +++ b/src/ipcpd/unicast/ca/tests/CMakeLists.txt @@ -0,0 +1,78 @@ +get_filename_component(CURRENT_SOURCE_PARENT_DIR + ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) +get_filename_component(CURRENT_BINARY_PARENT_DIR + ${CMAKE_CURRENT_BINARY_DIR} DIRECTORY) + +get_filename_component(UNICAST_SOURCE_DIR ${CURRENT_SOURCE_PARENT_DIR} DIRECTORY) +get_filename_component(UNICAST_BINARY_DIR ${CURRENT_BINARY_PARENT_DIR} DIRECTORY) + +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 + mb_ecn_test.c + ca_test.c + ) + +add_executable(${PARENT_DIR}_test ${${PARENT_DIR}_tests} + ${UNICAST_SOURCE_DIR}/ca.c + ${UNICAST_SOURCE_DIR}/cap.c + ${CURRENT_SOURCE_PARENT_DIR}/nop.c + ) + +target_include_directories(${PARENT_DIR}_test PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CURRENT_SOURCE_PARENT_DIR} + ${CURRENT_BINARY_PARENT_DIR} + ${UNICAST_SOURCE_DIR} + ${UNICAST_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_BINARY_DIR}/include + ${CMAKE_SOURCE_DIR}/src/ipcpd + ${CMAKE_BINARY_DIR}/src/ipcpd +) + +disable_test_logging_for_target(${PARENT_DIR}_test) +target_link_libraries(${PARENT_DIR}_test PRIVATE ouroboros-common) + +add_dependencies(build_tests ${PARENT_DIR}_test) + +ouroboros_register_tests(TARGET ${PARENT_DIR}_test TESTS ${${PARENT_DIR}_tests}) + +# The lab includes mb-ecn.c for its statics, so it needs its own binary +create_test_sourcelist(${PARENT_DIR}_lab_tests test_lab_suite.c + mb_ecn_lab_test.c + ) + +add_executable(${PARENT_DIR}_lab_test ${${PARENT_DIR}_lab_tests} + ${UNICAST_SOURCE_DIR}/cap.c + ) + +target_include_directories(${PARENT_DIR}_lab_test PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${CURRENT_SOURCE_PARENT_DIR} + ${CURRENT_BINARY_PARENT_DIR} + ${UNICAST_SOURCE_DIR} + ${UNICAST_BINARY_DIR} + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_BINARY_DIR}/include + ${CMAKE_SOURCE_DIR}/src/ipcpd + ${CMAKE_BINARY_DIR}/src/ipcpd +) + +disable_test_logging_for_target(${PARENT_DIR}_lab_test) +target_link_libraries(${PARENT_DIR}_lab_test PRIVATE ouroboros-common) + +if(MB_ECN_LAB_FULL) + target_compile_definitions(${PARENT_DIR}_lab_test PRIVATE MB_ECN_LAB_FULL) +endif() + +add_dependencies(build_tests ${PARENT_DIR}_lab_test) + +ouroboros_register_tests(TARGET ${PARENT_DIR}_lab_test + TESTS ${${PARENT_DIR}_lab_tests}) diff --git a/src/ipcpd/unicast/ca/tests/ca_test.c b/src/ipcpd/unicast/ca/tests/ca_test.c new file mode 100644 index 00000000..1b86eab8 --- /dev/null +++ b/src/ipcpd/unicast/ca/tests/ca_test.c @@ -0,0 +1,392 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Unit tests for the congestion-avoidance interface + * + * 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 "ca.h" + +#include <test/test.h> + +#define ADDR_A 0x1111ULL +#define ADDR_B 0x2222ULL + +static const struct { + enum pol_cong_avoid pol; + const char * name; +} ca_pols[] = { + { CA_NONE, "none" }, + { CA_MB_ECN, "mb-ecn" } +}; + +#define CA_POLS (sizeof(ca_pols) / sizeof(ca_pols[0])) + +static int test_ca_init_fini(enum pol_cong_avoid pol, + const char * name) +{ + TEST_START("(%s)", name); + + if (ca_init(pol, 100) < 0) { + printf("Failed to init ca for %s.\n", name); + goto fail; + } + + ca_fini(); + + TEST_SUCCESS("(%s)", name); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL("(%s)", name); + return TEST_RC_FAIL; +} + +static int test_ca_init_fini_all(void) +{ + int ret = 0; + size_t i; + + for (i = 0; i < CA_POLS; i++) + ret |= test_ca_init_fini(ca_pols[i].pol, ca_pols[i].name); + + return ret; +} + +static int test_ca_init_invalid(void) +{ + TEST_START(); + + if (ca_init(CA_INVALID, 100) == 0) { + printf("Init accepted an invalid policy.\n"); + ca_fini(); + goto fail; + } + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_ca_ctx_share(enum pol_cong_avoid pol, + const char * name) +{ + void * c1; + void * c2; + + TEST_START("(%s)", name); + + if (ca_init(pol, 100) < 0) { + printf("Failed to init ca for %s.\n", name); + goto fail; + } + + c1 = ca_ctx_get(ADDR_A, QOS_CUBE_BE); + if (c1 == NULL) { + printf("Failed to get ctx.\n"); + goto fail_init; + } + + c2 = ca_ctx_get(ADDR_A, QOS_CUBE_BE); + if (c2 == NULL) { + printf("Failed to get second ctx.\n"); + goto fail_c1; + } + +#ifdef IPCP_CA_PER_FLOW + if (c1 == c2) { + printf("Per-flow build shared a ctx across flows.\n"); + goto fail_c2; + } +#else + if (c1 != c2) { + printf("Aggregate build did not share ctx per (addr, qc).\n"); + goto fail_c2; + } +#endif + ca_ctx_put(c2); + ca_ctx_put(c1); + + ca_fini(); + + TEST_SUCCESS("(%s)", name); + + return TEST_RC_SUCCESS; + fail_c2: + ca_ctx_put(c2); + fail_c1: + ca_ctx_put(c1); + fail_init: + ca_fini(); + fail: + TEST_FAIL("(%s)", name); + return TEST_RC_FAIL; +} + +static int test_ca_ctx_share_all(void) +{ + int ret = 0; + size_t i; + + for (i = 0; i < CA_POLS; i++) + ret |= test_ca_ctx_share(ca_pols[i].pol, ca_pols[i].name); + + return ret; +} + +static int test_ca_ctx_distinct(void) +{ + void * a_be; + void * b_be; + void * a_video; + + TEST_START(); + + if (ca_init(CA_NONE, 100) < 0) { + printf("Failed to init ca.\n"); + goto fail; + } + + a_be = ca_ctx_get(ADDR_A, QOS_CUBE_BE); + if (a_be == NULL) { + printf("Failed to get ctx.\n"); + goto fail_init; + } + + b_be = ca_ctx_get(ADDR_B, QOS_CUBE_BE); + if (b_be == NULL) { + printf("Failed to get ctx.\n"); + goto fail_a_be; + } + + a_video = ca_ctx_get(ADDR_A, QOS_CUBE_VIDEO); + if (a_video == NULL) { + printf("Failed to get ctx.\n"); + goto fail_b_be; + } + + if (a_be == b_be) { + printf("Distinct addresses shared a ctx.\n"); + goto fail_a_video; + } + + if (a_be == a_video) { + printf("Distinct qos cubes shared a ctx.\n"); + goto fail_a_video; + } + + ca_ctx_put(a_video); + ca_ctx_put(b_be); + ca_ctx_put(a_be); + + ca_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_a_video: + ca_ctx_put(a_video); + fail_b_be: + ca_ctx_put(b_be); + fail_a_be: + ca_ctx_put(a_be); + fail_init: + ca_fini(); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Refcount survival is an aggregate-only property. */ +#ifndef IPCP_CA_PER_FLOW +static int test_ca_ctx_refcount(void) +{ + void * c1; + void * c3; + + TEST_START(); + + if (ca_init(CA_NONE, 100) < 0) { + printf("Failed to init ca.\n"); + goto fail; + } + + c1 = ca_ctx_get(ADDR_A, QOS_CUBE_BE); /* refs = 1 */ + if (c1 == NULL) { + printf("Failed to get ctx.\n"); + goto fail_init; + } + + if (ca_ctx_get(ADDR_A, QOS_CUBE_BE) == NULL) { /* refs = 2 */ + printf("Failed to get second ref.\n"); + goto fail_c1; + } + + ca_ctx_put(c1); /* refs = 1 */ + + c3 = ca_ctx_get(ADDR_A, QOS_CUBE_BE); /* refs = 2 */ + if (c3 == NULL) { + printf("Failed to get third ref.\n"); + goto fail_c1; + } + + if (c3 != c1) { + printf("Refcounted ctx freed while still referenced.\n"); + goto fail_c3; + } + + ca_ctx_put(c3); + ca_ctx_put(c1); + + ca_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_c3: + ca_ctx_put(c3); + fail_c1: + ca_ctx_put(c1); + fail_init: + ca_fini(); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* The last put frees the interned ctx; a fresh get recreates it. */ +static int test_ca_ctx_recreate(void) +{ + void * c1; + void * c2; + void * c3; + + TEST_START(); + + if (ca_init(CA_NONE, 100) < 0) { + printf("Failed to init ca.\n"); + goto fail; + } + + c1 = ca_ctx_get(ADDR_A, QOS_CUBE_BE); /* refs = 1 */ + if (c1 == NULL) { + printf("Failed to get ctx.\n"); + goto fail_init; + } + + ca_ctx_put(c1); /* refs = 0: freed and de-interned */ + + c2 = ca_ctx_get(ADDR_A, QOS_CUBE_BE); /* fresh entry */ + if (c2 == NULL) { + printf("Get after release did not recreate.\n"); + goto fail_init; + } + + c3 = ca_ctx_get(ADDR_A, QOS_CUBE_BE); /* refs = 2: shares */ + if (c3 == NULL) { + printf("Failed to share recreated ctx.\n"); + goto fail_c2; + } + + if (c3 != c2) { + printf("Recreated ctx did not intern.\n"); + goto fail_c3; + } + + ca_ctx_put(c3); + ca_ctx_put(c2); + + ca_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_c3: + ca_ctx_put(c3); + fail_c2: + ca_ctx_put(c2); + fail_init: + ca_fini(); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* ca_fini drains a ctx a flow left interned, with no leak. */ +static int test_ca_fini_drains(void) +{ + void * c1; + void * c2; + + TEST_START(); + + if (ca_init(CA_NONE, 100) < 0) { + printf("Failed to init ca.\n"); + goto fail; + } + + c1 = ca_ctx_get(ADDR_A, QOS_CUBE_BE); /* refs = 1 */ + if (c1 == NULL) { + printf("Failed to get ctx.\n"); + goto fail_init; + } + + c2 = ca_ctx_get(ADDR_A, QOS_CUBE_BE); /* refs = 2 */ + if (c2 == NULL) { + printf("Failed to get second ref.\n"); + goto fail_init; + } + + /* Leave both refs live: ca_fini must drain and free the ctx. */ + ca_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_init: + ca_fini(); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} +#endif /* !IPCP_CA_PER_FLOW */ + +int ca_test(int argc, + char ** argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + + ret |= test_ca_init_fini_all(); + ret |= test_ca_init_invalid(); + ret |= test_ca_ctx_share_all(); + ret |= test_ca_ctx_distinct(); +#ifndef IPCP_CA_PER_FLOW + ret |= test_ca_ctx_refcount(); + ret |= test_ca_ctx_recreate(); + ret |= test_ca_fini_drains(); +#endif + return ret; +} diff --git a/src/ipcpd/unicast/ca/tests/mb_ecn_lab_test.c b/src/ipcpd/unicast/ca/tests/mb_ecn_lab_test.c new file mode 100644 index 00000000..dac5e8ac --- /dev/null +++ b/src/ipcpd/unicast/ca/tests/mb_ecn_lab_test.c @@ -0,0 +1,1294 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Shared-bottleneck lab for multi-bit ECN congestion avoidance + * + * 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 "mb-ecn.c" +#include <test/test.h> + +#define MS (MILLION) /* one millisecond in ns */ +#define LEN 1000 /* default packet size (bytes) */ + +/* Create a context with the clock zeroed for deterministic time steps. */ +static struct mb_ecn_ctx * mk_ctx(void) +{ + struct mb_ecn_ctx * ctx; + + ctx = mb_ecn_ctx_create(); + if (ctx == NULL) + return NULL; + + ctx->rx_ts = 0; + ctx->rx_win = 0; + ctx->last_ts = 0; + ctx->last_ctrl = 0; + ctx->last_fb = 0; + ctx->last_sig = 0; + ctx->last_loc = 0; + ctx->last_cap = 0; + + ctx->snd_byt = 0; + ctx->snd_win = 0; + ctx->snd_r0 = CA_RATE_INIT; + ctx->snd_rate = CA_RATE_INIT; + ctx->backlogged = true; + ctx->src_limited = false; + ctx->started = false; + ctx->ss_tc = 20 * MS; /* fixed slope for deterministic SS */ + + return ctx; +} + +/* + * ------------------------------------------------------------------ + * Lab: packet-level shared-bottleneck simulator. + * + * Exact-time FIFO link of capacity cap: a packet departs at + * max(enqueue, previous departure) + len / cap. Packets are marked + * at enqueue from the instantaneous byte queue by mb_ecn_calc_ecn, + * the same function the forwarding path calls. Delivered packets + * drive a per-flow receiver estimator (mb_ecn_rcv); every window + * close is fed back to the sender as ece after a one-way lag, + * including the ece 0 release (fa.c). The sender sees its own + * previous packet's mark as the local fallback (fa.c l_ecn) and + * heartbeat pongs keep liveness. Greedy flows send whenever the + * pacer allows; CBR flows follow an absolute schedule. A tick every + * LAB_SAMPLE drains the link between sends, so feedback queued by a + * departure is due on time even while every flow sits idle. With + * cfg.shared every flow runs on one ctx, as a production build does, + * and the flow count follows t0, t1 and the churn period. + * + * The fixpoint tests assert; the sweep always returns success: an + * instrument, not a regression test. + * ------------------------------------------------------------------ + */ + +#define LAB_MAXF 8 /* most flows on one link */ +#define LAB_FIFO 16384 /* bottleneck ring, packets */ +#define LAB_FBQ 64 /* pending feedback ring */ +#define LAB_NONE UINT64_MAX /* no pending event */ +#define LAB_SAMPLE (5 * MS) /* service tick */ + +struct lab_pkt { + uint64_t dep; /* departure time (ns) */ + uint8_t ecn; + uint8_t f; /* flow index */ +}; + +struct lab_fb { + uint64_t t; + uint16_t ece; + uint8_t fcap; +}; + +struct lab_flow { + struct mb_ecn_ctx * snd; + struct mb_ecn_ctx * rcv; + uint64_t t_snd; /* next send attempt (ns) */ + uint64_t last; /* ctx clock high-water (ns) */ + uint64_t ftag; + uint64_t ia; /* app interval, 0 = greedy */ + uint64_t app; /* next app slot (ns) */ + uint64_t lag; /* feedback one-way lag (ns) */ + uint8_t lecn; /* own previous packet's mark */ + struct lab_fb fbq[LAB_FBQ]; + size_t fb_h; + size_t fb_n; + uint64_t hb_t; /* pong due, LAB_NONE = none */ + uint64_t hb_rtt; + /* metrics, accumulated past warmup */ + uint64_t m_t; /* last accounting time */ + uint64_t dlv; /* delivered bytes */ + uint64_t dlv2; /* delivered in score window */ + uint64_t r_int; /* integral of rate dt */ + uint64_t r_lo; + uint64_t r_hi; + uint64_t lead_B; /* lead-term cut volume */ + uint64_t prop_B; /* proportional cut volume */ + uint64_t cuts; /* >45% single-event cuts */ + uint64_t hold_ns; /* time with ai_hold set */ + uint64_t lim_ns; /* time src_limited (latch) */ + uint64_t idl_ns; /* time the backlog test off */ +}; + +struct lab_link { + uint64_t cap; /* bytes/s */ + uint8_t cc; /* stamped capacity code */ + uint64_t t_srv; /* line busy until (ns) */ + uint64_t q; /* queued bytes */ + uint64_t qmax; /* blocking threshold (bytes) */ + struct lab_pkt pk[LAB_FIFO]; + size_t h; + size_t n; + /* metrics */ + uint64_t q_t; /* last q-change time */ + uint64_t q_int; /* integral of q dt */ + uint64_t mk_int; /* integral of ece(q) dt */ + uint64_t e_from; /* empty-dwell start */ + uint64_t e_ns; /* empty time past warmup */ + size_t e_eps; /* empty episodes past warmup */ + uint64_t dlv; /* delivered bytes */ +}; + +struct lab_cfg { + const char * name; + uint64_t cap; /* bytes/s */ + uint64_t dur; /* run length (ns) */ + uint64_t wu; /* warmup excluded (ns) */ + size_t len; /* packet size (bytes) */ + uint64_t qmax; /* bytes */ + size_t n; /* flows, up to LAB_MAXF */ + uint64_t ia[LAB_MAXF]; /* app interval, 0 = greedy */ + uint64_t lag[LAB_MAXF]; /* one-way feedback lag (ns) */ + bool no_loc; /* disable local-mark path */ + uint64_t st_d; /* service stall length (ns) */ + uint64_t st_p; /* stall period, 0 = never */ + unsigned st_f; /* stalled-flow mask, 0 = all */ + uint64_t t0[LAB_MAXF]; /* flow start offsets (ns) */ + uint64_t t1[LAB_MAXF]; /* flow stop, 0 = runs to end */ + uint64_t r0[LAB_MAXF]; /* seed rate, 0 = slow start */ + uint64_t sc_lo; /* score window (ns), as the */ + uint64_t sc_hi; /* integration test scores */ + bool shared; /* one ctx for every flow */ + uint64_t ch_p; /* churn period, 0 = never */ + unsigned ch_f; /* churning flow mask */ +}; + +static struct lab_link lab_lnk; +static struct lab_flow lab_fl[LAB_MAXF]; +static uint64_t lab_sc_lo; +static uint64_t lab_sc_hi; +static size_t lab_len; + +/* + * Does flow i hold the ctx at t? A churning flow holds it for the + * first half of every ch_p and is gone for the second. + */ +static bool lab_up(const struct lab_cfg * c, + size_t i, + uint64_t t) +{ + if (t < c->t0[i]) + return false; + + if (c->t1[i] > 0 && t >= c->t1[i]) + return false; + + if (c->ch_p == 0 || ((c->ch_f >> i) & 1) == 0) + return true; + + return t % c->ch_p < c->ch_p / 2; +} + +/* Flows holding the ctx at t, the count ca_ctx_get refcounts. */ +static size_t lab_live(const struct lab_cfg * c, + uint64_t t) +{ + size_t n = 0; + size_t i; + + for (i = 0; i < c->n; i++) + if (lab_up(c, i, t)) + n++; + + return n > 0 ? n : 1; +} + +/* The bottleneck marks with the production function, nothing else. */ +static uint8_t lab_mark(uint64_t q) +{ + uint8_t e = 0; + + if (q == 0) + return 0; + + mb_ecn_calc_ecn(q, &e, QOS_CUBE_BE, lab_len); + + return e; +} + +/* Track the queue integral, the mark integral and empty dwells. */ +static void lab_q_acct(struct lab_link * l, + uint64_t now, + uint64_t wu) +{ + uint64_t dt = now - l->q_t; + + if (l->q_t >= wu && dt > 0) { + l->q_int += l->q * dt; + l->mk_int += (uint64_t) lab_mark(l->q) * 32 * dt; + } + + if (l->q == 0) { + if (l->e_from == LAB_NONE) + l->e_from = l->q_t; + } else if (l->e_from != LAB_NONE) { + if (now >= wu) { + uint64_t f = l->e_from > wu ? l->e_from : wu; + l->e_ns += l->q_t > f ? l->q_t - f : 0; + l->e_eps++; + } + l->e_from = LAB_NONE; + } + + l->q_t = now; +} + +/* Deliver everything due; receiver estimator feeds the fb ring. */ +static void lab_service(struct lab_link * l, + uint64_t now, + uint64_t wu) +{ + uint16_t ece; + uint8_t fcap; + + while (l->n > 0 && l->pk[l->h].dep <= now) { + struct lab_pkt * p = &l->pk[l->h]; + struct lab_flow * f = &lab_fl[p->f]; + + lab_q_acct(l, p->dep, wu); + l->q -= lab_len; + + if (p->dep >= wu) { + l->dlv += lab_len; + f->dlv += lab_len; + } + + if (p->dep >= lab_sc_lo && p->dep < lab_sc_hi) + f->dlv2 += lab_len; + + if (mb_ecn_rcv(f->rcv, lab_len, p->ecn, l->cc, &ece, &fcap, + p->dep) && + f->fb_n < LAB_FBQ) { + size_t i = (f->fb_h + f->fb_n++) % LAB_FBQ; + f->fbq[i].t = p->dep + f->lag; + f->fbq[i].ece = ece; + f->fbq[i].fcap = fcap; + } + + l->h = (l->h + 1) % LAB_FIFO; + l->n--; + } +} + +/* Integrate rate, regime dwell and extrema between a flow's events. */ +static void lab_f_acct(struct lab_flow * f, + uint64_t now, + uint64_t wu) +{ + struct mb_ecn_ctx * c = f->snd; + uint64_t dt; + uint16_t m; + + if (now < f->m_t) + now = f->m_t; + + dt = now - f->m_t; + if (f->m_t >= wu && dt > 0) { + f->r_int += c->rate * dt; + + if (c->ai_hold) + f->hold_ns += dt; + + if (c->src_limited) + f->lim_ns += dt; + + if (!c->backlogged) + f->idl_ns += dt; + + m = c->tx_ece > 0 ? c->tx_ece + : (uint16_t) (c->tx_loc << CA_SHFT); + + if (m > CA_ECE_MAX) + m = CA_ECE_MAX; + f->prop_B += c->rate / CA_ECE_REF * m * dt / BILLION; + + if (c->rate < f->r_lo) + f->r_lo = c->rate; + + if (c->rate > f->r_hi) + f->r_hi = c->rate; + } + + f->m_t = now; +} + +/* One send attempt; returns false when blocked on a full buffer. */ +static bool lab_send(struct lab_link * l, + struct lab_flow * f, + size_t fi, + size_t nf, + uint64_t wu, + bool no_loc) +{ + uint64_t t = f->t_snd; + uint64_t r0; + uint64_t dep; + uint8_t ecn; + time_t w; + + lab_service(l, t, wu); + + if (l->q + lab_len > l->qmax) { /* blocking write */ + f->t_snd = l->pk[l->h].dep; + return false; + } + + lab_f_acct(f, t, wu); + + ecn = lab_mark(l->q); + + r0 = f->snd->rate; + + mb_ecn_flows(f->snd, nf, t); + + if (!no_loc) + mb_ecn_loc(f->snd, f->lecn, t); + + w = mb_ecn_snd(f->snd, lab_len, t, &f->ftag); + + if (f->snd->rate * 100 < r0 * 55) + f->cuts++; + + f->lecn = ecn; + f->last = t; + + lab_q_acct(l, t, wu); + + dep = (t > l->t_srv ? t : l->t_srv) + lab_len * BILLION / l->cap; + l->t_srv = dep; + l->pk[(l->h + l->n) % LAB_FIFO].dep = dep; + l->pk[(l->h + l->n) % LAB_FIFO].ecn = ecn; + l->pk[(l->h + l->n) % LAB_FIFO].f = (uint8_t) fi; + l->n++; + l->q += lab_len; + + if (mb_ecn_ctx_hb_due(f->snd, t) && f->hb_t == LAB_NONE) { + f->hb_rtt = l->q * BILLION / l->cap + 2 * f->lag; + f->hb_t = t + f->hb_rtt; + } + + if (f->ia == 0) { + f->t_snd = t + (w > 0 ? (uint64_t) w : 1); + } else { + f->app += f->ia; + f->t_snd = f->app > t + (uint64_t) w ? f->app + : t + (uint64_t) w; + } + + return true; +} + +/* Apply one queued feedback to the sender, with lead accounting. */ +static void lab_fb_apply(struct lab_flow * f, + uint64_t wu) +{ + struct lab_fb * fb = &f->fbq[f->fb_h]; + uint64_t t = fb->t > f->last ? fb->t : f->last; + uint64_t r0 = f->snd->rate; + uint16_t step; + bool up; + + lab_f_acct(f, t, wu); + + if (t >= wu) { + up = fb->ece > f->snd->tx_ecp; + step = up ? fb->ece - f->snd->tx_ecp + : f->snd->tx_ecp - fb->ece; + + if (step > CA_ECE_REF) + step = CA_ECE_REF; + + if (up) + f->lead_B += r0 * step + / (CA_ECE_REF * CA_MD_KD_DIV); + } + + mb_ecn_ece(f->snd, fb->ece, fb->fcap, t); + + if (f->snd->rate * 100 < r0 * 55) + f->cuts++; + + f->last = t; + f->fb_h = (f->fb_h + 1) % LAB_FBQ; + f->fb_n--; +} + +static void lab_run(const struct lab_cfg * c) +{ + struct lab_link * l = &lab_lnk; + uint64_t smp = 0; + uint64_t st_t; + uint64_t span; + double secs; + size_t i; + + memset(l, 0, sizeof(*l)); + + l->cap = c->cap; + l->cc = cap_enc(c->cap); + l->qmax = c->qmax; + l->e_from = LAB_NONE; + + memset(lab_fl, 0, sizeof(lab_fl)); + + for (i = 0; i < c->n; i++) { + struct lab_flow * f = &lab_fl[i]; + + /* Production interns one ctx per (peer, qos cube). */ + if (c->shared && i > 0) { + f->snd = lab_fl[0].snd; + f->rcv = lab_fl[0].rcv; + } else { + f->snd = mk_ctx(); + f->rcv = mk_ctx(); + } + + if (f->snd == NULL || f->rcv == NULL) { + printf("lab: no memory.\n"); + goto fail_ctx; + } + + f->ia = c->ia[i]; + f->lag = c->lag[i]; + f->hb_t = LAB_NONE; + f->r_lo = UINT64_MAX; + f->t_snd = c->t0[i]; + f->app = c->t0[i]; + f->m_t = c->t0[i]; + /* Layer-declared RTT seed; pongs then track truth. */ + f->snd->ss_tc = 2 * CA_SS_RTT_DEF * MILLION; + + /* Seeded: start in AIMD, so the sweep probes the + attractor and not the ramp. */ + if (c->r0[i] == 0) + continue; + + f->snd->rate = c->r0[i]; + f->snd->inv_rate = mb_ecn_rate_inv(c->r0[i]); + f->snd->snd_r0 = c->r0[i]; + f->snd->snd_rate = c->r0[i]; + f->snd->tx_cav = true; + } + + lab_len = c->len; + lab_sc_lo = c->sc_lo; + lab_sc_hi = c->sc_hi; + + st_t = c->st_p > 0 ? c->st_p : LAB_NONE; + + while (true) { + uint64_t nxt = LAB_NONE; + int ev = -1; /* flow * 4 + kind */ + + /* + * Sender-side service stall: the scheduler feeding + * the transmit queue pauses for st_d, the queue + * drains clean, and the resume bursts the backlog + * through the marker (dsched untrack/starve model). + * Jitter the period so it cannot phase-lock. + */ + if (st_t != LAB_NONE && smp >= st_t) { + uint64_t end = st_t + c->st_d; + unsigned msk = c->st_f == 0 ? 3 : c->st_f; + + for (i = 0; i < c->n; i++) + if (((msk >> i) & 1) && lab_fl[i].t_snd < end) + lab_fl[i].t_snd = end; + st_t += c->st_p + (st_t / c->st_p % 3) * c->st_p / 5; + } + + for (i = 0; i < c->n; i++) { + struct lab_flow * f = &lab_fl[i]; + + if (c->t1[i] > 0 && f->t_snd >= c->t1[i]) { + f->t_snd = LAB_NONE; + } else if (c->ch_p > 0 && !lab_up(c, i, f->t_snd)) { + /* Gone: the app resumes next period. */ + f->t_snd = (f->t_snd / c->ch_p + 1) * c->ch_p; + f->app = f->t_snd; + } + + if (f->t_snd < nxt) { + nxt = f->t_snd; + ev = (int) i * 4; + } + if (f->fb_n > 0 && f->fbq[f->fb_h].t < nxt) { + nxt = f->fbq[f->fb_h].t; + ev = (int) i * 4 + 1; + } + if (f->hb_t < nxt) { + nxt = f->hb_t; + ev = (int) i * 4 + 2; + } + } + + if (smp < nxt) { + nxt = smp; + ev = -2; + } + + if (nxt >= c->dur) + break; + + if (ev == -2) { + lab_service(l, smp, c->wu); + smp += LAB_SAMPLE; + continue; + } + + i = (size_t) (ev / 4); + switch (ev % 4) { + case 0: + (void) lab_send(l, &lab_fl[i], i, + c->shared ? lab_live(c, nxt) : 1, + c->wu, c->no_loc); + break; + case 1: + lab_fb_apply(&lab_fl[i], c->wu); + break; + default: + lab_f_acct(&lab_fl[i], lab_fl[i].hb_t, c->wu); + if (lab_fl[i].hb_t > lab_fl[i].last) + lab_fl[i].last = lab_fl[i].hb_t; + mb_ecn_ctx_rtt(lab_fl[i].snd, lab_fl[i].last, + lab_fl[i].hb_rtt); + lab_fl[i].hb_t = LAB_NONE; + break; + } + } + + lab_service(l, c->dur, c->wu); + lab_q_acct(l, c->dur, c->wu); + + span = c->dur - c->wu; + secs = (double) span / BILLION; + + printf("%-14s C %5.2f MB/s n %zu | util %5.1f%% " + "q %6.1f pkt mk %5.1f e%% %4.1f eps %3zu\n", + c->name, (double) c->cap / MILLION, c->n, + 100.0 * (double) l->dlv / ((double) c->cap * secs), + (double) l->q_int / ((double) span * c->len), + (double) l->mk_int / (double) span, + 100.0 * (double) l->e_ns / (double) span, + l->e_eps); + + for (i = 0; i < c->n; i++) { + struct lab_flow * f = &lab_fl[i]; + struct mb_ecn_ctx * s = f->snd; + + lab_f_acct(f, c->dur, c->wu); + + if (c->sc_hi > c->sc_lo) + printf(" f%zu score %.3f Mb/s in [%llu,%llu)s\n", + i, 8.0 * (double) f->dlv2 / + ((double) (c->sc_hi - c->sc_lo) / BILLION + * MILLION), + (unsigned long long) (c->sc_lo / BILLION), + (unsigned long long) (c->sc_hi / BILLION)); + printf(" f%zu %s dlv %5.3f Mb/s rate mean %8.0f " + "lo %8" PRIu64 " hi %8" PRIu64 "\n" + " lead %8" PRIu64 " prop %8" PRIu64 + " cuts %4" PRIu64 " hold %4.1f%% lim %4.1f%%" + " idl %4.1f%% loss %" PRIu64 "\n", + i, f->ia == 0 ? "gdy" : "cbr", + 8.0 * (double) f->dlv / ((double) secs * MILLION), + (double) f->r_int / (double) span, + f->r_lo == UINT64_MAX ? 0 : f->r_lo, f->r_hi, + f->lead_B, f->prop_B, f->cuts, + 100.0 * (double) f->hold_ns / (double) span, + 100.0 * (double) f->lim_ns / (double) span, + 100.0 * (double) f->idl_ns / (double) span, + s->n_loss); + + if (c->shared && i > 0) + continue; + + mb_ecn_ctx_destroy(f->snd); + mb_ecn_ctx_destroy(f->rcv); + } + + return; + fail_ctx: + for (i = 0; i < c->n; i++) { + if (c->shared && i > 0) + break; + + mb_ecn_ctx_destroy(lab_fl[i].snd); + mb_ecn_ctx_destroy(lab_fl[i].rcv); + } +} + +/* + * Faithful test_cbr_protection / test_single_flow_slow_link protocol: + * 3-node chain, bottleneck one hop past the sender (no local mark), + * ~2 ms feedback path, CBR from t = 0, greedy joining at 300 ms, + * scored over the integration test's own window (slow start and + * convergence included, as the real assertion sees them). + */ +static void lab_cfg_std(struct lab_cfg * c, + const char * name, + uint64_t cap, + size_t n) +{ + size_t i; + + memset(c, 0, sizeof(*c)); + + c->name = name; + c->cap = cap; + c->n = n; + c->len = LEN; + /* 1024 = SSM_RBUFF_SIZE (cmake/config/lib/ssm.cmake). */ + c->qmax = 1024 * c->len; + c->no_loc = true; + + for (i = 0; i < n; i++) + c->lag[i] = 2 * MS; + + if (n == 2) { /* cbr_protection */ + c->ia[1] = c->len * BILLION / 375000; + c->t0[0] = 300 * MS; + c->dur = 35ULL * BILLION; + c->wu = 30ULL * BILLION; + c->sc_lo = 5ULL * BILLION; + c->sc_hi = 30ULL * BILLION; + } else { /* single_flow_slow_link */ + c->dur = 95ULL * BILLION; + c->wu = 55ULL * BILLION; + c->sc_lo = 20ULL * BILLION; + c->sc_hi = 50ULL * BILLION; + } +} + +/* Spread the seeds may end on and still count as one attractor. */ +#define LAB_FIX_TOL 0.10 + +/* + * Two flows on one bottleneck reach the same split whatever they start + * from: the difference mode contracts, so the seed cannot survive in + * the answer. A second attractor shows as seeds that disagree, a bias + * as agreement away from 1. + */ +static int test_mb_ecn_lab_fixpoint(uint64_t cap, + uint64_t lag) +{ + static struct lab_cfg c; + static const unsigned num[] = { 1, 1, 4 }; + static const unsigned den[] = { 4, 1, 1 }; + uint64_t kb = cap * 8 / 1000; + uint64_t ms = lag / MS; + double r[3]; + double lo; + double hi; + size_t i; + + TEST_START("(%" PRIu64 " kb/s, lag %" PRIu64 " ms)", kb, ms); + + for (i = 0; i < 3; i++) { + lab_cfg_std(&c, "fixpoint", cap, 2); + + /* 10 Gb/s carries 9000 B frames; below 1 Gb/s, 1000 B. */ + c.len = cap >= 125000000 ? 9000 : LEN; + c.qmax = 1024 * c.len; + + c.ia[1] = 0; /* both greedy */ + c.t0[0] = 0; + c.lag[0] = lag; + c.lag[1] = lag; + c.dur = 60ULL * BILLION; + c.wu = 30ULL * BILLION; + c.sc_lo = 30ULL * BILLION; + c.sc_hi = 60ULL * BILLION; + c.r0[0] = cap * num[i] / (num[i] + den[i]); + c.r0[1] = cap * den[i] / (num[i] + den[i]); + + lab_run(&c); + + if (lab_fl[1].dlv2 == 0) { + printf("seed %u:%u starved a flow.\n", num[i], den[i]); + goto fail; + } + + r[i] = (double) lab_fl[0].dlv2 / (double) lab_fl[1].dlv2; + } + + lo = hi = r[0]; + + for (i = 1; i < 3; i++) { + if (r[i] < lo) + lo = r[i]; + + if (r[i] > hi) + hi = r[i]; + } + + if (hi > lo * (1.0 + LAB_FIX_TOL)) { + printf("seeds disagree: %.3f %.3f %.3f.\n", r[0], r[1], r[2]); + goto fail; + } + + if (lo < 1.0 - LAB_FIX_TOL || hi > 1.0 + LAB_FIX_TOL) { + printf("split %.3f..%.3f is not fair.\n", lo, hi); + goto fail; + } + + TEST_SUCCESS("(%" PRIu64 " kb/s, lag %" PRIu64 " ms)", kb, ms); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL("(%" PRIu64 " kb/s, lag %" PRIu64 " ms)", kb, ms); + return TEST_RC_FAIL; +} + +static int test_mb_ecn_lab_fixpoint_all(void) +{ +#ifdef MB_ECN_LAB_FULL + static const uint64_t cap[] = { 1250000000, 12500000, + 1250000, 62500 }; +#else + static const uint64_t cap[] = { 1250000, 62500 }; +#endif + static const uint64_t lag[] = { 2 * MS, 42 * MS }; + int ret = 0; + size_t i; + size_t j; + + for (i = 0; i < sizeof(cap) / sizeof(cap[0]); i++) + for (j = 0; j < sizeof(lag) / sizeof(lag[0]); j++) + ret |= test_mb_ecn_lab_fixpoint(cap[i], lag[j]); + + return ret; +} + +/* Seed weights: even, graded, and graded reversed. */ +static const unsigned lab_n8_w[3][LAB_MAXF] = { + { 1, 1, 1, 1, 1, 1, 1, 1 }, + { 1, 2, 3, 4, 5, 6, 7, 8 }, + { 8, 7, 6, 5, 4, 3, 2, 1 } +}; + +/* Spread across eight flows that still counts as one even split. */ +#define LAB_N8_TOL 0.10 + +/* + * Below this, packet-size quantisation dominates the spread, so the + * split is not scored here; starvation (lo == 0) and the utilisation + * gate still apply, so the exemption is narrow. + */ +#define LAB_N8_FAIR 125000 /* bytes/s, 1 Mb/s */ + +/* Aggregate the link has to carry for a run to say anything at all. */ +#define LAB_N8_UTIL 2 /* divisor: half of capacity */ + +/* + * Eight flows on one bottleneck. The additive increase is per flow, so + * both the aggregate probe and the contraction rate scale with the flow + * count, and this is where that scaling shows. + */ +static int test_mb_ecn_lab_fixpoint_n8(uint64_t cap, + uint64_t lag) +{ + static struct lab_cfg c; + uint64_t kb = cap * 8 / 1000; + uint64_t ms = lag / MS; + double worst = 1.0; + uint64_t want; + uint64_t tot; + uint64_t sum; + uint64_t lo; + uint64_t hi; + size_t i; + size_t j; + + TEST_START("(%" PRIu64 " kb/s, lag %" PRIu64 " ms)", kb, ms); + + for (i = 0; i < 3; i++) { + lab_cfg_std(&c, "fixpoint-n8", cap, LAB_MAXF); + + /* 10 Gb/s carries 9000 B frames; below 1 Gb/s, 1000 B. */ + c.len = cap >= 125000000 ? 9000 : LEN; + c.qmax = 1024 * c.len; + + sum = 0; + + for (j = 0; j < LAB_MAXF; j++) + sum += lab_n8_w[i][j]; + + for (j = 0; j < LAB_MAXF; j++) { + c.ia[j] = 0; /* all greedy */ + c.t0[j] = 0; + c.lag[j] = lag; + c.r0[j] = cap * lab_n8_w[i][j] / sum; + } + + c.dur = 400ULL * BILLION; + c.wu = 200ULL * BILLION; + c.sc_lo = 200ULL * BILLION; + c.sc_hi = 400ULL * BILLION; + + lab_run(&c); + + tot = 0; + lo = lab_fl[0].dlv2; + hi = lab_fl[0].dlv2; + + for (j = 0; j < LAB_MAXF; j++) { + tot += lab_fl[j].dlv2; + if (lab_fl[j].dlv2 < lo) + lo = lab_fl[j].dlv2; + + if (lab_fl[j].dlv2 > hi) + hi = lab_fl[j].dlv2; + } + + if (lo == 0) { + printf("seed %zu wedged a flow.\n", i); + goto fail; + } + + want = cap * ((c.sc_hi - c.sc_lo) / BILLION); + if (tot < want / LAB_N8_UTIL) { + printf("seed %zu carried %" PRIu64 " of %" PRIu64 + " bytes.\n", i, tot, want); + goto fail; + } + + if ((double) hi / (double) lo > worst) + worst = (double) hi / (double) lo; + } + + if (cap >= LAB_N8_FAIR && worst > 1.0 + LAB_N8_TOL) { + printf("widest split %.3f across eight flows.\n", worst); + goto fail; + } + + TEST_SUCCESS("(%" PRIu64 " kb/s, lag %" PRIu64 " ms)", kb, ms); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL("(%" PRIu64 " kb/s, lag %" PRIu64 " ms)", kb, ms); + return TEST_RC_FAIL; +} + +static int test_mb_ecn_lab_fixpoint_n8_all(void) +{ +#ifdef MB_ECN_LAB_FULL + static const uint64_t cap[] = { 1250000000, 12500000, + 1250000, 62500 }; +#else + static const uint64_t cap[] = { 1250000, 62500 }; +#endif + static const uint64_t lag[] = { 2 * MS, 42 * MS }; + int ret = 0; + size_t i; + size_t j; + + for (i = 0; i < sizeof(cap) / sizeof(cap[0]); i++) + for (j = 0; j < sizeof(lag) / sizeof(lag[0]); j++) + ret |= test_mb_ecn_lab_fixpoint_n8(cap[i], lag[j]); + + return ret; +} + +/* Jain's fairness index over the score bytes of flows [lo, hi). */ +static double lab_jain(size_t lo, + size_t hi) +{ + double s = 0.0; + double s2 = 0.0; + double x; + size_t i; + + for (i = lo; i < hi; i++) { + x = (double) lab_fl[i].dlv2; + s += x; + s2 += x * x; + } + + if (s2 == 0.0) + return 0.0; + + return s * s / ((double) (hi - lo) * s2); +} + +/* Greedy flows on one ctx, all from t = 0, short feedback path. */ +static void lab_cfg_shared(struct lab_cfg * c, + const char * name, + uint64_t cap, + size_t n) +{ + size_t i; + + lab_cfg_std(c, name, cap, n); + + c->shared = true; + + for (i = 0; i < n; i++) { + c->ia[i] = 0; + c->t0[i] = 0; + c->lag[i] = 2 * MS; + } +} + +/* + * ------------------------------------------------------------------ + * Shared context: n flows, one struct mb_ecn_ctx, one ftag each. + * + * This is what a production build runs: ca_ctx_get interns one ctx + * per (peer, qos cube), so rate, vt and the mark are shared and the + * start tag is all a flow owns. The pacer then admits n * rate, so + * the attractor for rate is C / n, and the offered load the ctx + * measures is n flows' bytes against one flow's rate. + * ------------------------------------------------------------------ + */ + +/* + * Spread the shared attractor may sit in and still count as a + * per-flow share. The flows share one virtual clock, so the pacer + * fires them in one burst per tick; where that burst is a large part + * of the bandwidth-delay product the quarter-log2 mark prices it as + * a queue and the loop settles into a deep sawtooth, down to ~0.55 + * of C / n around 10 Mb/s at 1 kB packets. The band carries that and + * is still an order of magnitude under the path rate C. + */ +#define LAB_SHR_LO 0.45 +#define LAB_SHR_HI 1.10 + +/* Peak of the same sawtooth, over the settled window. */ +#define LAB_SHR_PK 1.35 + +/* One even split across the flows sharing the ctx. */ +#define LAB_SHR_JN 0.98 + +/* + * Greedy flows on one ctx, optionally with half of them joining and + * leaving again mid-run. Scores the epoch after the last change. + */ +static int test_mb_ecn_lab_shared(uint64_t cap, + size_t n, + bool churn) +{ + static struct lab_cfg c; + uint64_t kb = cap * 8 / 1000; + uint64_t tc = 20ULL * BILLION; + uint64_t fair; + uint64_t mean; + double jain; + size_t nl; + size_t i; + + TEST_START("(%" PRIu64 " kb/s, %zu flows%s)", kb, n, + churn ? ", churn" : ""); + + lab_cfg_shared(&c, churn ? "shr-churn" : "shr-gdy", cap, n); + + nl = churn ? n / 2 : n; + /* Half the flows join at tc and are gone again at 2 * tc. */ + for (i = nl; i < n; i++) { + c.t0[i] = tc; + c.t1[i] = 2 * tc; + } + + fair = cap / nl; + + /* + * Warmup ends at the last change, so r_hi is the peak of the + * epoch that has to settle back to the new share. + */ + c.dur = 2 * tc + 60ULL * BILLION; + c.wu = churn ? 2 * tc : 40ULL * BILLION; + c.sc_lo = c.wu + 20ULL * BILLION; + c.sc_hi = c.dur; + + lab_run(&c); + + mean = lab_fl[0].r_int / (c.dur - c.wu); + jain = lab_jain(0, nl); + + if (mean < (uint64_t) (LAB_SHR_LO * (double) fair) + || mean > (uint64_t) (LAB_SHR_HI * (double) fair)) { + printf("rate %" PRIu64 " is not a %" PRIu64 " share.\n", + mean, fair); + goto fail; + } + + if (lab_fl[0].r_hi > (uint64_t) (LAB_SHR_PK * (double) fair)) { + printf("rate peaked at %.2f of the share.\n", + (double) lab_fl[0].r_hi / (double) fair); + goto fail; + } + + if (jain < LAB_SHR_JN) { + printf("fairness %.4f across %zu flows.\n", jain, nl); + goto fail; + } + + TEST_SUCCESS("(%" PRIu64 " kb/s, %zu flows%s)", kb, n, + churn ? ", churn" : ""); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL("(%" PRIu64 " kb/s, %zu flows%s)", kb, n, + churn ? ", churn" : ""); + return TEST_RC_FAIL; +} + +/* + * Offered load per flow, as a divisor of the fair share. Low enough + * that the aggregate never fills the link, so nothing marks and the + * offered-load path is the only thing bounding the rate. + */ +#define LAB_SRC_DIV 4 + +/* + * mb_ecn_ceiling admits twice the load ONE flow offers. The slack + * covers the additive increase banked between two window closes and + * the truncation in sharing the offered bytes out. Reading the whole + * ctx's load as one flow's puts the bound n times higher, so a wide + * slack still separates the two. + */ +#define LAB_SRC_CEIL 2.5 + +/* Churn half-period; below CA_SND_WIN no window would ever close. */ +#define LAB_SRC_CHP (100 * MS) + +/* + * Source-limited flows on one ctx. Each offers a fixed rate well + * under its share, so mb_ecn_ceiling and the backlog level are all + * that bound the rate, and both read the offered load. With churn, + * the flows above n / 4 come and go every LAB_SRC_CHP, so a window + * that does not restart on the count change never measures one + * population. + */ +static int test_mb_ecn_lab_shared_load(uint64_t cap, + size_t n, + bool churn) +{ + static struct lab_cfg c; + uint64_t kb = cap * 8 / 1000; + uint64_t off = cap / (LAB_SRC_DIV * n); + uint64_t hi; + double jain; + size_t ns; + size_t i; + + TEST_START("(%" PRIu64 " kb/s, %zu flows%s)", kb, n, + churn ? ", churn" : ""); + + lab_cfg_shared(&c, churn ? "src-churn" : "src-cbr", cap, n); + + for (i = 0; i < n; i++) + c.ia[i] = c.len * BILLION / off; + + if (churn) { + c.ch_p = 2 * LAB_SRC_CHP; + c.ch_f = ~0u << (n / 4); + } + + c.dur = 120ULL * BILLION; + c.wu = 60ULL * BILLION; + c.sc_lo = 60ULL * BILLION; + c.sc_hi = 120ULL * BILLION; + + lab_run(&c); + + hi = lab_fl[0].r_hi; + + /* Score the flows that shared the ctx over the same epochs. */ + ns = churn ? n / 4 : 0; + jain = lab_jain(ns, n); + + if (hi > (uint64_t) (LAB_SRC_CEIL * (double) off)) { + printf("rate peaked at %.2f of the %" PRIu64 " offered, " + "%.2f of the %" PRIu64 " path.\n", + (double) hi / (double) off, off, + (double) hi / (double) cap, cap); + goto fail; + } + + if (jain < LAB_SHR_JN) { + printf("fairness %.4f across %zu flows.\n", jain, n - ns); + goto fail; + } + + TEST_SUCCESS("(%" PRIu64 " kb/s, %zu flows%s)", kb, n, + churn ? ", churn" : ""); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL("(%" PRIu64 " kb/s, %zu flows%s)", kb, n, + churn ? ", churn" : ""); + return TEST_RC_FAIL; +} + +static int test_mb_ecn_lab(void) +{ + static const uint64_t gc_cap[] = { 625000, 1250000, 12500000 }; + static const char * gc_nm[] = { "gc-5M", "gc-10M", "gc-100M" }; + static const uint64_t sf_cap[] = { 62500, 125000, 1250000 }; + static const char * sf_nm[] = { "sf-500k", "sf-1M", "sf-10M" }; + static const uint64_t g2_cap[] = { + 1250000, 1250000, 1250000, 62500, 62500, 62500 + }; + static const uint64_t g2_lag[] = { + 2 * MS, 20 * MS, 42 * MS, 2 * MS, 20 * MS, 42 * MS + }; + static const char * g2_nm[] = { + "g2-10M-2", "g2-10M-20", "g2-10M-42", + "g2-500k-2", "g2-500k-20", "g2-500k-42" + }; + static const uint64_t ul_lag[] = { 20 * MS, 42 * MS }; + static const char * ul_nm[] = { "g2-ul20", "g2-ul42" }; + static struct lab_cfg c; + size_t i; + + TEST_START(); + + /* + * cbr_protection over capacity: a 3 Mb/s CBR flow shares the + * link with a greedy flow joining at 300 ms, so the share the + * CBR has to hold runs 60%, 30% and 3% of the link. + */ + for (i = 0; i < 3; i++) { + lab_cfg_std(&c, gc_nm[i], gc_cap[i], 2); + lab_run(&c); + } + + /* + * single_flow_slow_link over capacity: one greedy flow alone. + * The marking quantum is fixed in bytes, so capacity alone + * decides how much queueing delay one ecn step prices. + */ + for (i = 0; i < 3; i++) { + lab_cfg_std(&c, sf_nm[i], sf_cap[i], 1); + lab_run(&c); + } + + /* + * Two greedy flows over capacity and equal feedback lag: the + * split they settle on and how a long loop degrades it. + */ + for (i = 0; i < 6; i++) { + lab_cfg_std(&c, g2_nm[i], g2_cap[i], 2); + + c.ia[1] = 0; + c.lag[0] = g2_lag[i]; + c.lag[1] = g2_lag[i]; + c.dur = 65ULL * BILLION; + c.wu = 35ULL * BILLION; + + lab_run(&c); + } + + /* Unequal lag: flow 0 keeps 2 ms, flow 1 reacts slower. */ + for (i = 0; i < 2; i++) { + lab_cfg_std(&c, ul_nm[i], 1250000, 2); + + c.ia[1] = 0; + c.lag[1] = ul_lag[i]; + c.dur = 65ULL * BILLION; + c.wu = 35ULL * BILLION; + + lab_run(&c); + } + + /* + * Service stalls: the scheduler feeding the transmit queue + * pauses, the queue drains clean and the resume bursts the + * backlog through the marker. + */ + lab_cfg_std(&c, "st-gc", 1250000, 2); + + c.st_d = 60 * MS; + c.st_p = 400 * MS; + + lab_run(&c); + + lab_cfg_std(&c, "st-sf", 125000, 1); + + c.st_d = 200 * MS; + c.st_p = BILLION; + + lab_run(&c); + + /* Per-flow starvation: only the sparse CBR flow stalls. */ + lab_cfg_std(&c, "st-pf", 1250000, 2); + + c.st_d = 100 * MS; + c.st_p = 300 * MS; + c.st_f = 2; + + lab_run(&c); + + /* + * Greedy joins 10 s in, once the CBR flow has settled: a step + * into contention rather than a shared ramp. + */ + lab_cfg_std(&c, "gc-late", 1250000, 2); + + c.t0[0] = 10ULL * BILLION; + c.dur = 45ULL * BILLION; + c.wu = 40ULL * BILLION; + c.sc_lo = 15ULL * BILLION; + c.sc_hi = 40ULL * BILLION; + + lab_run(&c); + + /* + * Second greedy flow joins 5 s in: the incumbent has to give + * back half to a newcomer that is still in slow start. + */ + lab_cfg_std(&c, "g2-stag", 1250000, 2); + + c.ia[1] = 0; + c.t0[0] = 0; + c.t0[1] = 5ULL * BILLION; + c.dur = 65ULL * BILLION; + c.wu = 35ULL * BILLION; + + lab_run(&c); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; +} + +int mb_ecn_lab_test(int argc, + char ** argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + + ret |= test_mb_ecn_lab_shared(1250000, 5, false); + ret |= test_mb_ecn_lab_shared(1250000, 8, false); + ret |= test_mb_ecn_lab_shared(1250000, 8, true); + ret |= test_mb_ecn_lab_shared_load(1250000, 5, false); + ret |= test_mb_ecn_lab_shared_load(1250000, 8, false); + ret |= test_mb_ecn_lab_shared_load(1250000, 5, true); + ret |= test_mb_ecn_lab_shared_load(1250000, 8, true); + ret |= test_mb_ecn_lab_fixpoint_all(); + ret |= test_mb_ecn_lab_fixpoint_n8_all(); + ret |= test_mb_ecn_lab(); + + return ret; +} diff --git a/src/ipcpd/unicast/ca/tests/mb_ecn_test.c b/src/ipcpd/unicast/ca/tests/mb_ecn_test.c new file mode 100644 index 00000000..7186d3af --- /dev/null +++ b/src/ipcpd/unicast/ca/tests/mb_ecn_test.c @@ -0,0 +1,3156 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Unit tests for multi-bit ECN congestion avoidance + * + * 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 "mb-ecn.c" + +#include <test/test.h> + +#define MS (MILLION) /* one millisecond in ns */ +#define LEN 1000 /* default packet size (bytes) */ + +/* Create a context with the clock zeroed for deterministic time steps. */ +static struct mb_ecn_ctx * mk_ctx(void) +{ + struct mb_ecn_ctx * ctx; + + ctx = mb_ecn_ctx_create(); + if (ctx == NULL) + return NULL; + + ctx->rx_ts = 0; + ctx->rx_win = 0; + ctx->last_ts = 0; + ctx->last_ctrl = 0; + ctx->last_fb = 0; + ctx->last_sig = 0; + ctx->last_loc = 0; + ctx->last_cap = 0; + + ctx->snd_byt = 0; + ctx->snd_win = 0; + ctx->snd_r0 = CA_RATE_INIT; + ctx->snd_rate = CA_RATE_INIT; + ctx->backlogged = true; + ctx->src_limited = false; + ctx->started = false; + ctx->ss_tc = 20 * MS; /* fixed slope for deterministic SS */ + + return ctx; +} + +/* + * Drive ctx as a fully backlogged flow: offer a packet every paced + * wait, so the offered load tracks the paced rate. Returns end time. + */ +static uint64_t drive_backlogged(struct mb_ecn_ctx * ctx, + uint64_t * ftag, + uint64_t t, + uint64_t dur, + size_t len) +{ + uint64_t end = t + dur; + time_t w; + + while (t < end) { + w = mb_ecn_snd(ctx, len, t, ftag); + t += w > 0 ? (uint64_t) w : 1; + } + + return t; +} + +static int test_mb_ecn_ctx_create_destroy(void) +{ + struct mb_ecn_ctx * ctx; + + TEST_START(); + + ctx = mb_ecn_ctx_create(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + if (ctx->rate != CA_RATE_INIT) { + printf("Bad initial rate %" PRIu64 ".\n", ctx->rate); + goto fail_ctx; + } + + if (ctx->rate_min != CA_RATE_MIN) { + printf("Bad initial floor %" PRIu64 ".\n", ctx->rate_min); + goto fail_ctx; + } + + if (ctx->vt != 0) { + printf("Bad initial virtual clock %" PRIu64 ".\n", ctx->vt); + goto fail_ctx; + } + + if (ctx->tx_cav) { + printf("Context did not start in slow start.\n"); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* The pricing window derives from the declared RTT. */ +static int test_mb_ecn_init_window(void) +{ + TEST_START(); + + /* A fabric RTT lands on the floor, not below it. */ + mb_ecn_init(1); + + if (mb_ecn_tw != CA_TW_MIN) { + printf("fabric window %" PRIu64 ".\n", mb_ecn_tw); + goto fail; + } + + /* A WAN RTT caps the window. */ + mb_ecn_init(200); + + if (mb_ecn_tw != CA_TW) { + printf("wan window %" PRIu64 ".\n", mb_ecn_tw); + goto fail; + } + + /* An unspecified RTT takes the default and caps the window. */ + mb_ecn_init(0); + + if (mb_ecn_tw != CA_TW) { + printf("default window %" PRIu64 ".\n", mb_ecn_tw); + goto fail; + } + + mb_ecn_init(CA_SS_RTT_DEF); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + mb_ecn_init(CA_SS_RTT_DEF); + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Queue depth (packets) that reads as full congestion. */ +#define FULL_PKTS (CA_MARK_KNEE << ((CA_ECE_REF >> CA_SHFT) / 4)) + +static int test_mb_ecn_calc_ecn(void) +{ + uint8_t ecn; + + TEST_START(); + + /* One packet in the queue is the floor: it marks nothing. */ + ecn = 0; + + mb_ecn_calc_ecn(1400, &ecn, QOS_CUBE_BE, 1400); + + if (ecn != 0) { + printf("Single packet marked %u.\n", ecn); + goto fail; + } + + /* An unknown mean packet size cannot mark. */ + ecn = 0; + + mb_ecn_calc_ecn(1400, &ecn, QOS_CUBE_BE, 0); + + if (ecn != 0) { + printf("Unknown mean marked %u.\n", ecn); + goto fail; + } + + /* Each doubling of the queue adds 4. */ + ecn = 0; + + mb_ecn_calc_ecn(2 * 1400, &ecn, QOS_CUBE_BE, 1400); + + if (ecn != 4) { + printf("Expected ecn 4 at 2 packets, got %u.\n", ecn); + goto fail; + } + + /* FULL_PKTS packets is full congestion. */ + ecn = 0; + + mb_ecn_calc_ecn(FULL_PKTS * 1400, &ecn, QOS_CUBE_BE, 1400); + + if (ecn != (CA_ECE_REF >> CA_SHFT)) { + printf("Expected ecn %u at full, got %u.\n", + CA_ECE_REF >> CA_SHFT, ecn); + goto fail; + } + + /* The same packet count marks the same at any packet size. */ + ecn = 0; + + mb_ecn_calc_ecn(FULL_PKTS * 200, &ecn, QOS_CUBE_BE, 200); + + if (ecn != (CA_ECE_REF >> CA_SHFT)) { + printf("Size dependence: exp %u, got %u.\n", + CA_ECE_REF >> CA_SHFT, ecn); + goto fail; + } + + /* MAX keeps the larger value; a smaller mark cannot lower it. */ + ecn = 0x80; + + mb_ecn_calc_ecn(2 * 1400, &ecn, QOS_CUBE_BE, 1400); + + if (ecn != 0x80) { + printf("Expected ecn 0x80, got 0x%x.\n", ecn); + goto fail; + } + + ecn = 3; + + mb_ecn_calc_ecn(4 * 1400, &ecn, QOS_CUBE_BE, 1400); + + if (ecn != 8) { + printf("Expected ecn 8, got %u.\n", ecn); + goto fail; + } + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* The first mark after idle emits the raw value with zero latency. */ +static int test_mb_ecn_rcv_onset_immediate(void) +{ + struct mb_ecn_ctx * ctx; + uint16_t ece; + uint8_t fcap; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + if (!mb_ecn_rcv(ctx, LEN, 4, 0, &ece, &fcap, MS)) { + printf("Onset did not update.\n"); + goto fail_ctx; + } + + if (ece != 4 << CA_SHFT) { + printf("Onset ece: exp %u, got %u.\n", 4 << CA_SHFT, ece); + goto fail_ctx; + } + + if (mb_ecn_rcv(ctx, LEN, 4, 0, &ece, &fcap, 2 * MS)) { + printf("Mid-window packet updated.\n"); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* + * Two flows on the same wall-clock mark timeline, 15x apart in byte + * rate: the same congestion estimate, but the faster flow's window is + * shorter, so it feeds back more often (cadence tracks byte rate). + */ +static int test_mb_ecn_rcv_rate_independent(void) +{ + struct mb_ecn_ctx * a; + struct mb_ecn_ctx * b; + uint16_t ea; + uint16_t eb; + uint8_t fcap; + size_t ua; + size_t ub; + size_t i; + + TEST_START(); + + a = mk_ctx(); + b = mk_ctx(); + if (a == NULL || b == NULL) { + printf("Failed to create contexts.\n"); + goto fail_ctx; + } + + ea = 0; + eb = 0; + ua = 0; + ub = 0; + + /* 300 ms of sustained mark 8; a at 1 kpps, b at ~66 pps. */ + for (i = 1; i <= 300; i++) { + ua += mb_ecn_rcv(a, LEN, 8, 0, &ea, &fcap, i * MS) ? 1 : 0; + if (i % 15 != 0) + continue; + + ub += mb_ecn_rcv(b, LEN, 8, 0, &eb, &fcap, i * MS) ? 1 : 0; + } + + if (ea > eb + 32 || eb > ea + 32) { + printf("estimates diverge: %u vs %u.\n", ea, eb); + goto fail_ctx; + } + + if (ua < ub + 2) { + printf("cadence not rate-scaled: %zu vs %zu.\n", ua, ub); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(a); + mb_ecn_ctx_destroy(b); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(a); + mb_ecn_ctx_destroy(b); + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* + * Two flows on one bottleneck, equal byte rate but 7.5x apart in + * packet size: the same congestion estimate and the same feedback + * cadence. Framing does not skew the control signal (fair share). + */ +static int test_mb_ecn_rcv_size_fair(void) +{ + struct mb_ecn_ctx * a; + struct mb_ecn_ctx * b; + uint16_t ea; + uint16_t eb; + uint8_t fcap; + size_t ua; + size_t ub; + uint64_t ta; + uint64_t tb; + + TEST_START(); + + a = mk_ctx(); + b = mk_ctx(); + if (a == NULL || b == NULL) { + printf("Failed to create contexts.\n"); + goto fail_ctx; + } + + ea = 0; + eb = 0; + ua = 0; + ub = 0; + ta = 0; + tb = 0; + + /* 1 MB/s each: a at 200 B / 200 us, b at 1500 B / 1.5 ms. */ + while (ta < 500 * MS) { + ta += 200 * 1000; + ua += mb_ecn_rcv(a, 200, 8, 0, &ea, &fcap, ta) ? 1 : 0; + } + + while (tb < 500 * MS) { + tb += 1500 * 1000; + ub += mb_ecn_rcv(b, 1500, 8, 0, &eb, &fcap, tb) ? 1 : 0; + } + + if (ea != 8 << CA_SHFT || eb != 8 << CA_SHFT) { + printf("size-skewed estimate: %u vs %u.\n", ea, eb); + goto fail_ctx; + } + + if (ua > ub + 3 || ub > ua + 3) { + printf("cadence skewed by size: %zu vs %zu.\n", ua, ub); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(a); + mb_ecn_ctx_destroy(b); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(a); + mb_ecn_ctx_destroy(b); + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Release emits exactly one 0 and leaves the estimator fully idle. */ +static int test_mb_ecn_rcv_release_exact_zero(void) +{ + struct mb_ecn_ctx * ctx; + uint16_t ece; + uint8_t fcap; + size_t ends; + size_t i; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + for (i = 1; i <= 140; i++) + mb_ecn_rcv(ctx, LEN, 15, 0, &ece, &fcap, i * MS); + + ends = 0; + for (i = 141; i <= 350; i++) { + if (!mb_ecn_rcv(ctx, LEN, 0, 0, &ece, &fcap, i * MS)) + continue; + + if (ece == 0) + ends++; + } + + if (ends != 1) { + printf("end of congestion fired %zu times.\n", ends); + goto fail_ctx; + } + + if (ctx->rx_ece != 0 || ctx->rx_acc != 0) { + printf("estimator not idle: ece %u acc %" PRIu64 ".\n", + ctx->rx_ece, ctx->rx_acc); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* A gap past the window restarts fresh: no stale, diluted estimate. */ +static int test_mb_ecn_rcv_gap_restart(void) +{ + struct mb_ecn_ctx * ctx; + uint16_t ece; + uint8_t fcap; + uint64_t t; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + mb_ecn_rcv(ctx, LEN, 6, 0, &ece, &fcap, MS); + mb_ecn_rcv(ctx, LEN, 6, 0, &ece, &fcap, 2 * MS); + + t = 2 * MS + 10 * CA_TW; + if (!mb_ecn_rcv(ctx, LEN, 5, 0, &ece, &fcap, t)) { + printf("gap restart did not update.\n"); + goto fail_ctx; + } + + if (ece != 5 << CA_SHFT) { + printf("gap restart: exp %u, got %u.\n", 5 << CA_SHFT, ece); + goto fail_ctx; + } + + t += 10 * CA_TW; + if (!mb_ecn_rcv(ctx, LEN, 0, 0, &ece, &fcap, t) || ece != 0) { + printf("gap with clean packet did not end: %u.\n", ece); + goto fail_ctx; + } + + if (mb_ecn_rcv(ctx, LEN, 0, 0, &ece, &fcap, t + MS)) { + printf("idle packet updated.\n"); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* + * At a floored layer RTT, rx_tw sits at CA_TW_MIN, so 4 * rx_tw is + * well under CA_ECE_TTL. A gap in that band must still close the + * window as a diluted average, not restart fresh: a fresh restart + * always emits the raw undiluted mark (ecn << CA_SHFT), so an ece + * that low pins the CA_ECE_TTL floor in mb_ecn_rcv_fresh. + */ +static int test_mb_ecn_rcv_gap_floor(void) +{ + struct mb_ecn_ctx * ctx; + uint16_t ece; + uint8_t fcap; + uint64_t t; + + TEST_START(); + + mb_ecn_init(2); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + if (ctx->rx_tw != CA_TW_MIN) { + printf("window not floored: %" PRIu64 ".\n", ctx->rx_tw); + goto fail_ctx; + } + + /* Onset, then a second packet inside the window: mark banked. */ + mb_ecn_rcv(ctx, LEN, 8, 0, &ece, &fcap, MS); + mb_ecn_rcv(ctx, LEN, 8, 0, &ece, &fcap, 2 * MS); + + /* 20 ms gap: past 4 * rx_tw (16 ms), well under CA_ECE_TTL. */ + + t = 2 * MS + 20 * MS; + if (!mb_ecn_rcv(ctx, LEN, 8, 0, &ece, &fcap, t)) { + printf("window did not close.\n"); + goto fail_ctx; + } + + /* A fresh restart would emit the raw mark 8 << CA_SHFT, undiluted. */ + if (ece >= (8 << CA_SHFT)) { + printf("gap read as a fresh onset: ece %u.\n", ece); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + mb_ecn_init(CA_SS_RTT_DEF); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + mb_ecn_init(CA_SS_RTT_DEF); + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Max marks at max gaps: exact ceiling, no overflow past the edge. */ +static int test_mb_ecn_rcv_accum_bounds(void) +{ + struct mb_ecn_ctx * ctx; + uint16_t ece; + uint8_t fcap; + uint64_t t; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + mb_ecn_rcv(ctx, LEN, 15, 0, &ece, &fcap, MS); + + /* Two packets at dt just under CA_TW straddle the boundary. */ + t = MS + CA_TW - 1; + if (mb_ecn_rcv(ctx, LEN, 15, 0, &ece, &fcap, t)) { + printf("update before the window closed.\n"); + goto fail_ctx; + } + + t += CA_TW - 1; + if (!mb_ecn_rcv(ctx, LEN, 15, 0, &ece, &fcap, t)) { + printf("no update at the window boundary.\n"); + goto fail_ctx; + } + + if (ece != 15 << CA_SHFT) { + printf("ceiling: exp %u, got %u.\n", 15 << CA_SHFT, ece); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* + * The window floors at CA_TW, tracks the rate below the knee, + * and only a pathological fold hits the CA_TW_ABSMAX ceiling. + */ +static int test_mb_ecn_rcv_window_clip_bounds(void) +{ + struct mb_ecn_ctx * ctx; + uint16_t ece; + uint8_t fcap; + uint64_t ia; + uint64_t t; + size_t closes; + + TEST_START(); + + /* 1 GbE is above the high knee: the window floors at CA_TW. */ + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + ia = 8000ULL * BILLION / 1000000000ULL; + t = 0; + closes = 0; + while (closes < 40) { + t += ia; + if (mb_ecn_rcv(ctx, LEN, 8, 0, &ece, &fcap, t)) + closes++; + } + + if (ctx->rx_tw != CA_TW) { + printf("high-rate window: exp %" PRIu64 ", got %" PRIu64 + ".\n", (uint64_t) CA_TW, ctx->rx_tw); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + /* 1 Mbps: below the knee, ~16 pkts = 16 * 8 ms = 131 ms. */ + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + ia = 8000ULL * BILLION / 1000000ULL; + t = 0; + closes = 0; + while (closes < 40) { + t += ia; + if (mb_ecn_rcv(ctx, LEN, 8, 0, &ece, &fcap, t)) + closes++; + } + + if (ctx->rx_tw < 120 * MS || ctx->rx_tw > 140 * MS) { + printf("low-rate window: exp ~131 ms, got %" PRIu64 ".\n", + ctx->rx_tw); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + /* A near-empty window folds a huge target: ceiling holds. */ + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + mb_ecn_rcv(ctx, 10, 8, 0, &ece, &fcap, MS); + mb_ecn_rcv(ctx, 10, 8, 0, &ece, &fcap, MS + CA_TW); + + if (ctx->rx_tw != CA_TW_ABSMAX) { + printf("window ceiling breached: %" PRIu64 ".\n", + ctx->rx_tw); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* A CA-limited slow flow grows its window to hold ~16 packets. */ +static int test_mb_ecn_rcv_slow_window(void) +{ + struct mb_ecn_ctx * ctx; + uint16_t ece; + uint8_t fcap; + size_t i; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + /* 1400 B every 171 ms (~8 KB/s), sustained mark 8. */ + for (i = 1; i <= 100; i++) + mb_ecn_rcv(ctx, 1400, 8, 0, &ece, &fcap, i * 171 * MS); + + /* Target window 16 * 1000 B at 8187 B/s ~= 2.0 s. */ + if (ctx->rx_tw < 1400 * MS || ctx->rx_tw > 2800 * MS) { + printf("slow window: exp ~2 s, got %" PRIu64 ".\n", + ctx->rx_tw); + goto fail_ctx; + } + + /* Steady mark 8 emits exactly 256 once the window settles. */ + if (ece != 8 << CA_SHFT) { + printf("slow-flow estimate: exp %u, got %u.\n", + 8 << CA_SHFT, ece); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* A physically maximal window must fold without overflow or wrap. */ +static int test_mb_ecn_rcv_no_overflow_highrate(void) +{ + struct mb_ecn_ctx * ctx; + uint16_t ece; + uint8_t fcap; + bool ok; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + /* Open a window, then inject a maximal byte count and span. */ + mb_ecn_rcv(ctx, LEN, 15, 0, &ece, &fcap, 0); + ctx->rx_byt = CA_RATE_MAX / 8; + ctx->rx_ts = 2 * CA_TW - 2; + + ok = mb_ecn_rcv(ctx, LEN, 15, 0, &ece, &fcap, 2 * CA_TW - 1); + + if (!ok) { + printf("max-window close did not fire.\n"); + goto fail_ctx; + } + + if (ece > 8160) { + printf("estimate %u wrapped.\n", ece); + goto fail_ctx; + } + + /* A wrapped numerator drives rx_tw to MAX; it must descend. */ + if (ctx->rx_tw > CA_TW || ctx->rx_tw < CA_TW) { + printf("window %" PRIu64 " did not descend.\n", ctx->rx_tw); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* + * The sender holds a mark across the full inter-feedback gap (TTL > + * 2 * CA_TW); a repeated mark adds only the proportional cut. + */ +static int test_mb_ecn_ece_ttl_covers_cadence(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t prev; + uint64_t t; + uint64_t ftag = 0; + size_t i; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + ctx->rate = (uint64_t) 100 << 20; + ctx->tx_cav = true; + + mb_ecn_ece(ctx, 100, 0, MS); + mb_ecn_snd(ctx, LEN, MS, &ftag); + + /* Sends between feedbacks spaced 2 * CA_TW + 5 ms apart. */ + t = MS; + for (i = 0; i < 4; i++) { + t += (2 * CA_TW + 5 * MS) / 4; + mb_ecn_snd(ctx, LEN, t, &ftag); + if (ctx->tx_ece == 0) { + printf("mark cleared inside the feedback gap.\n"); + goto fail_ctx; + } + } + + /* Same mark again: rise 0, so only the proportional cut. */ + prev = ctx->rate; + t += MS; + mb_ecn_ece(ctx, 100, 0, t); + mb_ecn_snd(ctx, LEN, t, &ftag); + + if (prev - ctx->rate > prev / 100) { + printf("phantom lead cut: %" PRIu64 " -> %" PRIu64 ".\n", + prev, ctx->rate); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_mb_ecn_slow_start(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t prev; + uint64_t t; + uint64_t ftag = 0; + size_t i; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + prev = ctx->rate; + t = 0; + + /* No feedback: the flow stays in slow start and grows each step. */ + for (i = 0; i < 16; i++) { + t += MS; + mb_ecn_snd(ctx, LEN, t, &ftag); + if (ctx->rate <= prev) { + printf("rate did not grow: %" PRIu64 ".\n", ctx->rate); + goto fail_ctx; + } + + prev = ctx->rate; + } + + /* Exponential ramp doubles in ~ln2 * CA_SS_TC ~= 14 ms. */ + if (ctx->rate < 2 * CA_RATE_INIT) { + printf("slow start too slow: %" PRIu64 ".\n", ctx->rate); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_mb_ecn_dt_scaling_invariant(void) +{ + struct mb_ecn_ctx * a; + struct mb_ecn_ctx * b; + uint64_t inc_a; + uint64_t inc_b; + uint64_t t; + uint64_t fta = 0; + uint64_t ftb = 0; + size_t i; + + TEST_START(); + + a = mk_ctx(); + b = mk_ctx(); + if (a == NULL || b == NULL) { + printf("Failed to create contexts.\n"); + goto fail_ctx; + } + + /* Leave slow start; seed a realistic rate (truncation-free). */ + mb_ecn_ece(a, 0, 0, 0); + mb_ecn_ece(b, 0, 0, 0); + a->rate = (uint64_t) 10 << 20; + b->rate = (uint64_t) 10 << 20; + + /* a: one 30 ms step. */ + mb_ecn_snd(a, LEN, 30 * MS, &fta); + + /* b: thirty 1 ms steps over the same 30 ms. */ + t = 0; + for (i = 0; i < 30; i++) { + t += MS; + mb_ecn_snd(b, LEN, t, &ftb); + } + + inc_a = a->rate - ((uint64_t) 10 << 20); + inc_b = b->rate - ((uint64_t) 10 << 20); + + /* Equal within 1 %; the small gap is per-step integer truncation. */ + if (inc_a == 0 || inc_b == 0) { + printf("no additive increase: %" PRIu64 " %" PRIu64 ".\n", + inc_a, inc_b); + goto fail_ctx; + } + + if (inc_a > inc_b + inc_a / 100 || inc_b > inc_a + inc_a / 100) { + printf("cadence-dependent AI: %" PRIu64 " vs %" PRIu64 ".\n", + inc_a, inc_b); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(a); + mb_ecn_ctx_destroy(b); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(a); + mb_ecn_ctx_destroy(b); + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_mb_ecn_multiplicative_decrease(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t prev; + uint64_t t; + uint64_t ftag = 0; + size_t i; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + ctx->rate = (uint64_t) 100 << 20; + prev = ctx->rate; + t = 0; + + for (i = 0; i < 10; i++) { + t += MS; + mb_ecn_ece(ctx, CA_ECE_REF, 0, t); + mb_ecn_snd(ctx, LEN, t, &ftag); + if (ctx->rate >= prev) { + printf("rate did not shrink: %" PRIu64 ".\n", + ctx->rate); + goto fail_ctx; + } + + prev = ctx->rate; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* The hold releases on any unsaturated mark, including above REF. */ +static int test_mb_ecn_ai_hold_release(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t r0 = (uint64_t) 100 << 20; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + /* A saturated mark keeps the hold: the queue has not drained. */ + ctx->ai_hold = true; + + mb_ecn_ece(ctx, CA_ECE_MAX, 0, MS); + + if (!ctx->ai_hold) { + printf("saturated feedback released the hold.\n"); + goto fail_ctx; + } + + /* A standing mark of 20 flows is unsaturated: release. */ + ctx->ai_hold = true; + + mb_ecn_ece(ctx, 22 << CA_SHFT, 0, 2 * MS); + + if (ctx->ai_hold) { + printf("unsaturated feedback held the increase.\n"); + goto fail_ctx; + } + + /* The decrease still scales with the mark saturated at CA_ECE_MAX. */ + ctx->rate = r0; + ctx->tx_cav = true; + ctx->tx_ece = CA_ECE_MAX; + ctx->tx_ecp = CA_ECE_MAX; + ctx->dec_acc = 0; + + mb_ecn_decrease(ctx, MILLION); + + if (ctx->rate > r0 - r0 / 700) { + printf("clamped decrease too weak: %" PRIu64 ".\n", ctx->rate); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_mb_ecn_rate_floor(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t ftag = 0; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + /* + * A starved 600 ms at full mark takes the rate/2 branch, far + * below the floor; stays inside the initial ~976 ms mark TTL. + */ + ctx->rate = CA_RATE_MIN + 1000; + mb_ecn_ece(ctx, CA_ECE_REF, 0, 0); + mb_ecn_snd(ctx, LEN, 600 * MS, &ftag); + + if (ctx->rate != CA_RATE_MIN) { + printf("rate floor breached: %" PRIu64 ".\n", ctx->rate); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_mb_ecn_fixed_point(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t exp; + uint64_t t; + uint64_t ftag = 0; + size_t i; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + exp = CA_AI_RATE * CA_ECE_REF / + (128 - CA_ECE_REF * BILLION / CA_PROBE_TC); + t = 0; + + /* ~20 s: the probe raises the loop time constant to CA_PROBE_TC. */ + for (i = 0; i < 20000; i++) { + t += MS; + mb_ecn_ece(ctx, 128, 0, t); + mb_ecn_snd(ctx, LEN, t, &ftag); + } + + if (ctx->rate < exp - exp / 4 || ctx->rate > exp + exp / 4) { + printf("no fixed point: exp ~%" PRIu64 ", got %" PRIu64 ".\n", + exp, ctx->rate); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* The lead is two-sided: it cuts on a rise and gives back on a fall. */ +static int test_mb_ecn_lead_symmetric(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t r0 = (uint64_t) 100 << 20; + uint64_t net; + uint64_t drop; + uint64_t gain; + uint64_t kd2; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + ctx->tx_cav = true; + + /* Rise of one reference: cut rate / CA_MD_KD_DIV, no more. */ + ctx->rate = r0; + ctx->tx_ece = CA_ECE_REF; + ctx->tx_ecp = 0; + ctx->dec_acc = 0; + + mb_ecn_decrease(ctx, 0); + + drop = r0 - ctx->rate; + if (drop != r0 / CA_MD_KD_DIV) { + printf("rise cut %" PRIu64 ", want %" PRIu64 ".\n", + drop, r0 / CA_MD_KD_DIV); + goto fail_ctx; + } + + /* Fall of one reference: give the same fraction back. */ + ctx->rate = r0; + ctx->tx_ece = 1; + ctx->tx_ecp = CA_ECE_REF + 1; + ctx->dec_acc = 0; + + mb_ecn_decrease(ctx, 0); + + gain = ctx->rate - r0; + if (gain != r0 / CA_MD_KD_DIV) { + printf("fall boost %" PRIu64 ", want %" PRIu64 ".\n", + gain, r0 / CA_MD_KD_DIV); + goto fail_ctx; + } + + /* A collapse from deep saturation is clamped to the same. */ + ctx->rate = r0; + ctx->tx_ece = 1; + ctx->tx_ecp = 255 << CA_SHFT; + ctx->dec_acc = 0; + + mb_ecn_decrease(ctx, 0); + + gain = ctx->rate - r0; + if (gain != r0 / CA_MD_KD_DIV) { + printf("unclamped fall boost %" PRIu64 ".\n", gain); + goto fail_ctx; + } + + /* A cycle that stays marked nets out: no standing bias. */ + ctx->rate = r0; + ctx->tx_ecp = 4 << CA_SHFT; + ctx->tx_ece = 12 << CA_SHFT; + ctx->dec_acc = 0; + + mb_ecn_decrease(ctx, 0); + + ctx->tx_ece = 4 << CA_SHFT; + ctx->dec_acc = 0; + + mb_ecn_decrease(ctx, 0); + + net = ctx->rate > r0 ? ctx->rate - r0 : r0 - ctx->rate; + /* + * The two lead steps compound to (1 - x)(1 + x), x = 1 / + * (2 * CA_MD_KD_DIV), so net ~= r0 / (4 * CA_MD_KD_DIV^2). + * Band it a factor of 2 either side so a materially weaker + * gain (e.g. KD off by a factor of 4) fails the floor. + */ + kd2 = (uint64_t) CA_MD_KD_DIV * CA_MD_KD_DIV; + if (net > r0 / (2 * kd2)) { + printf("cycle bias %" PRIu64 " of %" PRIu64 ".\n", net, r0); + goto fail_ctx; + } + + if (net < r0 / (8 * kd2)) { + printf("lead gain weaker than expected: net %" PRIu64 + " of %" PRIu64 ".\n", net, r0); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* A local first-hop mark exits slow start with no feedback needed. */ +static int test_mb_ecn_slow_start_local_brake(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t prev; + uint64_t ftag = 0; + size_t i; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + for (i = 1; i <= 50; i++) + mb_ecn_snd(ctx, LEN, i * MS, &ftag); + + if (ctx->tx_cav) { + printf("Left slow start without any signal.\n"); + goto fail_ctx; + } + + prev = ctx->rate; + mb_ecn_loc(ctx, 1, 50 * MS); + if (!ctx->tx_cav) { + printf("Local mark did not exit slow start.\n"); + goto fail_ctx; + } + + mb_ecn_snd(ctx, LEN, 51 * MS, &ftag); + if (ctx->rate > prev + prev / 20) { + printf("SS ramp survived the brake: %" PRIu64 ".\n", + ctx->rate); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* A clean path still ramps to line rate in well under a second. */ +static int test_mb_ecn_slow_start_clean_ramp(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t ftag = 0; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + /* Backlogged, no marks: slow start sprints in a couple windows. */ + drive_backlogged(ctx, &ftag, MS, 2 * CA_SND_WIN, LEN); + + if (ctx->rate < (1ULL << 24)) { + printf("backlogged slow start too slow: %" PRIu64 ".\n", + ctx->rate); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* + * A sender starved of send-path control steps recovers through the + * feedback path: honest elapsed time, at most a 50% cut per step. + */ +static int test_mb_ecn_starved_decrease_escape(void) +{ + struct mb_ecn_ctx * ctx; + size_t i; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + ctx->rate = (uint64_t) 100 << 20; + ctx->tx_cav = true; + + /* Feedback arrives once per second; no sends at all. */ + for (i = 1; i <= 6; i++) + mb_ecn_ece(ctx, 480, 0, i * BILLION); + + if (ctx->rate > (5ULL << 19)) { + printf("still wedged at %" PRIu64 " B/s.\n", ctx->rate); + goto fail_ctx; + } + + if (ctx->rate < CA_RATE_MIN) { + printf("rate floor breached: %" PRIu64 ".\n", ctx->rate); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* With feedback fully dead, the local mark alone recovers the rate. */ +static int test_mb_ecn_starved_local_fallback(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t t; + uint64_t ftag = 0; + size_t i; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + ctx->rate = (uint64_t) 100 << 20; + + for (i = 1; i <= 7; i++) { + t = i * BILLION; + mb_ecn_loc(ctx, 15, t); + mb_ecn_snd(ctx, LEN, t, &ftag); + } + + if (!ctx->tx_cav) { + printf("Local mark did not exit slow start.\n"); + goto fail_ctx; + } + + if (ctx->rate > (5ULL << 19)) { + printf("still wedged at %" PRIu64 " B/s.\n", ctx->rate); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* + * MD Δt-invariance: the same elapsed time under the same mark cuts + * the same, in one big step or five small ones. + */ +static int test_mb_ecn_decrease_dt_invariant(void) +{ + struct mb_ecn_ctx * a; + struct mb_ecn_ctx * b; + uint64_t cut_a; + uint64_t cut_b; + uint64_t r0; + uint64_t fta = 0; + uint64_t ftb = 0; + size_t i; + + TEST_START(); + + a = mk_ctx(); + b = mk_ctx(); + if (a == NULL || b == NULL) { + printf("Failed to create contexts.\n"); + goto fail_ctx; + } + + r0 = (uint64_t) 100 << 20; + a->rate = r0; + b->rate = r0; + + mb_ecn_ece(a, 256, 0, 0); + mb_ecn_ece(b, 256, 0, 0); + + /* a: one 50 ms step; b: five 10 ms steps (both within DT_CAP). */ + mb_ecn_snd(a, LEN, 50 * MS, &fta); + + for (i = 1; i <= 5; i++) + mb_ecn_snd(b, LEN, i * 10 * MS, &ftb); + + cut_a = r0 - a->rate; + cut_b = r0 - b->rate; + + if (cut_a == 0 || cut_b == 0) { + printf("no cut: %" PRIu64 " %" PRIu64 ".\n", cut_a, cut_b); + goto fail_ctx; + } + + /* Within 10%: residual is Euler compounding of MD and the probe. */ + if (cut_a > cut_b + cut_a / 10 || cut_b > cut_a + cut_a / 10) { + printf("cadence-dependent MD: %" PRIu64 " vs %" PRIu64 + ".\n", cut_a, cut_b); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(a); + mb_ecn_ctx_destroy(b); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(a); + mb_ecn_ctx_destroy(b); + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Sub-ms control steps must not lose decrease time to truncation. */ +static int test_mb_ecn_decrease_subms_carry(void) +{ + struct mb_ecn_ctx * a; + struct mb_ecn_ctx * b; + uint64_t cut_a; + uint64_t cut_b; + uint64_t r0; + uint64_t fta = 0; + uint64_t ftb = 0; + size_t i; + + TEST_START(); + + a = mk_ctx(); + b = mk_ctx(); + if (a == NULL || b == NULL) { + printf("Failed to create contexts.\n"); + goto fail_ctx; + } + + r0 = (uint64_t) 100 << 20; + a->rate = r0; + b->rate = r0; + + mb_ecn_ece(a, 256, 0, 0); + mb_ecn_ece(b, 256, 0, 0); + + /* Same 30 ms of marked time; b's steps have a 0.5 ms tail. */ + for (i = 1; i <= 10; i++) + mb_ecn_snd(a, LEN, i * 3 * MS, &fta); + + for (i = 1; i <= 20; i++) + mb_ecn_snd(b, LEN, i * 3 * MS / 2, &ftb); + + cut_a = r0 - a->rate; + cut_b = r0 - b->rate; + + if (cut_a == 0 || cut_b == 0) { + printf("no cut: %" PRIu64 " %" PRIu64 ".\n", cut_a, cut_b); + goto fail_ctx; + } + + /* Within 10%: the sub-ms remainder must carry, not vanish. */ + if (cut_a > cut_b + cut_a / 10 || cut_b > cut_a + cut_a / 10) { + printf("sub-ms decrease lost: %" PRIu64 " vs %" PRIu64 + ".\n", cut_a, cut_b); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(a); + mb_ecn_ctx_destroy(b); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(a); + mb_ecn_ctx_destroy(b); + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Resuming after a long idle gap: bounded AI, no cut from stale marks. */ +static int test_mb_ecn_idle_resume_bounded(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t prev; + uint64_t bump; + uint64_t ftag = 0; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + ctx->rate = (uint64_t) 10 << 20; + + mb_ecn_loc(ctx, 15, MS); + mb_ecn_ece(ctx, 480, 0, MS); + + prev = ctx->rate; + + /* 600 s later: both signals stale; one capped AI + probe step. */ + mb_ecn_snd(ctx, LEN, 600 * BILLION, &ftag); + + if (ctx->rate < prev) { + printf("stale mark cut the rate: %" PRIu64 ".\n", + ctx->rate); + goto fail_ctx; + } + + bump = CA_AI_RATE * CA_DT_CAP / BILLION; + bump += (prev + bump) * CA_DT_CAP / CA_PROBE_TC; + + if (ctx->rate > prev + bump + 2) { + printf("idle resume cap breached: %" PRIu64 ".\n", ctx->rate); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* The congestion signal ages out on wall-clock time, not packet count. */ +static int test_mb_ecn_ece_staleness(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t ftag = 0; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + /* A fast flow's control step caches the floor TTL. */ + ctx->rate = (uint64_t) 1 << 20; + ctx->tx_cav = true; + mb_ecn_snd(ctx, LEN, MS, &ftag); + + if (ctx->ece_ttl != CA_ECE_TTL) { + printf("Fast-flow TTL: exp %" PRIu64 ", got %" PRIu64 ".\n", + (uint64_t) CA_ECE_TTL, ctx->ece_ttl); + goto fail_ctx; + } + + mb_ecn_ece(ctx, CA_ECE_REF, 0, 2 * MS); + + /* Just inside the TTL: the signal is still held. */ + mb_ecn_snd(ctx, LEN, 2 * MS + ctx->ece_ttl, &ftag); + if (ctx->tx_ece == 0) { + printf("signal aged out too early.\n"); + goto fail_ctx; + } + + /* Past the TTL without feedback: the signal is cleared. */ + mb_ecn_snd(ctx, LEN, 2 * MS + ctx->ece_ttl + 1, &ftag); + if (ctx->tx_ece != 0) { + printf("stale signal not cleared: %u.\n", ctx->tx_ece); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* The staleness horizon stretches with a slow flow's window. */ +static int test_mb_ecn_ece_ttl_tracks_rate(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t want; + uint64_t ftag = 0; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + ctx->rate = 8192; + ctx->rate_min = 8192; + ctx->ai_rate = 0; + ctx->tx_cav = true; + mb_ecn_snd(ctx, 1400, MS, &ftag); + + want = (1 << CA_TW_GAP_SHFT) * CA_RX_WBYTES * BILLION / ctx->rate; + if (ctx->ece_ttl != want) { + printf("Slow-flow TTL: exp %" PRIu64 ", got %" PRIu64 ".\n", + want, ctx->ece_ttl); + goto fail_ctx; + } + + if (ctx->ece_ttl < 7 * (uint64_t) BILLION) { + printf("TTL did not stretch: %" PRIu64 ".\n", + ctx->ece_ttl); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* + * First packet of a flow starts at the clock; a same-instant second + * packet leads by its length and is paced by lead / rate. + */ +static int test_mb_ecn_sfq_pace(void) +{ + struct mb_ecn_ctx * ctx; + time_t wait; + uint64_t ftag = 0; + time_t want; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + ctx->rate = 1U << 20; + ctx->inv_rate = mb_ecn_rate_inv(ctx->rate); + + /* First send: start tag equals the clock, so no wait. */ + wait = mb_ecn_snd(ctx, 1500, 0, &ftag); + if (wait != 0) { + printf("first packet waited %ld, expected 0.\n", (long) wait); + goto fail_ctx; + } + + /* Same instant (dt = 0): the flow now leads by 1500 B. */ + wait = mb_ecn_snd(ctx, 1500, 0, &ftag); + want = (time_t) ((uint64_t) 1500 * BILLION / ctx->rate); + + if (wait != want) { + printf("paced wait %ld, expected %ld.\n", + (long) wait, (long) want); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* + * The proportional probe grows the rate by the same fraction per unit + * time regardless of the absolute rate: two clean flows 100x apart in + * rate grow by the same ratio. Deleting the probe leaves only the tiny + * additive increase, failing the growth floor. + */ +static int test_mb_ecn_probe_scale_invariant(void) +{ + struct mb_ecn_ctx * a; + struct mb_ecn_ctx * b; + uint64_t ra0; + uint64_t rb0; + double ga; + double gb; + uint64_t t; + size_t i; + + TEST_START(); + + a = mk_ctx(); + b = mk_ctx(); + if (a == NULL || b == NULL) { + printf("Failed to create contexts.\n"); + goto fail_ctx; + } + + /* Clean path (mark 0), out of slow start, backlogged, 100x apart. */ + mb_ecn_ece(a, 0, 0, 0); + mb_ecn_ece(b, 0, 0, 0); + a->rate = (uint64_t) 10 << 20; + b->rate = (uint64_t) 1000 << 20; + a->backlogged = true; + b->backlogged = true; + ra0 = a->rate; + rb0 = b->rate; + + /* Drive control via the feedback path so backlogged stays set. */ + t = 0; + for (i = 0; i < 500; i++) { + t += MS; + mb_ecn_ece(a, 0, 0, t); + mb_ecn_ece(b, 0, 0, t); + } + + ga = (double) a->rate / ra0; + gb = (double) b->rate / rb0; + + if (ga < gb - gb / 50 || gb < ga - ga / 50) { + printf("probe not scale-invariant: %.4f vs %.4f.\n", ga, gb); + goto fail_ctx; + } + + /* And it must actually grow: the probe is present, not deleted. */ + if (ga < 1.05) { + printf("probe did not grow the rate: %.4f.\n", ga); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(a); + mb_ecn_ctx_destroy(b); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(a); + mb_ecn_ctx_destroy(b); + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* + * The proportional probe e-folds the rate over CA_PROBE_TC: a clean flow + * grows by ~e in 8 s. Pinned to a literal e-band so a mistuned + * CA_PROBE_TC (e.g. 4 s gives e^2) fails. + */ +static int test_mb_ecn_probe_time_constant(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t r0; + double ratio; + uint64_t t; + size_t i; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + /* Clean path, out of slow start, backlogged, below the ceiling. */ + mb_ecn_ece(ctx, 0, 0, 0); + + ctx->rate = (uint64_t) 1 << 30; + ctx->backlogged = true; + r0 = ctx->rate; + + /* 8000 x 1 ms of clean growth, driven via the feedback path. */ + t = 0; + for (i = 0; i < 8000; i++) { + t += MS; + mb_ecn_ece(ctx, 0, 0, t); + } + + /* Undamped probe: 8 s at TC 8 s is one full e-fold, ~2.72x. */ + ratio = (double) ctx->rate / r0; + if (ratio < 2.6 || ratio > 2.85) { + printf("probe TC off: exp ~2.72, got %.3fx over 8 s.\n", ratio); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* The fed-back capacity is the MIN of the nonzero caps in the window. */ +static int test_mb_ecn_rcv_cap_window_min(void) +{ + struct mb_ecn_ctx * ctx; + uint16_t ece; + uint8_t fcap; + uint64_t t; + bool upd; + size_t i; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + /* Onset packet carries no capacity: feed back unknown. */ + if (!mb_ecn_rcv(ctx, LEN, 8, 0, &ece, &fcap, MS)) { + printf("Onset did not update.\n"); + goto fail_ctx; + } + + if (fcap != 0) { + printf("Onset fed back cap: exp 0, got %u.\n", fcap); + goto fail_ctx; + } + + mb_ecn_rcv(ctx, LEN, 8, 40, &ece, &fcap, 2 * MS); + mb_ecn_rcv(ctx, LEN, 8, 36, &ece, &fcap, 3 * MS); + + t = 3 * MS + CA_TW; + if (!mb_ecn_rcv(ctx, LEN, 8, 0, &ece, &fcap, t)) { + printf("Window did not close.\n"); + goto fail_ctx; + } + + if (fcap != 36) { + printf("Window min cap: exp 36, got %u.\n", fcap); + goto fail_ctx; + } + + /* The next window starts unknown; follow the adapted rx_tw. */ + upd = false; + for (i = 0; i < 128 && !upd; i++) { + t += CA_TW; + upd = mb_ecn_rcv(ctx, LEN, 8, 0, &ece, &fcap, t); + } + + if (!upd) { + printf("Second window did not close.\n"); + goto fail_ctx; + } + + if (fcap != 0) { + printf("Stale cap %u leaked into the next window.\n", fcap); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Onset and gap restarts emit the triggering packet's cap, fresh. */ +static int test_mb_ecn_rcv_cap_onset_fresh(void) +{ + struct mb_ecn_ctx * ctx; + uint16_t ece; + uint8_t fcap; + uint64_t t; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + if (!mb_ecn_rcv(ctx, LEN, 4, 77, &ece, &fcap, MS)) { + printf("Onset did not update.\n"); + goto fail_ctx; + } + + if (fcap != 77) { + printf("Onset cap: exp 77, got %u.\n", fcap); + goto fail_ctx; + } + + mb_ecn_rcv(ctx, LEN, 4, 50, &ece, &fcap, 2 * MS); + + /* A gap restart must not fold in the stale window min. */ + t = 2 * MS + 5 * CA_TW; + if (!mb_ecn_rcv(ctx, LEN, 4, 90, &ece, &fcap, t)) { + printf("Gap restart did not update.\n"); + goto fail_ctx; + } + + if (fcap != 90) { + printf("Gap restart cap: exp 90, got %u.\n", fcap); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Fed-back capacity derives the floor and slope: EWMA toward C/32. */ +static int test_mb_ecn_ece_cap_derives_rates(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t tgt; + uint64_t want; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + /* Code 120 = 2^30 B/s; target floor = 2^25 B/s. */ + tgt = cap_dec(120) >> CA_CAP_SHFT; + + mb_ecn_ece(ctx, 100, 120, MS); + + want = CA_RATE_MIN + ((tgt - CA_RATE_MIN) >> CA_CAP_SM_SHFT); + if (ctx->rate_min != want) { + printf("Floor: exp %" PRIu64 ", got %" PRIu64 ".\n", + want, ctx->rate_min); + goto fail_ctx; + } + + if (ctx->ai_rate != 2 * ctx->rate_min) { + printf("AI slope did not track the floor.\n"); + goto fail_ctx; + } + + mb_ecn_ece(ctx, 100, 120, 2 * MS); + + want += (tgt - want) >> CA_CAP_SM_SHFT; + if (ctx->rate_min != want) { + printf("Floor EWMA: exp %" PRIu64 ", got %" PRIu64 ".\n", + want, ctx->rate_min); + goto fail_ctx; + } + + if (ctx->n_cap != 2) { + printf("Capacity updates: exp 2, got %" PRIu64 ".\n", + ctx->n_cap); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Feedback without a capacity leaves the derived rates untouched. */ +static int test_mb_ecn_ece_cap_zero_keeps_rates(void) +{ + struct mb_ecn_ctx * ctx; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + mb_ecn_ece(ctx, 100, 0, MS); + + if (ctx->rate_min != CA_RATE_MIN || ctx->ai_rate != CA_AI_RATE) { + printf("Unknown cap moved the derived rates.\n"); + goto fail_ctx; + } + + if (ctx->n_cap != 0) { + printf("Unknown cap counted as an update.\n"); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* The derived floor clamps to [CA_RATE_MIN, CA_RMIN_MAX]. */ +static int test_mb_ecn_ece_cap_clamps(void) +{ + struct mb_ecn_ctx * ctx; + size_t i; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + /* A path slower than the default floor cannot lower it. */ + mb_ecn_ece(ctx, 100, 1, MS); + + if (ctx->rate_min != CA_RATE_MIN) { + printf("Slow path lowered the floor: %" PRIu64 ".\n", + ctx->rate_min); + goto fail_ctx; + } + + /* A absurdly fast path saturates at the ceiling. */ + for (i = 1; i <= 40; i++) + mb_ecn_ece(ctx, 100, 255, (1 + i) * MS); + + if (ctx->rate_min > CA_RMIN_MAX) { + printf("Floor above the ceiling: %" PRIu64 ".\n", + ctx->rate_min); + goto fail_ctx; + } + + if (ctx->rate_min < CA_RMIN_MAX - 4) { + printf("Floor did not reach the ceiling: %" PRIu64 ".\n", + ctx->rate_min); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* Stale capacity reverts the derived rates to the defaults. */ +static int test_mb_ecn_cap_ttl_reverts(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t ftag = 0; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + mb_ecn_ece(ctx, 100, 120, MS); + + if (ctx->rate_min == CA_RATE_MIN) { + printf("Capacity did not derive a floor.\n"); + goto fail_ctx; + } + + /* Just inside the TTL: the derived rates hold. */ + mb_ecn_snd(ctx, LEN, MS + (ctx->ece_ttl << CA_CAP_TTL_SHFT), &ftag); + + if (ctx->rate_min == CA_RATE_MIN) { + printf("Derived rates reverted too early.\n"); + goto fail_ctx; + } + + /* Past the TTL: back to the defaults. */ + mb_ecn_snd(ctx, LEN, MS + (ctx->ece_ttl << CA_CAP_TTL_SHFT) + 1, + &ftag); + + if (ctx->rate_min != CA_RATE_MIN || ctx->ai_rate != CA_AI_RATE) { + printf("Stale capacity kept the derived rates.\n"); + goto fail_ctx; + } + + if (ctx->tx_cap != 0) { + printf("Stale capacity code not cleared.\n"); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* The control law uses the per-ctx AI slope. */ +static int test_mb_ecn_ctrl_per_ctx_ai(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t want; + uint64_t ftag = 0; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + /* Leave slow start; raise the slope as capacity would. */ + mb_ecn_ece(ctx, 0, 0, 0); + + ctx->rate = (uint64_t) 10 << 20; + ctx->ai_rate = 16 * CA_AI_RATE; + + want = ctx->rate + ctx->ai_rate * (30 * MS) / BILLION; + want += want * (30 * MS) / CA_PROBE_TC; + + mb_ecn_snd(ctx, LEN, 30 * MS, &ftag); + + if (ctx->rate != want) { + printf("AI not per-ctx: exp %" PRIu64 ", got %" PRIu64 ".\n", + want, ctx->rate); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* The rate clamp honours the per-ctx derived floor. */ +static int test_mb_ecn_ctrl_per_ctx_floor(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t ftag = 0; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + ctx->rate_min = (uint64_t) 1 << 20; + ctx->rate = ((uint64_t) 1 << 20) + 1000; + + mb_ecn_ece(ctx, CA_ECE_REF, 0, 0); + mb_ecn_snd(ctx, LEN, 30 * MS, &ftag); + + if (ctx->rate != ctx->rate_min) { + printf("Floor not per-ctx: %" PRIu64 ".\n", ctx->rate); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* + * A paced flow slower than one packet per CA_DT_CAP must not decay: + * the gap credit law grants its true elapsed service, so the lead + * stays pinned at ~one packet instead of growing without bound. + */ +static int test_mb_ecn_snd_slow_rate_paced(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t ftag = 0; + uint64_t t = 0; + time_t wait; + size_t i; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + /* 8 KB/s, 1400 B packets: inter-send gap ~171 ms > CA_DT_CAP. */ + ctx->rate = 8192; + ctx->rate_min = 8192; + ctx->ai_rate = 0; + ctx->inv_rate = mb_ecn_rate_inv(8192); + ctx->tx_cav = true; + + for (i = 0; i < 50; i++) { + wait = mb_ecn_snd(ctx, 1400, t, &ftag); + t += wait > 0 ? (uint64_t) wait : 1; + } + + if (ctx->lead > 2 * 1400) { + printf("Pacer starves a slow flow: lead %" PRIu64 ".\n", + ctx->lead); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* + * A long idle makes the aggregate source-limited, so the offered-load + * ceiling bounds the resume rate (hence the burst) well below the + * pre-idle rate. + */ +static int test_mb_ecn_snd_idle_burst_bound(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t ftag = 0; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + ctx->rate = (uint64_t) 1 << 20; + ctx->inv_rate = mb_ecn_rate_inv(ctx->rate); + ctx->tx_cav = true; + + mb_ecn_snd(ctx, 1400, 0, &ftag); /* warm-up: sets started */ + + /* 600 s idle. */ + mb_ecn_snd(ctx, 1400, 600 * BILLION, &ftag); + + if (ctx->backlogged) { + printf("long idle did not clear backlogged.\n"); + goto fail_ctx; + } + + if (ctx->rate >= ((uint64_t) 1 << 20)) { + printf("idle resume rate not ceiling-bounded: %" PRIu64 + ".\n", ctx->rate); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* A deep backlog at a low rate must not wrap the wait computation. */ +static int test_mb_ecn_snd_wait_no_overflow(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t want; + uint64_t ftag; + time_t wait; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + ctx->rate = 8192; + ctx->inv_rate = mb_ecn_rate_inv(8192); + + /* 128 flows x 1400 B of SFQ lead at the floor rate. */ + ftag = 128 * 1400; + + wait = mb_ecn_snd(ctx, 1400, 0, &ftag); + want = (uint64_t) 128 * 1400 * BILLION / 8192; + + if ((uint64_t) wait < want - want / 100 || + (uint64_t) wait > want + want / 100) { + printf("Wait wrapped: exp ~%" PRIu64 ", got %" PRIu64 ".\n", + want, (uint64_t) wait); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* A fully paced-backlogged flow reads backlogged after a window. */ +static int test_mb_ecn_backlogged_paced(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t ftag = 0; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + ctx->backlogged = false; /* prove a window close re-earns it */ + + drive_backlogged(ctx, &ftag, MS, 4 * CA_SND_WIN, LEN); + + if (!ctx->backlogged) { + printf("paced-backlogged flow read source-limited.\n"); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* + * A source-limited flow is capped to the backlog level above the + * offered estimate, and NOT re-floored to a high capacity rate_min. + */ +static int test_mb_ecn_source_limited_ceiling(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t ftag = 0; + uint64_t t = 10 * MS; + uint64_t expect; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + ctx->tx_cav = true; + ctx->started = true; + ctx->backlogged = false; + ctx->rate = (uint64_t) 100 << 20; + ctx->inv_rate = mb_ecn_rate_inv(ctx->rate); + ctx->rate_min = (uint64_t) 50 << 20; + ctx->snd_rate = (uint64_t) 1 << 20; + ctx->snd_r0 = ctx->rate; + ctx->snd_win = t; + ctx->last_ts = t; + ctx->last_ctrl = t; + + mb_ecn_snd(ctx, LEN, t + 2 * MS, &ftag); + + expect = 1398101; /* (1 << 20) * 4 / 3, truncated */ + if (ctx->rate != expect) { + printf("ceiling: exp %" PRIu64 ", got %" PRIu64 ".\n", + expect, ctx->rate); + goto fail_ctx; + } + + if (!ctx->src_limited) { + printf("ceiling bound but src_limited not set.\n"); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + /* Non-power-of-two case: verify the exact level, not a step. */ + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + ctx->tx_cav = true; + ctx->started = true; + ctx->backlogged = false; + ctx->rate = (uint64_t) 100 << 20; + ctx->inv_rate = mb_ecn_rate_inv(ctx->rate); + ctx->rate_min = (uint64_t) 50 << 20; + ctx->snd_rate = (uint64_t) 303 << 12; + ctx->snd_r0 = ctx->rate; + ctx->snd_win = t; + ctx->last_ts = t; + ctx->last_ctrl = t; + + mb_ecn_snd(ctx, LEN, t + 2 * MS, &ftag); + + expect = 1654784; /* (303 << 12) * 4 / 3, exact */ + if (ctx->rate != expect) { + printf("exact ceiling: exp %" PRIu64 ", got %" PRIu64 + ".\n", expect, ctx->rate); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* One quiet window must not collapse the max-filter; it decays ~1/16. */ +static int test_mb_ecn_max_filter(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t ftag = 0; + uint64_t hi = (uint64_t) 10 << 20; + uint64_t t = 10 * MS; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + ctx->started = true; + ctx->snd_rate = hi; + ctx->snd_r0 = hi; + ctx->snd_byt = 0; + ctx->snd_win = t; + ctx->last_ts = t; + ctx->last_ctrl = t; + + /* Close one window with almost no bytes offered. */ + mb_ecn_snd(ctx, LEN, t + CA_SND_WIN + 1, &ftag); + + if (ctx->snd_rate >= hi || ctx->snd_rate < hi - hi / 8) { + printf("max-filter: exp ~15/16 of %" PRIu64 ", got %" + PRIu64 " after one quiet window.\n", + hi, ctx->snd_rate); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* A >CA_DT_CAP gap clears backlogged without touching the estimate. */ +static int test_mb_ecn_idle_clears_backlogged(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t ftag = 0; + uint64_t snd_rate = (uint64_t) 5 << 20; + uint64_t t = 10 * MS; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + ctx->started = true; + ctx->backlogged = true; + ctx->rate = snd_rate; + ctx->snd_rate = snd_rate; + ctx->snd_win = t; + ctx->last_ts = t; + ctx->last_ctrl = t; + + /* 55 ms gap: past CA_DT_CAP, under CA_SND_WIN (no window close). */ + mb_ecn_snd(ctx, LEN, t + 55 * MS, &ftag); + + if (ctx->backlogged) { + printf("idle gap did not clear backlogged.\n"); + goto fail_ctx; + } + + if (ctx->snd_rate != snd_rate) { + printf("idle step altered snd_rate %" PRIu64 ".\n", + ctx->snd_rate); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* The first send is never misread as idle, whatever the wall clock. */ +static int test_mb_ecn_first_send_warmup(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t ftag = 0; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + /* started == false; a large first timestamp must not look idle. */ + mb_ecn_snd(ctx, LEN, 500 * MS, &ftag); + + if (!ctx->started) { + printf("first send did not set the warm-up sentinel.\n"); + goto fail_ctx; + } + + if (!ctx->backlogged) { + printf("first send misclassified as idle.\n"); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* + * Couple one or two backlogged flows through a shared bottleneck of + * capacity cap: each step marks the shared queue with the real + * calc_ecn, feeds each flow that mark delayed by its own lag (in + * steps), drives it backlogged for one step, then drains the queue. + * A faithful discrete run of the fluid model on the real control law, + * with the forwarder abstracted to a single shared price. b may be + * NULL for a single-flow run. + */ +#define TF_STEP (5 * MS) /* control step (ns) */ +#define TF_HIST 64 /* mark ring, bounds max lag */ +#define TF_QMAX (8192 * LEN) /* bottleneck buffer (bytes) */ +#define TF_MAXN 32 /* flows per shared-link run */ + +static void shared_link_run(struct mb_ecn_ctx * a, + struct mb_ecn_ctx * b, + uint64_t cap, + size_t lag_a, + size_t lag_b, + size_t steps) +{ + uint16_t hist[TF_HIST]; + uint64_t ta = 0; + uint64_t tb = 0; + uint64_t fta = 0; + uint64_t ftb = 0; + uint64_t q = 0; + uint64_t drain = cap * TF_STEP / BILLION; + uint64_t tgt; + uint64_t arr; + uint8_t cc = cap_enc(cap); + uint8_t ecn; + uint16_t ea; + uint16_t eb; + size_t k; + + memset(hist, 0, sizeof(hist)); + + for (k = 0; k < steps; k++) { + tgt = (k + 1) * TF_STEP; + ecn = 0; + + mb_ecn_calc_ecn(q, &ecn, QOS_CUBE_BE, LEN); + hist[k % TF_HIST] = (uint16_t) (ecn << CA_SHFT); + + ea = k < lag_a ? 0 : hist[(k - lag_a) % TF_HIST]; + if (ea > 0) /* no feedback until congestion */ + mb_ecn_ece(a, ea, cc, ta); + + /* Active flow: heartbeat/window liveness stays fresh. */ + a->last_sig = ta; + if (tgt > ta) + ta = drive_backlogged(a, &fta, ta, tgt - ta, LEN); + + arr = a->rate * TF_STEP / BILLION; + + if (b != NULL) { + eb = k < lag_b ? 0 : hist[(k - lag_b) % TF_HIST]; + if (eb > 0) + mb_ecn_ece(b, eb, cc, tb); + + b->last_sig = tb; + if (tgt > tb) + tb = drive_backlogged(b, &ftb, tb, + tgt - tb, LEN); + + arr += b->rate * TF_STEP / BILLION; + } + + q += arr; + q = q > drain ? q - drain : 0; + if (q > (uint64_t) TF_QMAX) + q = TF_QMAX; + } +} + +/* + * Two flows sharing one bottleneck, both fed every queue mark, must + * converge from a lopsided start toward an equal split (the fluid + * model's drho/dt -> 0) rather than latch winner-take-all. Isolates + * the rate law from the forwarder: neither flow is starved of marks. + */ +static int test_mb_ecn_two_flow_converge(void) +{ + struct mb_ecn_ctx * a; + struct mb_ecn_ctx * b; + uint64_t cap = 1ULL << 20; + uint64_t fair = (1ULL << 20) / 2; + uint64_t lo; + uint64_t hi; + + TEST_START(); + + a = mk_ctx(); + b = mk_ctx(); + if (a == NULL || b == NULL) { + printf("Failed to create contexts.\n"); + goto fail_ctx; + } + + a->rate = cap; /* a hogs, b starts small */ + b->rate = CA_RATE_INIT; + + shared_link_run(a, b, cap, 0, 0, 6000); + + lo = a->rate < b->rate ? a->rate : b->rate; + hi = a->rate > b->rate ? a->rate : b->rate; + + if (lo < fair / 4) { + printf("flow starved: %" PRIu64 " / %" PRIu64 ".\n", + a->rate, b->rate); + goto fail_ctx; + } + + if (hi > 3 * lo) { + printf("did not converge: %" PRIu64 " / %" PRIu64 ".\n", + a->rate, b->rate); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(a); + mb_ecn_ctx_destroy(b); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(a); + mb_ecn_ctx_destroy(b); + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* + * A remote bottleneck exits slow start only on fed-back congestion, + * so a longer feedback delay lets the exponential ramp overshoot + * further: the slow-start peak grows with RTT. Nothing caps the ramp + * at the path capacity, so the sender latches near the first-hop rate. + */ +static int test_mb_ecn_ramp_overshoot_grows_with_rtt(void) +{ + struct mb_ecn_ctx * lo_rtt; + struct mb_ecn_ctx * hi_rtt; + uint64_t cap = 1ULL << 20; + + TEST_START(); + + lo_rtt = mk_ctx(); + hi_rtt = mk_ctx(); + if (lo_rtt == NULL || hi_rtt == NULL) { + printf("Failed to create contexts.\n"); + goto fail_ctx; + } + + /* Same bottleneck; feedback lags 1 vs 8 steps (~5 vs 40 ms). */ + shared_link_run(lo_rtt, NULL, cap, 1, 0, 2000); + shared_link_run(hi_rtt, NULL, cap, 8, 0, 2000); + + if (hi_rtt->ss_peak <= lo_rtt->ss_peak) { + printf("overshoot did not grow with RTT: %" PRIu64 + " vs %" PRIu64 ".\n", + hi_rtt->ss_peak, lo_rtt->ss_peak); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(lo_rtt); + mb_ecn_ctx_destroy(hi_rtt); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(lo_rtt); + mb_ecn_ctx_destroy(hi_rtt); + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* + * Flows sharing a context offer bytes together but each may send at + * rate, so the window must be shared out before the backlog level is + * read. Four flows offering two thirds of a share each stay below it. + */ +static int test_mb_ecn_shared_ctx_offered_per_flow(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t rate = 1000000; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + ctx->rate = rate; + ctx->snd_flows = 4; + ctx->snd_r0 = rate; + ctx->snd_win = 0; + ctx->snd_byt = 4 * rate * 2 / 3; + + mb_ecn_win(ctx, BILLION); + + if (ctx->backlogged) { + printf("aggregate load read as one flow's backlog.\n"); + goto fail_ctx; + } + + /* The close left a fresh window; a full share each clears it. */ + ctx->snd_byt = 4 * rate; + + mb_ecn_win(ctx, 2 * BILLION); + + if (!ctx->backlogged) { + printf("per-flow share did not read as backlogged.\n"); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* + * A join or a leave opens a fresh window, so none divides the bytes + * one population offered by the count of another. An unchanged count + * leaves the running window alone. + */ +static int test_mb_ecn_flow_count_restarts_window(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t rate = 1000000; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + ctx->rate = rate; + ctx->snd_flows = 2; + ctx->snd_win = MS; + ctx->snd_byt = 12345; + ctx->snd_r0 = 7; + + mb_ecn_flows(ctx, 5, 8 * MS); + + if (ctx->snd_flows != 5 || ctx->snd_byt != 0 + || ctx->snd_win != 8 * MS || ctx->snd_r0 != rate) { + printf("count change left a stale window: flows=%zu " + "byt=%" PRIu64 " win=%" PRIu64 " r0=%" PRIu64 + ".\n", ctx->snd_flows, ctx->snd_byt, + ctx->snd_win, ctx->snd_r0); + goto fail_ctx; + } + + ctx->snd_byt = 999; + + mb_ecn_flows(ctx, 5, 20 * MS); + + if (ctx->snd_byt != 999 || ctx->snd_win != 8 * MS) { + printf("unchanged count restarted the window.\n"); + goto fail_ctx; + } + + /* An empty context still measures a single sender. */ + mb_ecn_flows(ctx, 0, 30 * MS); + + if (ctx->snd_flows != 1) { + printf("zero flows did not floor at one: %zu.\n", + ctx->snd_flows); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +/* + * The ceiling must land where a window of the delivered rate reads + * backlogged again: a context clamped above that level can never + * leave the clamp, and loses its capacity floor with it. + */ +static int test_mb_ecn_ceiling_clears_backlog(void) +{ + struct mb_ecn_ctx * ctx; + uint64_t x = 1 << 20; + + TEST_START(); + + ctx = mk_ctx(); + if (ctx == NULL) { + printf("Failed to create context.\n"); + goto fail; + } + + ctx->backlogged = false; + ctx->snd_rate = x; + ctx->rate = 100 * x; + ctx->rate_min = CA_RATE_MIN; + + mb_ecn_ceiling(ctx); + + if (ctx->rate >= 100 * x) { + printf("ceiling did not bind: %" PRIu64 ".\n", ctx->rate); + goto fail_ctx; + } + + /* One window delivering x, with the pacer deferring nothing. */ + ctx->snd_r0 = ctx->rate; + ctx->snd_flows = 1; + ctx->snd_win = 0; + ctx->snd_byt = x; + ctx->snd_pac = 0; + + mb_ecn_win(ctx, BILLION); + + if (!ctx->backlogged) { + printf("clamped at %" PRIu64 " cannot clear on %" PRIu64 + ".\n", ctx->snd_r0, x); + goto fail_ctx; + } + + mb_ecn_ctx_destroy(ctx); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_ctx: + mb_ecn_ctx_destroy(ctx); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int mb_ecn_test(int argc, + char ** argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + + ret |= test_mb_ecn_ctx_create_destroy(); + ret |= test_mb_ecn_init_window(); + ret |= test_mb_ecn_calc_ecn(); + ret |= test_mb_ecn_rcv_onset_immediate(); + ret |= test_mb_ecn_rcv_rate_independent(); + ret |= test_mb_ecn_rcv_size_fair(); + ret |= test_mb_ecn_rcv_release_exact_zero(); + ret |= test_mb_ecn_rcv_gap_restart(); + ret |= test_mb_ecn_rcv_gap_floor(); + ret |= test_mb_ecn_rcv_accum_bounds(); + ret |= test_mb_ecn_rcv_window_clip_bounds(); + ret |= test_mb_ecn_rcv_slow_window(); + ret |= test_mb_ecn_rcv_no_overflow_highrate(); + ret |= test_mb_ecn_ece_ttl_covers_cadence(); + ret |= test_mb_ecn_slow_start(); + ret |= test_mb_ecn_dt_scaling_invariant(); + ret |= test_mb_ecn_probe_scale_invariant(); + ret |= test_mb_ecn_multiplicative_decrease(); + ret |= test_mb_ecn_ai_hold_release(); + ret |= test_mb_ecn_fixed_point(); + ret |= test_mb_ecn_lead_symmetric(); + ret |= test_mb_ecn_slow_start_local_brake(); + ret |= test_mb_ecn_slow_start_clean_ramp(); + ret |= test_mb_ecn_starved_decrease_escape(); + ret |= test_mb_ecn_starved_local_fallback(); + ret |= test_mb_ecn_decrease_dt_invariant(); + ret |= test_mb_ecn_decrease_subms_carry(); + ret |= test_mb_ecn_idle_resume_bounded(); + ret |= test_mb_ecn_rate_floor(); + ret |= test_mb_ecn_ece_staleness(); + ret |= test_mb_ecn_ece_ttl_tracks_rate(); + ret |= test_mb_ecn_sfq_pace(); + ret |= test_mb_ecn_probe_time_constant(); + ret |= test_mb_ecn_rcv_cap_window_min(); + ret |= test_mb_ecn_rcv_cap_onset_fresh(); + ret |= test_mb_ecn_ece_cap_derives_rates(); + ret |= test_mb_ecn_ece_cap_zero_keeps_rates(); + ret |= test_mb_ecn_ece_cap_clamps(); + ret |= test_mb_ecn_cap_ttl_reverts(); + ret |= test_mb_ecn_ctrl_per_ctx_ai(); + ret |= test_mb_ecn_ctrl_per_ctx_floor(); + ret |= test_mb_ecn_snd_slow_rate_paced(); + ret |= test_mb_ecn_snd_idle_burst_bound(); + ret |= test_mb_ecn_snd_wait_no_overflow(); + ret |= test_mb_ecn_backlogged_paced(); + ret |= test_mb_ecn_source_limited_ceiling(); + ret |= test_mb_ecn_max_filter(); + ret |= test_mb_ecn_idle_clears_backlogged(); + ret |= test_mb_ecn_first_send_warmup(); + ret |= test_mb_ecn_two_flow_converge(); + ret |= test_mb_ecn_ramp_overshoot_grows_with_rtt(); + ret |= test_mb_ecn_shared_ctx_offered_per_flow(); + ret |= test_mb_ecn_flow_count_restarts_window(); + ret |= test_mb_ecn_ceiling_clears_backlog(); + + return ret; +} diff --git a/src/ipcpd/unicast/cap.c b/src/ipcpd/unicast/cap.c new file mode 100644 index 00000000..67b7967c --- /dev/null +++ b/src/ipcpd/unicast/cap.c @@ -0,0 +1,99 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Link capacity codes + * + * 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/. + */ + +/* + * Rate <-> 8-bit code (cap_enc / cap_dec): the high 6 bits hold a + * band e = floor(log2 rate), the low 2 a quarter k splitting the + * band at 256 * 2^(k/4) = {256, 304, 362, 431}; code = 4 * e + k. + * Capacity is only ever needed to order-of-magnitude accuracy. + */ + +#include "cap.h" + +uint8_t cap_enc(uint64_t rate) +{ + static const uint16_t thr[3] = {304, 362, 431}; + uint64_t r = rate; /* copy halved to find band */ + unsigned e = 0; /* band: floor log2 rate */ + unsigned k = 0; /* quarter within band 0..3 */ + unsigned c; /* code = 4 * band + quarter */ + uint16_t top; /* rate scaled to [256, 512) */ + + if (rate == 0) + return 0; + + while (r > 1) { + r >>= 1; + e++; + } + + if (e >= 8) + top = (uint16_t) (rate >> (e - 8)); + else + top = (uint16_t) (rate << (8 - e)); + + while (k < 3 && top >= thr[k]) + k++; + + c = 4 * e + k; + if (c == 0) + c = 1; /* 0 means unknown */ + + return (uint8_t) c; +} + +uint64_t cap_dec(uint8_t c) +{ + static const uint16_t m[4] = {256, 304, 362, 431}; + unsigned e = c >> 2; /* band = c >> 2 */ + unsigned k = c & 3; /* quarter = c & 3 */ + + if (c == 0) + return 0; + + if (e >= 8) + return (uint64_t) m[k] << (e - 8); + + return ((uint64_t) m[k] << e) >> 8; +} + +uint8_t cap_min(uint8_t a, + uint8_t b) +{ + if (a == 0) + return b; + + if (b == 0) + return a; + + return a < b ? a : b; +} + +void cap_stamp(uint8_t * pci, + uint8_t own) +{ + if (own == 0) + return; + + if (*pci == 0 || own < *pci) + *pci = own; +} diff --git a/src/ipcpd/eth/dix.c b/src/ipcpd/unicast/cap.h index 37b9896d..ca6b6355 100644 --- a/src/ipcpd/eth/dix.c +++ b/src/ipcpd/unicast/cap.h @@ -1,7 +1,7 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * - * IPC processes over Ethernet - DIX + * Link capacity codes * * Dimitri Staessens <dimitri@ouroboros.rocks> * Sander Vrijders <sander@ouroboros.rocks> @@ -20,7 +20,21 @@ * Foundation, Inc., http://www.fsf.org/about/contact/. */ -#define BUILD_ETH_DIX -#define OUROBOROS_PREFIX "ipcpd/eth-dix" +#ifndef OUROBOROS_IPCPD_UNICAST_CAP_H +#define OUROBOROS_IPCPD_UNICAST_CAP_H -#include "eth.c" +#include <stdint.h> + +/* Quarter-log2 capacity code: ~2^(c / 4) bytes/s, 0 = unknown. */ +uint8_t cap_enc(uint64_t rate); + +uint64_t cap_dec(uint8_t c); + +uint8_t cap_min(uint8_t a, + uint8_t b); + +/* MIN-combine the own link code into the PCI byte. */ +void cap_stamp(uint8_t * pci, + uint8_t own); + +#endif /* OUROBOROS_IPCPD_UNICAST_CAP_H */ diff --git a/src/ipcpd/unicast/connmgr.c b/src/ipcpd/unicast/connmgr.c index 11c5d5b6..f60f6fca 100644 --- a/src/ipcpd/unicast/connmgr.c +++ b/src/ipcpd/unicast/connmgr.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * 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 e0cb09fc..a31a562f 100644 --- a/src/ipcpd/unicast/dir.c +++ b/src/ipcpd/unicast/dir.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * 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 b261ea2c..2be7b10f 100644 --- a/src/ipcpd/unicast/dir.h +++ b/src/ipcpd/unicast/dir.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * 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 08a5a5a9..9d60ce30 100644 --- a/src/ipcpd/unicast/dir/dht.c +++ b/src/ipcpd/unicast/dir/dht.c @@ -1,10 +1,9 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * 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" @@ -38,13 +39,14 @@ #include <ouroboros/errno.h> #include <ouroboros/logs.h> #include <ouroboros/list.h> -#include <ouroboros/notifier.h> #include <ouroboros/random.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" @@ -58,125 +60,96 @@ #include <limits.h> #include "dht.pb-c.h" -typedef DhtMsg dht_msg_t; -typedef DhtContactMsg dht_contact_msg_t; +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)) -struct cookie_el { - struct list_head next; +#define IS_REQUEST(code) \ + (code == DHT_FIND_NODE_REQ || code == DHT_FIND_VALUE_REQ) - uint32_t cookie; +enum dht_code { + DHT_STORE, + DHT_FIND_NODE_REQ, + DHT_FIND_NODE_RSP, + DHT_FIND_VALUE_REQ, + DHT_FIND_VALUE_RSP }; -struct lookup { - struct list_head next; - - struct list_head cookies; - - uint8_t * key; - - struct list_head contacts; - size_t n_contacts; - - uint64_t * addrs; - size_t n_addrs; - - enum lookup_state state; - pthread_cond_t cond; - pthread_mutex_t lock; +const char * dht_code_str[] = { + "DHT_STORE", + "DHT_FIND_NODE_REQ", + "DHT_FIND_NODE_RSP", + "DHT_FIND_VALUE_REQ", + "DHT_FIND_VALUE_RSP" }; -struct val { - struct list_head next; - - uint64_t addr; - - time_t t_exp; - time_t t_rep; +enum dht_state { + DHT_NULL = 0, + DHT_INIT, + DHT_RUNNING }; -struct ref_entry { +struct val_entry { struct list_head next; - uint8_t * key; + buffer_t val; - time_t t_rep; + time_t t_exp; /* Expiry time */ + time_t t_repl; /* Last replication time */ }; struct dht_entry { struct list_head next; uint8_t * key; - size_t n_vals; - struct list_head vals; + + struct llist vals; /* We don't own these, only replicate */ + struct llist lvals; /* We own these, must be republished */ }; struct contact { @@ -189,1101 +162,1479 @@ struct contact { time_t t_seen; }; -struct bucket { - struct list_head contacts; - size_t n_contacts; +struct peer_entry { + struct list_head next; + + uint64_t cookie; + uint8_t * id; + uint64_t addr; + enum dht_code code; - struct list_head alts; - size_t n_alts; + time_t t_sent; +}; - time_t t_refr; +struct dht_req { + struct list_head next; - size_t depth; - uint8_t mask; + uint8_t * key; + time_t t_exp; - struct bucket * parent; - struct bucket * children[1L << KAD_BETA]; + struct llist peers; + struct llist cache; }; -struct cmd { - struct list_head next; +struct bucket { + struct llist contacts; + struct llist alts; - struct shm_du_buff * sdb; + time_t t_refr; + + size_t depth; + uint8_t mask; + + struct bucket * parent; + struct bucket * children[1L << DHT_BETA]; +}; + +struct cmd { + 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_exp; /* Expiry time for values (s) */ + time_t t_refr; /* 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 list_head requests; - struct bmp * cookies; + struct { + struct llist ll; + size_t vals; + size_t lvals; + } kv; - enum dht_state state; - struct list_head cmds; - pthread_cond_t cond; - pthread_mutex_t mtx; + pthread_rwlock_t lock; + } db; - pthread_rwlock_t lock; + struct { + struct llist ll; + pthread_cond_t cond; + pthread_mutex_t mtx; + } reqs; - uint64_t eid; + struct { + struct list_head list; + pthread_cond_t cond; + pthread_mutex_t mtx; + } cmds; +} dht; - struct tpm * tpm; - pthread_t worker; -}; +/* DHT RIB */ -struct join_info { - struct dht * dht; - uint64_t addr; +static const char * dht_dir[] = { + "database", + "stats", + NULL }; -struct packet_info { - struct dht * dht; - struct shm_du_buff * sdb; -}; +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"; -static uint8_t * dht_dup_key(const uint8_t * key, - size_t len) +static int dht_rib_statfile(char * buf, + size_t len) { - uint8_t * dup; + struct tm * tm; + char tmstr[RIB_TM_STRLEN]; + size_t keys; + size_t vals; + size_t lvals; - dup = malloc(sizeof(*dup) * len); - if (dup == NULL) - return NULL; + assert(buf != NULL); + assert(len > 0); - memcpy(dup, key, len); + pthread_rwlock_rdlock(&dht.db.lock); - return dup; -} + keys = dht.db.kv.ll.len; + lvals = dht.db.kv.lvals; + vals = dht.db.kv.vals; -static enum dht_state dht_get_state(struct dht * dht) -{ - enum dht_state state; - - pthread_mutex_lock(&dht->mtx); + pthread_rwlock_unlock(&dht.db.lock); - state = dht->state; + tm = gmtime(&dht.t0); + strftime(tmstr, sizeof(tmstr), RIB_TM_FORMAT, tm); - pthread_mutex_unlock(&dht->mtx); + snprintf(buf, len, dht_stats, + HASH_VAL64(dht.id.data), + tmstr, + ADDR_VAL32(&dht.addr), + dht.alpha, dht.k, + dht.t_exp, dht.t_refr, dht.t_repl, + keys, vals, lvals); - return state; + return strlen(buf); } -static int dht_set_state(struct dht * dht, - enum dht_state state) +static size_t dht_db_file_len(void) { - pthread_mutex_lock(&dht->mtx); + size_t sz; + size_t vals; + + sz = 18; /* DHT database + 2 * \n */ - if (state == DHT_JOINING && dht->state != DHT_INIT) { - pthread_mutex_unlock(&dht->mtx); - return -1; + pthread_rwlock_rdlock(&dht.db.lock); + + if (llist_is_empty(&dht.db.kv.ll)) { + pthread_rwlock_unlock(&dht.db.lock); + sz += 14; /* No entries */ + return sz; } - dht->state = state; + sz += 39 * 3 + 1; /* tally + extra newline */ + sz += dht.db.kv.ll.len * (25 + 19 + 23 + 1); - pthread_cond_broadcast(&dht->cond); + vals = dht.db.kv.vals + dht.db.kv.lvals; - pthread_mutex_unlock(&dht->mtx); + sz += vals * (48 + 2 * RIB_TM_STRLEN); - return 0; + pthread_rwlock_unlock(&dht.db.lock); + + return sz; } -int dht_wait_running(void * dir) +static int dht_rib_dbfile(char * buf, + size_t len) { - struct dht * dht; - int ret = 0; + struct tm * tm; + char tmstr[RIB_TM_STRLEN]; + char exstr[RIB_TM_STRLEN]; + size_t i = 0; + struct list_head * p; - dht = (struct dht *) dir; + assert(buf != NULL); + assert(len > 0); - pthread_mutex_lock(&dht->mtx); + pthread_rwlock_rdlock(&dht.db.lock); - pthread_cleanup_push(__cleanup_mutex_unlock, &dht->mtx); + if (llist_is_empty(&dht.db.kv.ll)) { + i += snprintf(buf, len, " No entries.\n"); + pthread_rwlock_unlock(&dht.db.lock); + return i; + } - while (dht->state == DHT_JOINING) - pthread_cond_wait(&dht->cond, &dht->mtx); + 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.ll.len, dht.db.kv.vals, dht.db.kv.lvals); - if (dht->state != DHT_RUNNING) - ret = -1; + llist_for_each(p, &dht.db.kv.ll) { + struct dht_entry * e = list_entry(p, struct dht_entry, next); + struct list_head * h; - pthread_cleanup_pop(true); + i += snprintf(buf + i, len - i, "Key: " KEY_FMT "\n", + KEY_VAL(e->key)); + i += snprintf(buf + i, len - i, " Local entries:\n"); - return ret; -} + llist_for_each(h, &e->vals) { + struct val_entry * v; -static uint8_t * create_id(size_t len) -{ - uint8_t * id; + v = list_entry(h, struct val_entry, next); - id = malloc(len); - if (id == NULL) - return NULL; + tm = gmtime(&v->t_repl); + strftime(tmstr, sizeof(tmstr), RIB_TM_FORMAT, tm); - if (random_buffer(id, len) < 0) { - free(id); - return NULL; - } + tm = gmtime(&v->t_exp); + strftime(exstr, sizeof(exstr), RIB_TM_FORMAT, tm); - return id; -} + i += snprintf(buf + i, len - i, + " " VAL_FMT + ", t_replicated=%.*s, t_exp=%.*s\n", + VAL_VAL(v->val), + RIB_TM_STRLEN, tmstr, + RIB_TM_STRLEN, exstr); + } -static void kad_req_create(struct dht * dht, - dht_msg_t * msg, - uint64_t addr) -{ - struct kad_req * req; - pthread_condattr_t cattr; - struct timespec t; - size_t b; + i += snprintf(buf + i, len - i, "\n"); - clock_gettime(CLOCK_REALTIME_COARSE, &t); + i += snprintf(buf + i, len - i, " Non-local entries:\n"); - req = malloc(sizeof(*req)); - if (req == NULL) - goto fail_malloc; + llist_for_each(h, &e->lvals) { + struct val_entry * v; - list_head_init(&req->next); + v= list_entry(h, struct val_entry, next); - 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; + tm = gmtime(&v->t_repl); + strftime(tmstr, sizeof(tmstr), RIB_TM_FORMAT, tm); - pthread_rwlock_rdlock(&dht->lock); - b = dht->b; - pthread_rwlock_unlock(&dht->lock); + tm = gmtime(&v->t_exp); + strftime(exstr, sizeof(exstr), RIB_TM_FORMAT, tm); - if (msg->has_key) { - req->key = dht_dup_key(msg->key.data, b); - if (req->key == NULL) - goto fail_dup_key; + i += snprintf(buf + i, len - i, + " " VAL_FMT + ", t_replicated=%.*s, t_exp=%.*s\n", + VAL_VAL(v->val), + RIB_TM_STRLEN, tmstr, + RIB_TM_STRLEN, exstr); + + } } - if (pthread_mutex_init(&req->lock, NULL)) - goto fail_mutex; + pthread_rwlock_unlock(&dht.db.lock); + printf("DHT RIB DB file generated (%zu bytes).\n", i); - if (pthread_condattr_init(&cattr)) - goto fail_condattr; -#ifndef __APPLE__ - pthread_condattr_setclock(&cattr, PTHREAD_COND_CLOCK); -#endif + return i; +} - if (pthread_cond_init(&req->cond, &cattr)) - goto fail_cond_init; +static int dht_rib_read(const char * path, + char * buf, + size_t len) +{ + char * entry; - pthread_condattr_destroy(&cattr); + entry = strstr(path, RIB_SEPARATOR) + 1; - pthread_rwlock_wrlock(&dht->lock); + if (strcmp(entry, "database") == 0) { + return dht_rib_dbfile(buf, len); + } else if (strcmp(entry, "stats") == 0) { + return dht_rib_statfile(buf, len); + } - list_add(&req->next, &dht->requests); + return 0; +} - pthread_rwlock_unlock(&dht->lock); +static int dht_rib_readdir(char *** buf) +{ + int i = 0; - return; + while (dht_dir[i++] != NULL); - fail_cond_init: - pthread_condattr_destroy(&cattr); - fail_condattr: - pthread_mutex_destroy(&req->lock); - fail_mutex: - free(req->key); - fail_dup_key: - free(req); - fail_malloc: - return; + *buf = malloc(sizeof(**buf) * i); + if (*buf == NULL) + goto fail_buf; + + i = 0; + + while (dht_dir[i] != NULL) { + (*buf)[i] = strdup(dht_dir[i]); + if ((*buf)[i] == NULL) + goto fail_dup; + i++; + } + + return i; + fail_dup: + freepp(char, *buf, i); + fail_buf: + return -ENOMEM; } -static void cancel_req_destroy(void * o) +static int dht_rib_getattr(const char * path, + struct rib_attr * attr) { - struct kad_req * req = (struct kad_req *) o; + struct timespec now; + char * entry; - pthread_mutex_unlock(&req->lock); + clock_gettime(CLOCK_REALTIME_COARSE, &now); - pthread_cond_destroy(&req->cond); - pthread_mutex_destroy(&req->lock); + attr->mtime = now.tv_sec; - if (req->key != NULL) - free(req->key); + entry = strstr(path, RIB_SEPARATOR) + 1; - free(req); + if (strcmp(entry, "database") == 0) { + attr->size = dht_db_file_len(); + } else if (strcmp(entry, "stats") == 0) { + attr->size = 545; + } + + return 0; } -static void kad_req_destroy(struct kad_req * req) -{ - assert(req); +static struct rib_ops r_ops = { + .read = dht_rib_read, + .readdir = dht_rib_readdir, + .getattr = dht_rib_getattr +}; - pthread_mutex_lock(&req->lock); +/* Helper functions */ - switch (req->state) { - case REQ_DESTROY: - pthread_mutex_unlock(&req->lock); - return; - case REQ_PENDING: - req->state = REQ_DESTROY; - pthread_cond_broadcast(&req->cond); - break; - case REQ_INIT: - case REQ_DONE: - req->state = REQ_NULL; - break; - case REQ_RESPONSE: - case REQ_NULL: - default: - break; +static uint8_t * generate_id(void) +{ + uint8_t * id; + + 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_cleanup_push(cancel_req_destroy, req); + id = malloc(dht.id.len); + if (id == NULL) { + log_err("Failed to malloc ID."); + goto fail_id; + } - while (req->state != REQ_NULL && req->state != REQ_DONE) - pthread_cond_wait(&req->cond, &req->lock); + if (random_buffer(id, dht.id.len) < 0) { + log_err("Failed to generate random ID."); + goto fail_rnd; + } - pthread_cleanup_pop(true); + return id; + fail_rnd: + free(id); + fail_id: + return NULL; } -static int kad_req_wait(struct kad_req * req, - time_t t) +static uint64_t generate_cookie(void) { - struct timespec timeo = TIMESPEC_INIT_S(0); - struct timespec abs; - int ret = 0; + uint64_t cookie = DHT_INVALID; - assert(req); + while (cookie == DHT_INVALID) + random_buffer((uint8_t *) &cookie, sizeof(cookie)); - timeo.tv_sec = t; + return cookie; +} - clock_gettime(PTHREAD_COND_CLOCK, &abs); +/* + * 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)); - ts_add(&abs, &timeo, &abs); + return betoh64(*((uint64_t *) src) ^ *((uint64_t *) dst)); +} - pthread_mutex_lock(&req->lock); +#define IS_CLOSER(x, y) (dist((x), dht.id.data) < dist((y), dht.id.data)) - req->state = REQ_PENDING; +static int addr_to_buf(const uint64_t addr, + buffer_t * buf) +{ + size_t len; + uint64_t _addr; - pthread_cleanup_push(__cleanup_mutex_unlock, &req->lock); + len = sizeof(addr); + _addr = hton64(addr); - while (req->state == REQ_PENDING && ret != -ETIMEDOUT) - ret = -pthread_cond_timedwait(&req->cond, &req->lock, &abs); + assert(buf != NULL); - 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; - } + buf->data = malloc(len); + if (buf->data == NULL) + goto fail_malloc; - pthread_cleanup_pop(true); + buf->len = sizeof(_addr); + memcpy(buf->data, &_addr, sizeof(_addr)); - return ret; + return 0; + fail_malloc: + return -ENOMEM; } -static void kad_req_respond(struct kad_req * req) +static int buf_to_addr(const buffer_t buf, + uint64_t * addr) { - pthread_mutex_lock(&req->lock); + assert(addr != NULL); + assert(buf.data != NULL); - req->state = REQ_RESPONSE; - pthread_cond_signal(&req->cond); + if (buf.len != sizeof(*addr)) + return - EINVAL; - pthread_mutex_unlock(&req->lock); -} + *addr = ntoh64(*((uint64_t *) buf.data)); -static struct contact * contact_create(const uint8_t * id, - size_t len, - uint64_t addr) -{ - struct contact * c; - struct timespec t; + if (*addr == dht.addr) + *addr = INVALID_ADDR; - c = malloc(sizeof(*c)); - if (c == NULL) - return NULL; + return 0; +} - list_head_init(&c->next); +static uint8_t * dht_dup_key(const uint8_t * key) +{ + uint8_t * dup; - clock_gettime(CLOCK_REALTIME_COARSE, &t); + assert(key != NULL); + assert(dht.id.len != 0); - 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); + dup = malloc(dht.id.len); + if (dup == NULL) return NULL; - } - return c; + memcpy(dup, key, dht.id.len); + + return dup; } -static void contact_destroy(struct contact * c) +/* DHT */ + +static struct val_entry * val_entry_create(const buffer_t val, + time_t exp) { - if (c != NULL) - free(c->id); + struct val_entry * e; + struct timespec now; - free(c); -} + assert(val.data != NULL); + assert(val.len > 0); -static struct bucket * iter_bucket(struct bucket * b, - const uint8_t * id) -{ - uint8_t byte; - uint8_t mask; + clock_gettime(CLOCK_REALTIME_COARSE, &now); - assert(b); +#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; - if (b->children[0] == NULL) - return b; + list_head_init(&e->next); - byte = id[(b->depth * KAD_BETA) / CHAR_BIT]; + e->val.len = val.len; + e->val.data = malloc(val.len); + if (e->val.data == NULL) + goto fail_val; - mask = ((1L << KAD_BETA) - 1) & 0xFF; + memcpy(e->val.data, val.data, val.len); - byte >>= (CHAR_BIT - KAD_BETA) - - (((b->depth) * KAD_BETA) & (CHAR_BIT - 1)); + e->t_repl = 0; + e->t_exp = exp; - return iter_bucket(b->children[(byte & mask)], id); + return e; + + fail_val: + free(e); + fail_entry: + return NULL; } -static struct bucket * dht_get_bucket(struct dht * dht, - const uint8_t * id) +static void val_entry_destroy(struct val_entry * v) { - assert(dht->buckets); + assert(v->val.data != NULL); - return iter_bucket(dht->buckets, id); + freebuf(v->val); + free(v); } -/* - * 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 dht_entry * dht_entry_create(const uint8_t * key) { - return betoh64(*((uint64_t *) src) ^ *((uint64_t *) dst)); + struct dht_entry * e; + + assert(key != NULL); + + e = malloc(sizeof(*e)); + if (e == NULL) + goto fail_entry; + + list_head_init(&e->next); + llist_init(&e->vals); + llist_init(&e->lvals); + + e->key = dht_dup_key(key); + if (e->key == NULL) + goto fail_key; + + return e; + fail_key: + free(e); + fail_entry: + return NULL; } -static size_t list_add_sorted(struct list_head * l, - struct contact * c, - const uint8_t * key) +static void dht_entry_destroy(struct dht_entry * e) { struct list_head * p; + struct list_head * h; - assert(l); - assert(c); - assert(key); - assert(c->id); + assert(e != NULL); - list_for_each(p, l) { - struct contact * e = list_entry(p, struct contact, next); - if (dist(c->id, key) > dist(e->id, key)) - break; + llist_for_each_safe(p, h, &e->vals) { + struct val_entry * v = list_entry(p, struct val_entry, next); + llist_del(&v->next, &e->vals); + val_entry_destroy(v); + --dht.db.kv.vals; } - list_add_tail(&c->next, p); + llist_for_each_safe(p, h, &e->lvals) { + struct val_entry * v = list_entry(p, struct val_entry, next); + llist_del(&v->next, &e->lvals); + val_entry_destroy(v); + --dht.db.kv.lvals; + } - return 1; + free(e->key); + + assert(llist_is_empty(&e->vals) && llist_is_empty(&e->lvals)); + + free(e); } -static size_t dht_contact_list(struct dht * dht, - struct list_head * l, - const uint8_t * key) +static struct val_entry * dht_entry_get_lval(const struct dht_entry * e, + const buffer_t val) { struct list_head * p; - struct bucket * b; - size_t len = 0; - size_t i; - struct timespec t; - assert(l); - assert(dht); - assert(key); - assert(list_is_empty(l)); - - clock_gettime(CLOCK_REALTIME_COARSE, &t); + assert(e != NULL); + assert(val.data != NULL); + assert(val.len > 0); - b = dht_get_bucket(dht, key); - if (b == NULL) - return 0; - - b->t_refr = t.tv_sec + KAD_T_REFR; - - 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; - } - } + llist_for_each(p, &e->lvals) { + struct val_entry * v = list_entry(p, struct val_entry, next); + if (bufcmp(&v->val, &val) == 0) + return v; } - assert(len == dht->k || b->parent == NULL); - - return len; + return NULL; } -static struct lookup * lookup_create(struct dht * dht, - const uint8_t * id) +static struct val_entry * dht_entry_get_val(const struct dht_entry * e, + const buffer_t val) { - struct lookup * lu; - pthread_condattr_t cattr; + struct list_head * p; - 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; + llist_for_each(p, &e->vals) { + struct val_entry * v = list_entry(p, struct val_entry, next); + if (bufcmp(&v->val, &val) == 0) + return v; - list_head_init(&lu->contacts); - list_head_init(&lu->cookies); + } - 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; + return NULL; +} - if (pthread_mutex_init(&lu->lock, NULL)) - goto fail_mutex; +static int dht_entry_update_val(struct dht_entry * e, + buffer_t val, + time_t exp) +{ + struct val_entry * v; + struct timespec now; - pthread_condattr_init(&cattr); -#ifndef __APPLE__ - pthread_condattr_setclock(&cattr, PTHREAD_COND_CLOCK); -#endif + assert(e != NULL); + assert(val.data != NULL); + assert(val.len > 0); - if (pthread_cond_init(&lu->cond, &cattr)) - goto fail_cond; + clock_gettime(CLOCK_REALTIME_COARSE, &now); - pthread_condattr_destroy(&cattr); + if (exp < now.tv_sec) + return -EINVAL; /* Refuse to add expired values */ - pthread_rwlock_wrlock(&dht->lock); + 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 */ + } - list_add(&lu->next, &dht->lookups); + v = dht_entry_get_val(e, val); + if (v == NULL) { + v = val_entry_create(val, exp); + if (v == NULL) + return -ENOMEM; - lu->n_contacts = dht_contact_list(dht, &lu->contacts, id); + llist_add_tail(&v->next, &e->vals); + ++dht.db.kv.vals; - pthread_rwlock_unlock(&dht->lock); + return 0; + } - return lu; + if (v->t_exp < exp) + v->t_exp = exp; - fail_cond: - pthread_condattr_destroy(&cattr); - pthread_mutex_destroy(&lu->lock); - fail_mutex: - free(lu->key); - fail_id: - free(lu); - fail_malloc: - return NULL; + return 0; } -static void cancel_lookup_destroy(void * o) +static int dht_entry_update_lval(struct dht_entry * e, + buffer_t val) { - struct lookup * lu; - struct list_head * p; - struct list_head * h; + struct val_entry * v; + struct timespec now; - lu = (struct lookup *) o; + assert(e != NULL); + assert(val.data != NULL); + assert(val.len > 0); - if (lu->key != NULL) - free(lu->key); - if (lu->addrs != NULL) - free(lu->addrs); + clock_gettime(CLOCK_REALTIME_COARSE, &now); - list_for_each_safe(p, h, &lu->contacts) { - struct contact * c = list_entry(p, struct contact, next); - list_del(&c->next); - contact_destroy(c); - } + 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_exp); + if (v == NULL) + return -ENOMEM; - list_for_each_safe(p, h, &lu->cookies) { - struct cookie_el * c = list_entry(p, struct cookie_el, next); - list_del(&c->next); - free(c); - } + llist_add_tail(&v->next, &e->lvals); + ++dht.db.kv.lvals; - pthread_mutex_unlock(&lu->lock); - - pthread_mutex_destroy(&lu->lock); + return 0; + } - free(lu); + return 0; } -static void lookup_destroy(struct lookup * lu) +static int dht_entry_remove_lval(struct dht_entry * e, + buffer_t val) { - assert(lu); + struct val_entry * v; - pthread_mutex_lock(&lu->lock); + assert(e != NULL); + assert(val.data != NULL); + assert(val.len > 0); - 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; - } + v = dht_entry_get_lval(e, val); + if (v == NULL) + return -ENOENT; - pthread_cleanup_push(cancel_lookup_destroy, lu); + log_dbg(KV_FMT " Removing lval.", KV_VAL(e->key, val)); - while (lu->state != LU_NULL) - pthread_cond_wait(&lu->cond, &lu->lock); + llist_del(&v->next, &e->lvals); + val_entry_destroy(v); + --dht.db.kv.lvals; - pthread_cleanup_pop(true); + return 0; } -static void lookup_update(struct dht * dht, - struct lookup * lu, - dht_msg_t * msg) +#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 = NULL; + struct list_head * p; struct list_head * h; - struct contact * c = NULL; - size_t n; - size_t pos = 0; - bool mod = false; + struct timespec now; - assert(lu); - assert(msg); + assert(e != NULL); - if (dht_get_state(dht) != DHT_RUNNING) - return; + clock_gettime(CLOCK_REALTIME_COARSE, &now); - pthread_mutex_lock(&lu->lock); + llist_for_each_safe(p, h, &e->vals) { + struct val_entry * v = list_entry(p, struct val_entry, next); + if (!IS_EXPIRED(v, &now)) + continue; - 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; - } + log_dbg(KV_FMT " Value expired." , KV_VAL(e->key, v->val)); + llist_del(&v->next, &e->vals); + val_entry_destroy(v); + --dht.db.kv.vals; } +} - if (lu->state == LU_COMPLETE) { - pthread_mutex_unlock(&lu->lock); - return; - } +static struct dht_entry * __dht_kv_find_entry(const uint8_t * key) +{ + struct list_head * p; - 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; - } + assert(key != NULL); - lu->state = LU_COMPLETE; - pthread_cond_broadcast(&lu->cond); - pthread_mutex_unlock(&lu->lock); - return; + llist_for_each(p, &dht.db.kv.ll) { + struct dht_entry * e = list_entry(p, struct dht_entry, next); + if (!memcmp(key, e->key, dht.id.len)) + return e; } - pthread_cleanup_push(__cleanup_mutex_unlock, &lu->lock); + return NULL; +} - while (lu->state == LU_INIT) { - pthread_rwlock_unlock(&dht->lock); - pthread_cond_wait(&lu->cond, &lu->lock); - pthread_rwlock_rdlock(&dht->lock); - } +static void dht_kv_remove_expired_entries(void) +{ + struct list_head * p; + struct list_head * h; + struct timespec now; - pthread_cleanup_pop(false); + clock_gettime(CLOCK_REALTIME_COARSE, &now); + + pthread_rwlock_wrlock(&dht.db.lock); - 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) + llist_for_each_safe(p, h, &dht.db.kv.ll) { + 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; - pos = 0; + log_dbg(KEY_FMT " Entry removed. ", KEY_VAL(e->key)); + llist_del(&e->next, &dht.db.kv.ll); + dht_entry_destroy(e); + } + + pthread_rwlock_unlock(&dht.db.lock); +} - 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; - } - if (dist(c->id, lu->key) > dist(e->id, lu->key)) - break; +static struct contact * contact_create(const uint8_t * id, + uint64_t addr) +{ + struct contact * c; + struct timespec t; - pos++; - } + c = malloc(sizeof(*c)); + if (c == NULL) + return NULL; - if (c == NULL) - continue; + list_head_init(&c->next); - 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; - } + 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; } - if (list_is_empty(&lu->cookies) && !mod) - lu->state = LU_COMPLETE; - else - lu->state = LU_UPDATE; + return c; +} - pthread_cond_broadcast(&lu->cond); - pthread_mutex_unlock(&lu->lock); - return; +static void contact_destroy(struct contact * c) +{ + assert(c != NULL); + assert(list_is_empty(&c->next)); + + free(c->id); + free(c); } -static ssize_t lookup_get_addrs(struct lookup * lu, - uint64_t * addrs) +static struct dht_req * dht_req_create(const uint8_t * key) { - ssize_t n; + struct dht_req * req; + struct timespec now; - assert(lu); + assert(key != NULL); - pthread_mutex_lock(&lu->lock); + clock_gettime(PTHREAD_COND_CLOCK, &now); - for (n = 0; (size_t) n < lu->n_addrs; ++n) - addrs[n] = lu->addrs[n]; + req = malloc(sizeof(*req)); + if (req == NULL) + goto fail_malloc; - assert((size_t) n == lu->n_addrs); + list_head_init(&req->next); - pthread_mutex_unlock(&lu->lock); + req->t_exp = now.tv_sec + DHT_T_RESP; - return n; + llist_init(&req->peers); + + req->key = dht_dup_key(key); + if (req->key == NULL) + goto fail_dup_key; + + llist_init(&req->cache); + + return req; + + fail_dup_key: + free(req); + fail_malloc: + return NULL; } -static ssize_t lookup_contact_addrs(struct lookup * lu, - uint64_t * addrs) +static void dht_req_destroy(struct dht_req * req) { struct list_head * p; - ssize_t n = 0; + struct list_head * h; - assert(lu); - assert(addrs); + assert(req); + assert(req->key); - pthread_mutex_lock(&lu->lock); + llist_for_each_safe(p, h, &req->peers) { + struct peer_entry * e = list_entry(p, struct peer_entry, next); + llist_del(&e->next, &req->peers); + free(e->id); + free(e); + } - list_for_each(p, &lu->contacts) { - struct contact * c = list_entry(p, struct contact, next); - addrs[n] = c->addr; - n++; + llist_for_each_safe(p, h, &req->cache) { + struct val_entry * e = list_entry(p, struct val_entry, next); + llist_del(&e->next, &req->cache); + val_entry_destroy(e); } - pthread_mutex_unlock(&lu->lock); + free(req->key); - return n; + assert(llist_is_empty(&req->peers)); + + free(req); } -static void lookup_new_addrs(struct lookup * lu, - uint64_t * addrs) +static struct peer_entry * dht_req_get_peer(struct dht_req * req, + const struct peer_entry * e) { struct list_head * p; - size_t n = 0; - assert(lu); - assert(addrs); + llist_for_each(p, &req->peers) { + struct peer_entry * x = list_entry(p, struct peer_entry, next); + if (x->addr == e->addr) + return x; + } - pthread_mutex_lock(&lu->lock); + return NULL; +} - /* 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++; +#define IS_MAGIC(peer) ((peer)->cookie == dht.magic) +static int dht_req_add_peer(struct dht_req * req, + const struct peer_entry * e) +{ + struct peer_entry * x; /* existing */ + struct list_head * p; /* iterator */ + size_t pos = 0; + + assert(req != NULL); + assert(e != NULL); + assert(e->id != NULL); + + /* + * Dedupe messages to the same peer, unless + * 1) The previous was FIND_NODE and now it's FIND_VALUE + * 2) We urgently need contacts (magic cookie) + */ + x = dht_req_get_peer(req, e); + if (x != NULL && x->code >= e->code && !IS_MAGIC(e)) + return -1; + + /* Find how this contact ranks in distance to the key */ + llist_for_each(p, &req->peers) { + struct peer_entry * y; + y = list_entry(p, struct peer_entry, next); + if (IS_CLOSER(y->id, e->id)) { + pos++; + continue; } + break; + } - if (n == KAD_ALPHA) - break; + if (pos >= dht.alpha && IS_MAGIC(e)) + return -1; + + x = malloc(sizeof(*x)); + if (x == NULL) { + log_err("Failed to malloc peer entry."); + return -1; } - assert(n <= KAD_ALPHA); + 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); + return -1; + } - addrs[n] = 0; + if (IS_MAGIC(e)) + llist_add_at(&x->next, p, &req->peers); + else + llist_add_tail_at(&x->next, p, &req->peers); - pthread_mutex_unlock(&lu->lock); + return 0; } -static void lookup_set_state(struct lookup * lu, - enum lookup_state state) +static size_t dht_req_add_peers(struct dht_req * req, + struct list_head * pl) { - pthread_mutex_lock(&lu->lock); + struct list_head * p; + struct list_head * h; + size_t n = 0; - lu->state = state; - pthread_cond_broadcast(&lu->cond); + assert(req != NULL); + assert(pl != NULL); - pthread_mutex_unlock(&lu->lock); + list_for_each_safe(p, h, pl) { + struct peer_entry * e; + e = list_entry(p, struct peer_entry, next); + if (dht_req_add_peer(req, e) < 0) { + list_del(&e->next); + free(e->id); + free(e); + } + } + + return n; } -static void cancel_lookup_wait(void * o) +static bool dht_req_has_peer(struct dht_req * req, + uint64_t cookie) { - struct lookup * lu = (struct lookup *) o; - lu->state = LU_NULL; - pthread_mutex_unlock(&lu->lock); - lookup_destroy(lu); + struct list_head * p; + + assert(req != NULL); + + llist_for_each(p, &req->peers) { + struct peer_entry * e = list_entry(p, struct peer_entry, next); + if (e->cookie == cookie) + return true; + } + + return false; } -static enum lookup_state lookup_wait(struct lookup * lu) +static void peer_list_destroy(struct list_head * pl) { - struct timespec timeo = TIMESPEC_INIT_S(KAD_T_RESP); - struct timespec abs; - enum lookup_state state; - int ret = 0; + struct list_head * p; + struct list_head * h; - clock_gettime(PTHREAD_COND_CLOCK, &abs); + assert(pl != NULL); - ts_add(&abs, &timeo, &abs); + 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); + } +} - pthread_mutex_lock(&lu->lock); +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; - if (lu->state == LU_INIT || lu->state == LU_UPDATE) - lu->state = LU_PENDING; + assert(cl != NULL); + assert(pl != NULL); + assert(list_is_empty(pl)); - pthread_cleanup_push(cancel_lookup_wait, lu); + clock_gettime(CLOCK_REALTIME_COARSE, &now); - while (lu->state == LU_PENDING && ret != -ETIMEDOUT) - ret = -pthread_cond_timedwait(&lu->cond, &lu->lock, &abs); + len = 0; - pthread_cleanup_pop(false); + 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; - if (ret == -ETIMEDOUT) - lu->state = LU_COMPLETE; + e = malloc(sizeof(*e)); + if (e == NULL) + return -ENOMEM; - state = lu->state; + 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; - return state; + list_add_tail(&e->next, pl); + + list_del(&c->next); + c->id = NULL; /* we stole the id */ + contact_destroy(c); + } + + return 0; } -static struct kad_req * dht_find_request(struct dht * dht, - dht_msg_t * msg) +static struct dht_req * __dht_kv_req_get_req(const uint8_t * key) { struct list_head * p; - assert(dht); - assert(msg); - - list_for_each(p, &dht->requests) { - struct kad_req * r = list_entry(p, struct kad_req, next); - if (r->cookie == msg->cookie) + llist_for_each(p, &dht.reqs.ll) { + 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 struct lookup * dht_find_lookup(struct dht * dht, - uint32_t cookie) +static struct dht_req * __dht_kv_get_req_cache(const uint8_t * key) { - 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; - } - } - pthread_mutex_unlock(&l->lock); - } + struct dht_req * req; - return NULL; + assert(key != NULL); + + req = __dht_kv_req_get_req(key); + if (req == NULL) + return NULL; + + if (llist_is_empty(&req->cache)) + return NULL; + + return req; } -static struct val * val_create(uint64_t addr, - time_t exp) +static void __dht_kv_req_remove(const uint8_t * key) { - struct val * v; - struct timespec t; + struct dht_req * req; - v = malloc(sizeof(*v)); - if (v == NULL) - return NULL; + assert(key != NULL); + + req = __dht_kv_req_get_req(key); + if (req == NULL) + return; - list_head_init(&v->next); - v->addr = addr; + llist_del(&req->next, &dht.reqs.ll); - clock_gettime(CLOCK_REALTIME_COARSE, &t); + dht_req_destroy(req); +} + +static struct dht_req * __dht_kv_get_req_peer(const uint8_t * key, + uint64_t cookie) +{ + struct dht_req * req; - v->t_exp = t.tv_sec + exp; - v->t_rep = t.tv_sec + KAD_T_REPL; + assert(key != NULL); + + req = __dht_kv_req_get_req(key); + if (req == NULL) + return NULL; + + if (!dht_req_has_peer(req, cookie)) + return NULL; - return v; + return req; } -static void val_destroy(struct val * v) +static bool dht_kv_has_req(const uint8_t * key, + uint64_t cookie) { - assert(v); + bool found; - free(v); + pthread_mutex_lock(&dht.reqs.mtx); + + found = __dht_kv_get_req_peer(key, cookie) != NULL; + + pthread_mutex_unlock(&dht.reqs.mtx); + + return found; } -static struct ref_entry * ref_entry_create(struct dht * dht, - const uint8_t * key) +/* + * 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 ref_entry * e; - struct timespec t; + struct dht_req * req; + struct timespec now; - assert(dht); - assert(key); + assert(key != NULL); + assert(pl != NULL); + assert(!list_is_empty(pl)); - e = malloc(sizeof(*e)); - if (e == NULL) - return NULL; + clock_gettime(PTHREAD_COND_CLOCK, &now); - e->key = dht_dup_key(key, dht->b); - if (e->key == NULL) { - free(e); - return NULL; + pthread_mutex_lock(&dht.reqs.mtx); + + req = __dht_kv_req_get_req(key); + if (req == NULL) { + if (dht.reqs.ll.len == DHT_MAX_REQS) { + log_err(KEY_FMT " Max reqs reached (%zu).", + KEY_VAL(key), dht.reqs.ll.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; + } + llist_add_tail(&req->next, &dht.reqs.ll); } - clock_gettime(CLOCK_REALTIME_COARSE, &t); + if (req->cache.len > 0) /* Already have values */ + peer_list_destroy(pl); - e->t_rep = t.tv_sec + dht->t_repub; + dht_req_add_peers(req, pl); + req->t_exp = now.tv_sec + DHT_T_RESP; - return e; + if (dht.reqs.ll.len > DHT_WARN_REQS) { + log_warn("Number of outstanding requests (%zu) exceeds %u.", + dht.reqs.ll.len, DHT_WARN_REQS); + } + + pthread_mutex_unlock(&dht.reqs.mtx); + + return 0; + fail_req: + pthread_mutex_unlock(&dht.reqs.mtx); + return -1; } -static void ref_entry_destroy(struct ref_entry * e) +static int dht_kv_respond_req(uint8_t * key, + binary_data_t * vals, + size_t len) { - free(e->key); - free(e); + struct dht_req * req; + struct timespec now; + size_t i; + + assert(key != NULL); + assert(vals != NULL); + assert(len > 0); + + clock_gettime(CLOCK_REALTIME_COARSE, &now); + + 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; + } + + llist_add_tail(&e->next, &req->cache); + } + + pthread_cond_broadcast(&dht.reqs.cond); + + pthread_mutex_unlock(&dht.reqs.mtx); + + return 0; + fail_req: + pthread_mutex_unlock(&dht.reqs.mtx); + return -1; } -static struct dht_entry * dht_entry_create(struct dht * dht, - const uint8_t * key) +static ssize_t dht_kv_wait_req(const uint8_t * key, + buffer_t ** vals) { - struct dht_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); - list_head_init(&e->next); - list_head_init(&e->vals); + ts_add(&t, &intv, &t); - e->n_vals = 0; + pthread_mutex_lock(&dht.reqs.mtx); - e->key = dht_dup_key(key, dht->b); - if (e->key == NULL) { - free(e); - return NULL; + 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; } - return e; + pthread_cleanup_pop(false); + + if (ret == ETIMEDOUT) { + log_warn(KEY_FMT " Req timed out.", KEY_VAL(key)); + __dht_kv_req_remove(key); + goto timedout; + } + + max = MIN(req->cache.len, DHT_MAX_VALS); + if (max == 0) + goto no_vals; + + *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)); + + llist_for_each(p, &req->cache) { + 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 void dht_entry_destroy(struct dht_entry * e) +static struct bucket * iter_bucket(struct bucket * b, + const uint8_t * id) +{ + uint8_t byte; + uint8_t mask; + + assert(b != NULL); + + if (b->children[0] == NULL) + return b; + + byte = id[(b->depth * DHT_BETA) / CHAR_BIT]; + + mask = ((1L << DHT_BETA) - 1) & 0xFF; + + byte >>= (CHAR_BIT - DHT_BETA) - + (((b->depth) * DHT_BETA) & (CHAR_BIT - 1)); + + return iter_bucket(b->children[(byte & mask)], id); +} + +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_refr; - list_add(&val->next, &e->vals); - ++e->n_vals; + if (b->contacts.len == dht.k || b->parent == NULL) { + llist_for_each(p, &b->contacts) { + 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) { + llist_for_each(p, &d->children[i]->contacts) { + 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; + } + } + } - return 0; -} + pthread_rwlock_unlock(&dht.db.lock); + 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 (*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 (llist_is_empty(&b->contacts)) + return; + + c = llist_first_entry(&b->contacts, struct contact, next); + if (t > c->t_seen + dht.t_refr) { + d = contact_create(c->id, c->addr); if (d != NULL) list_add(&d->next, r); - return; } } - static struct bucket * bucket_create(void) { struct bucket * b; @@ -1294,20 +1645,19 @@ static struct bucket * bucket_create(void) if (b == NULL) return NULL; - list_head_init(&b->contacts); - b->n_contacts = 0; + llist_init(&b->contacts); - list_head_init(&b->alts); - b->n_alts = 0; + llist_init(&b->alts); clock_gettime(CLOCK_REALTIME_COARSE, &t); - b->t_refr = t.tv_sec + KAD_T_REFR; + b->t_refr = t.tv_sec + dht.t_refr; - 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; } @@ -1318,24 +1668,22 @@ 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) { + llist_for_each_safe(p, h, &b->contacts) { struct contact * c = list_entry(p, struct contact, next); - list_del(&c->next); + llist_del(&c->next, &b->contacts); contact_destroy(c); - --b->n_contacts; } - list_for_each_safe(p, h, &b->alts) { + llist_for_each_safe(p, h, &b->alts) { struct contact * c = list_entry(p, struct contact, next); - list_del(&c->next); + llist_del(&c->next, &b->alts); contact_destroy(c); - --b->n_contacts; } free(b); @@ -1350,1534 +1698,2345 @@ 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); + + llist_for_each_safe(p, h, &b->contacts) { + d = list_entry(p, struct contact, next); + if (bucket_has_id(c, d->id)) { + llist_del(&d->next, &b->contacts); + llist_add_tail(&d->next, &c->contacts); + } + } + + 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(llist_is_empty(&b->alts)); + assert(!llist_is_empty(&b->contacts)); 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) { + llist_for_each_safe(p, h, &b->contacts) { struct contact * d = list_entry(p, struct contact, next); if (d->addr == addr) { - list_del(&d->next); + llist_del(&d->next, &b->contacts); contact_destroy(d); - --b->n_contacts; } } - 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)) { + llist_add_tail(&c->next, &b->contacts); if (split_bucket(b)) { - list_del(&c->next); + llist_del(&c->next, &b->contacts); contact_destroy(c); - --b->n_contacts; } - } 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); - list_del(&d->next); + d = llist_first_entry(&b->alts, + struct contact, next); + llist_del(&d->next, &b->alts); contact_destroy(d); - list_add_tail(&c->next, &b->alts); + llist_add_tail(&c->next, &b->alts); } else { - list_add_tail(&c->next, &b->alts); - ++b->n_alts; + llist_add_tail(&c->next, &b->alts); } } else { - list_add_tail(&c->next, &b->contacts); - ++b->n_contacts; + llist_add_tail(&c->next, &b->contacts); } + 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, - dht_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 = dht_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; - dht_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 dht_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, - dht_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) { - dht_msg_t msg = DHT_MSG__INIT; - dht_contact_msg_t cmsg = DHT_CONTACT_MSG__INIT; - dht_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; + + llist_for_each(p, &e->vals) { + 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); + } + + llist_for_each(p, &e->lvals) { + 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_t msg = DHT_MSG__INIT; - ssize_t sent = 0; + dht_msg__free_unpacked((dht_msg_t *) msg, NULL); +} - assert(dht); - assert(lu->key); +#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; - msg.code = code; + if (msg == NULL) + return; - msg.has_key = true; - msg.key.data = (uint8_t *) lu->key; - msg.key.len = dht->b; + 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)); + } - while (*addrs != 0) { - struct cookie_el * c; - int ret; + break; + default: + break; + } - if (*addrs == dht->addr) { - ++addrs; - continue; - } + pthread_cleanup_pop(false); +} - ret = send_msg(dht, &msg, *addrs); - if (ret < 0) - break; +static void dht_kv_debug_msg_snd(dht_msg_t * msg, + uint8_t * id, + uint64_t addr) +{ + if (msg == NULL) + return; - c = malloc(sizeof(*c)); - if (c == NULL) - break; + log_proto(TX_HDR_FMT ".", TX_HDR_VAL(msg, id, addr)); - c->cookie = (uint32_t) ret; + dht_kv_debug_msg(msg); +} - pthread_mutex_lock(&lu->lock); +static void dht_kv_debug_msg_rcv(dht_msg_t * msg) +{ + if (msg == NULL) + return; - list_add_tail(&c->next, &lu->cookies); + log_proto(RX_HDR_FMT ".", RX_HDR_VAL(msg)); - pthread_mutex_unlock(&lu->lock); + dht_kv_debug_msg(msg); +} +#endif + +#ifndef __DHT_TEST__ +static int dht_send_msg(dht_msg_t * msg, + uint64_t addr) +{ + size_t len; + struct ssm_pk_buff * spb; - ++sent; - ++addrs; + if (msg == NULL) + return 0; + + assert(addr != INVALID_ADDR && addr != dht.addr); + + len = dht_msg__get_packed_size(msg); + if (len == 0) { + log_warn("%s failed to pack.", DHT_CODE(msg)); + goto fail_msg; + } + + if (ipcp_spb_reserve(&spb, len)) { + log_warn("%s failed to get spb.", DHT_CODE(msg)); + goto fail_msg; } - return sent; + dht_msg__pack(msg, ssm_pk_buff_head(spb)); + + if (dt_write_packet(addr, QOS_CUBE_BE, dht.eid, spb, NULL) < 0) { + log_warn("%s write failed", DHT_CODE(msg)); + goto fail_send; + } + + 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; -static void lookup_detach(struct dht * dht, - struct lookup * lu) + 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); + + 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); + + clock_gettime(CLOCK_REALTIME_COARSE, &now); + 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; + } - assert(dht); - assert(key); + list_head_init(&pl); - pthread_rwlock_rdlock(&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; + } - k = dht->k; - t_expire = dht->t_expire; + 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; + } - pthread_rwlock_unlock(&dht->lock); + dht_msg__free_unpacked(msg, NULL); - addrs = malloc(k * sizeof(*addrs)); - if (addrs == NULL) - return; + return 0; + fail_msgs: + peer_list_destroy(&pl); + fail_peer_list: + dht_msg__free_unpacked(msg, NULL); + fail_msg: + return -1; +} - lu = kad_lookup(dht, key, KAD_FIND_NODE); - if (lu == NULL) { - free(addrs); - return; +/* 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) { - dht_contact_msg_t msg = DHT_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; } - lookup_destroy(lu); + 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; + } - free(addrs); + dht_msg__free_unpacked(msg, NULL); + + 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) { - dht_msg_t msg = DHT_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; + llist_for_each(p, &dht.db.kv.ll) { + struct dht_entry * d = list_entry(p, struct dht_entry, next); + if (IS_CLOSER(d->key, e->key)) + continue; + break; + } + + llist_add_tail_at(&e->next, p, &dht.db.kv.ll); } -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) { + llist_del(&e->next, &dht.db.kv.ll); + dht_entry_destroy(e); } + 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; + + assert(key != NULL); + assert(val.data != NULL); + assert(val.len > 0); + + clock_gettime(CLOCK_REALTIME_COARSE, &now); - e = dht_find_entry(dht, key); + pthread_rwlock_wrlock(&dht.db.lock); + + e = __dht_kv_find_entry(key); if (e == NULL) { - 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.db.lock); + + dht_kv_store_remote(key, val, now.tv_sec + dht.t_exp); return 0; + fail_add: + if (new) { + llist_del(&e->next, &dht.db.kv.ll); + dht_entry_destroy(e); + } + 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; - pthread_rwlock_rdlock(&dht->lock); + assert(key != NULL); - e = dht_find_entry(dht, key); + pthread_rwlock_wrlock(&dht.db.lock); + + 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; - buf.data = NULL; - return buf; + if (store->val.data == NULL || store->val.len == 0) { + log_warn("Invalid value in DHT store msg."); + return -EINVAL; + } + + return 0; } -static ssize_t dht_get_contacts(struct dht * dht, - const uint8_t * key, - dht_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; +} - dht_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); + assert(store != NULL); + + val.data = store->val.data; + val.len = store->val.len; + key = store->key.data; + exp = store->exp; + + if (dht_kv_store(key, 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); - /* 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); - } - } + free(contacts); + fail_contacts: + return NULL; +} + +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, - dht_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 (contacts != NULL && n_contacts-- > 0) + dht_contact_msg__free_unpacked(contacts[n_contacts], NULL); - dht->k = msg->k; - dht->b = msg->b; - dht->t_expire = msg->t_expire; - dht->t_repub = MAX(1, dht->t_expire - 10); + free(contacts); + fail_contacts: + return NULL; +} - if (pthread_create(&dht->worker, NULL, work, dht)) { - bucket_destroy(dht->buckets); - pthread_rwlock_unlock(&dht->lock); - return -1; - } +static void do_dht_kv_find_node_rsp(const dht_find_node_rsp_msg_t * rsp) +{ + struct list_head pl; - kad_req_respond(req); + assert(rsp != NULL); - dht_update_bucket(dht, msg->s_id.data, msg->s_addr); + list_head_init(&pl); - pthread_rwlock_unlock(&dht->lock); + dht_kv_process_node_rsp(rsp->contacts, rsp->n_contacts, &pl, + DHT_FIND_NODE_REQ); - log_dbg("Enrollment of DHT completed."); + if (list_is_empty(&pl)) + goto no_contacts; - return 0; + 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; + } + + dht_kv_query_contacts(rsp->key.data, &pl); + + return; + + fail_update: + peer_list_destroy(&pl); + no_contacts: + return; } -static int kad_handle_find_resp(struct dht * dht, - struct kad_req * req, - dht_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; - 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; + } - return 0; + dht_kv_query_remote(key, NULL, &pl); + + return; + fail_update: + peer_list_destroy(&pl); + no_contacts: + return; } -static void kad_handle_response(struct dht * dht, - dht_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; - pthread_rwlock_unlock(&dht->lock); + if (dht_kv_update_contacts(id, addr) < 0) + log_warn(PEER_FMT " Failed to update contact from msg src.", + PEER_VAL(id, addr)); - switch(req->code) { - case KAD_JOIN: - if (kad_handle_join_resp(dht, req, msg)) - log_err("Enrollment of DHT failed."); + pthread_cleanup_push(__cleanup_dht_msg, msg); + + 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); + do_dht_msg(msg); + + tpm_end_work(dht.tpm); + } + + return (void *) 0; +} #ifndef __DHT_TEST__ - dht->b = hash_len(ipcpi.dir_hash_algo); -#else - dht->b = DHT_TEST_KEY_LEN; -#endif +static void dht_post_packet(void * comp, + struct ssm_pk_buff * spb) +{ + struct cmd * cmd; - dht->id = create_id(dht->b); - if (dht->id == NULL) - goto fail_id; + (void) comp; - dht->buckets = bucket_create(); - if (dht->buckets == NULL) - goto fail_buckets; + cmd = malloc(sizeof(*cmd)); + if (cmd == NULL) { + log_err("Command malloc failed."); + goto fail_cmd; + } - dht->buckets->depth = 0; - dht->buckets->mask = 0; + cmd->cbuf.data = malloc(ssm_pk_buff_len(spb)); + if (cmd->cbuf.data == NULL) { + log_err("Command buffer malloc failed."); + goto fail_buf; + } - dht->t_expire = 86400; /* 1 day */ - dht->t_repub = dht->t_expire - 10; - dht->k = KAD_K; + cmd->cbuf.len = ssm_pk_buff_len(spb); - if (pthread_create(&dht->worker, NULL, work, dht)) - goto fail_pthread_create; + memcpy(cmd->cbuf.data, ssm_pk_buff_head(spb), cmd->cbuf.len); - dht->state = DHT_RUNNING; + ipcp_spb_release(spb); - dht_update_bucket(dht, dht->id, dht->addr); + pthread_mutex_lock(&dht.cmds.mtx); - pthread_rwlock_unlock(&dht->lock); + list_add(&cmd->next, &dht.cmds.list); - return 0; + pthread_cond_signal(&dht.cmds.cond); - 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); - return -1; + pthread_mutex_unlock(&dht.cmds.mtx); + + return; + + fail_buf: + free(cmd); + fail_cmd: + ipcp_spb_release(spb); + return; } +#endif -static struct ref_entry * ref_entry_get(struct dht * dht, - const uint8_t * key) +int dht_reg(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_publish(key, val)) { + log_err(KV_FMT " Failed to publish.", KV_VAL(key, val)); + goto fail_publish; + } + + freebuf(val); + + return 0; + fail_publish: + freebuf(val); + fail_a2b: + return -1; } -int dht_reg(void * dir, - const uint8_t * key) +int dht_unreg(const uint8_t * key) { - struct dht * dht; - struct ref_entry * e; - uint64_t addr; - time_t t_expire; + buffer_t val; - dht = (struct dht *) dir; + if (addr_to_buf(dht.addr, &val) < 0) { + log_err("Failed to convert address to buffer."); + goto fail_a2b; + } - assert(dht); - assert(key); - assert(dht->addr != 0); + if (dht_kv_unpublish(key, val)) { + log_err(KV_FMT " Failed to unpublish.", KV_VAL(key, val)); + goto fail_unpublish; + } - if (dht_wait_running(dht)) - return -1; + freebuf(val); - pthread_rwlock_wrlock(&dht->lock); + return 0; + fail_unpublish: + freebuf(val); + fail_a2b: + return -ENOMEM; +} - if (ref_entry_get(dht, key) != NULL) { - log_dbg("Name already registered."); - pthread_rwlock_unlock(&dht->lock); - return 0; - } +uint64_t dht_query(const uint8_t * key) +{ + buffer_t * vals; + ssize_t n; + uint64_t addr; - e = ref_entry_create(dht, key); - if (e == NULL) { - pthread_rwlock_unlock(&dht->lock); - return -ENOMEM; + n = dht_kv_retrieve(key, &vals); + if (n < 0) { + log_err(KEY_FMT " Failed to query db.", KEY_VAL(key)); + goto fail_vals; } - list_add(&e->next, &dht->refs); + if (n == 0) { + assert(vals == NULL); - t_expire = dht->t_expire; - addr = dht->addr; + 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_unlock(&dht->lock); + if (buf_to_addr(vals[0], &addr) < 0) { + log_err(VAL_FMT " Failed addr conversion.", VAL_VAL(vals[0])); + goto fail_b2a; + } - kad_publish(dht, key, addr, t_expire); + 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; + } - return 0; + freebufs(vals, n); + + return addr; + fail_b2a: + freebufs(vals, n); + return INVALID_ADDR; + no_vals: + free(vals); + fail_vals: + return INVALID_ADDR; } -int dht_unreg(void * dir, - const uint8_t * key) +static int emergency_peer(struct list_head * pl) { - struct dht * dht; - struct list_head * p; - struct list_head * h; + struct peer_entry * e; + struct timespec now; - dht = (struct dht *) dir; + assert(pl != NULL); + assert(list_is_empty(pl)); - assert(dht); - assert(key); - - if (dht_get_state(dht) != DHT_RUNNING) + if (dht.peer == INVALID_ADDR) return -1; - pthread_rwlock_wrlock(&dht->lock); + clock_gettime(CLOCK_REALTIME_COARSE, &now); - 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); - } + e = malloc(sizeof(*e)); + if (e == NULL) { + log_err("Failed to malloc emergency peer entry."); + goto fail_malloc; + } + + 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; } - dht_del(dht, key, dht->addr); + e->addr = dht.peer; + e->cookie = dht.magic; + e->code = DHT_FIND_NODE_REQ; + e->t_sent = now.tv_sec; - pthread_rwlock_unlock(&dht->lock); + list_add_tail(&e->next, pl); return 0; + fail_id: + free(e); + fail_malloc: + return -ENOMEM; } -uint64_t dht_query(void * dir, - const uint8_t * key) +static bool __dht_kv_bucket_has_addr(struct bucket * b, + uint64_t addr) { - struct dht * dht; - struct dht_entry * e; - struct lookup * lu; - uint64_t addrs[KAD_K]; - size_t n; + struct list_head * p; + size_t i; - dht = (struct dht *) dir; + assert(b != NULL); - assert(dht); + if (*b->children != NULL) + for (i = 0; i < (1L << DHT_BETA); ++i) + if (__dht_kv_bucket_has_addr(b->children[i], addr)) + return true; - addrs[0] = 0; + llist_for_each(p, &b->contacts) { + struct contact * c; + c = list_entry(p, struct contact, next); + if (c->addr == addr) + return true; + } - if (dht_wait_running(dht)) - return 0; + return false; +} + +static bool dht_kv_knows_peer(void) +{ + bool found; + + pthread_rwlock_rdlock(&dht.db.lock); + + found = __dht_kv_bucket_has_addr(dht.db.contacts.root, dht.peer); - pthread_rwlock_rdlock(&dht->lock); + pthread_rwlock_unlock(&dht.db.lock); - e = dht_find_entry(dht, key); - if (e != NULL) - addrs[0] = dht_entry_get_addr(dht, e); + return found; +} - pthread_rwlock_unlock(&dht->lock); +static int dht_kv_seed_bootstrap_peer(void) +{ + struct list_head pl; - if (addrs[0] != 0) - return addrs[0]; + list_head_init(&pl); - lu = kad_lookup(dht, key, KAD_FIND_VALUE); - if (lu == NULL) + if (dht.peer == INVALID_ADDR) { + log_dbg("No-one to contact."); return 0; + } - n = lookup_get_addrs(lu, addrs); - if (n == 0) { - lookup_destroy(lu); + if (dht_kv_knows_peer()) return 0; + + if (emergency_peer(&pl) < 0) { + log_err("Could not create emergency peer."); + goto fail_peer; } - lookup_destroy(lu); + log_dbg("Pinging emergency peer " ADDR_FMT32 ".", + ADDR_VAL32(&dht.peer)); - /* Current behaviour is anycast and return the first peer address. */ - if (addrs[0] != dht->addr) - return addrs[0]; + if (dht_kv_query_contacts(dht.id.data, &pl) < 0) { + log_warn("Failed to bootstrap peer."); + goto fail_query; + } - if (n > 1) - return addrs[1]; + peer_list_destroy(&pl); - return 0; + /* Sent, but not bootstrapped until the peer is in the DHT. */ + return -EAGAIN; + fail_query: + peer_list_destroy(&pl); + fail_peer: + return -EAGAIN; } -static void * dht_handle_packet(void * o) +static void dht_kv_check_contacts(void) { - struct dht * dht = (struct dht *) o; + struct list_head cl; + struct list_head pl; - assert(dht); + list_head_init(&cl); - while (true) { - dht_msg_t * msg; - dht_contact_msg_t ** cmsgs; - dht_msg_t resp_msg = DHT_MSG__INIT; - uint64_t addr; - buffer_t buf; - size_t i; - size_t b; - size_t t_expire; - struct cmd * cmd; + dht_kv_contact_list(dht.id.data, &cl, dht.k); - pthread_mutex_lock(&dht->mtx); + if (!list_is_empty(&cl)) + goto success; - pthread_cleanup_push(__cleanup_mutex_unlock, &dht->mtx); + contact_list_destroy(&cl); - while (list_is_empty(&dht->cmds)) - pthread_cond_wait(&dht->cond, &dht->mtx); + list_head_init(&pl); - cmd = list_last_entry(&dht->cmds, struct cmd, next); - list_del(&cmd->next); + if (dht.peer == INVALID_ADDR) { + log_dbg("No-one to contact."); + return; + } - pthread_cleanup_pop(true); + if (emergency_peer(&pl) < 0) { + log_err("Could not create emergency peer."); + goto fail_peer; + } - i = shm_du_buff_len(cmd->sdb); + log_dbg("No contacts found, using emergency peer " ADDR_FMT32 ".", + ADDR_VAL32(&dht.peer)); - msg = dht_msg__unpack(NULL, i, shm_du_buff_head(cmd->sdb)); -#ifndef __DHT_TEST__ - ipcp_sdb_release(cmd->sdb); -#endif - free(cmd); + dht_kv_query_contacts(dht.id.data, &pl); - if (msg == NULL) { - log_err("Failed to unpack message."); - continue; - } + peer_list_destroy(&pl); - if (msg->code != KAD_RESPONSE && dht_wait_running(dht)) { - dht_msg__free_unpacked(msg, NULL); - log_dbg("Got a request message when not running."); - continue; + 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); + + llist_for_each_safe(p, h, &dht.reqs.ll) { + 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)); + llist_del(&e->next, &dht.reqs.ll); + dht_req_destroy(e); } + } - pthread_rwlock_rdlock(&dht->lock); + pthread_mutex_unlock(&dht.reqs.mtx); +} - b = dht->b; - t_expire = dht->t_expire; +static void value_list_destroy(struct list_head * vl) +{ + struct list_head * p; + struct list_head * h; - pthread_rwlock_unlock(&dht->lock); + assert(vl != NULL); - if (msg->has_key && msg->key.len != b) { - dht_msg__free_unpacked(msg, NULL); - log_warn("Bad key in message."); - continue; + 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); + } +} + +#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)) +/* A local value must be (re)stored if near expiry or never stored. */ +#define MUST_STORE_LVAL(v, now) (MUST_REPUBLISH(v, now) || (v)->t_repl == 0) +static void dht_entry_get_repl_lists(const struct dht_entry * e, + struct list_head * repl, + struct list_head * rebl, + struct timespec * now) +{ + struct list_head * p; + struct val_entry * n; + + llist_for_each(p, &e->vals) { + 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; + + list_add_tail(&n->next, repl); } + } - if (msg->has_s_id && !msg->has_b && msg->s_id.len != b) { - dht_msg__free_unpacked(msg, NULL); - log_warn("Bad source ID in message of type %d.", - msg->code); - continue; + llist_for_each(p, &e->lvals) { + struct val_entry * v = list_entry(p, struct val_entry, next); + if (MUST_REPLICATE(v, now) && MUST_STORE_LVAL(v, now)) { + /* Add expire time here, to allow creating val_entry */ + n = val_entry_create(v->val, now->tv_sec + dht.t_exp); + if (n == NULL) + continue; + + list_add_tail(&n->next, rebl); } + } +} - tpm_dec(dht->tpm); +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; - addr = msg->s_addr; + assert(key != NULL); + assert(repl != NULL); + assert(rebl != NULL); - resp_msg.code = KAD_RESPONSE; - resp_msg.cookie = msg->cookie; + clock_gettime(CLOCK_REALTIME_COARSE, &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; - } + assert(list_is_empty(repl)); + assert(list_is_empty(rebl)); - if (msg->t_replicate != KAD_T_REPL) { - log_warn("Replication time mismatch. " - "DHT enrolment refused."); + pthread_rwlock_rdlock(&dht.db.lock); - break; - } + llist_for_each_safe(p, h, &dht.db.kv.ll) { + e = list_entry(p, struct dht_entry, next); + if (!IS_CLOSER(key, e->key)) + continue; /* Already processed */ - if (msg->t_refresh != KAD_T_REFR) { - log_warn("Refresh time mismatch. " - "DHT enrolment refused."); - break; - } + memcpy(key, e->key, dht.id.len); - 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; + dht_entry_get_repl_lists(e, repl, rebl, &now); + + if (!list_is_empty(repl) || !list_is_empty(rebl)) break; - case KAD_STORE: - if (msg->n_contacts < 1) { - log_warn("No contacts in store message."); - break; - } + } - if (!msg->has_t_expire) { - log_warn("No expiry time in store message."); - break; - } + pthread_rwlock_unlock(&dht.db.lock); - 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; - } + return list_is_empty(repl) && list_is_empty(rebl) ? -ENOENT : 0; +} - 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; - } +static void dht_kv_replicate_value(const uint8_t * key, + struct val_entry * v, + const struct timespec * now) +{ + assert(MUST_REPLICATE(v, now)); - if (dht_update_bucket(dht, msg->s_id.data, addr)) - log_warn("Failed to update bucket."); - pthread_rwlock_unlock(&dht->lock); - } + (void) now; - if (msg->code < KAD_STORE && send_msg(dht, &resp_msg, addr) < 0) - log_warn("Failed to send response."); + if (dht_kv_store_remote(key, v->val, v->t_exp) == 0) { + log_dbg(KV_FMT " Replicated.", KV_VAL(key, v->val)); + return; + } - finish: - dht_msg__free_unpacked(msg, NULL); + log_dbg(KV_FMT " Replication failed.", KV_VAL(key, v->val)); - 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); - continue; - } +static void dht_kv_republish_value(const uint8_t * key, + struct val_entry * v, + const struct timespec * now) +{ + assert(MUST_REPLICATE(v, now)); - for (i = 0; i < resp_msg.n_contacts; ++i) - dht_contact_msg__free_unpacked(resp_msg.contacts[i], - NULL); - free(resp_msg.contacts); + if (MUST_REPUBLISH(v, now)) + assert(v->t_exp >= now->tv_sec + dht.t_exp); - tpm_inc(dht->tpm); + if (dht_kv_store_remote(key, v->val, v->t_exp) == 0) { + log_dbg(KV_FMT " Republished.", KV_VAL(key, v->val)); + return; } - return (void *) 0; + 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)); + + list_del(&v->next); + val_entry_destroy(v); } -static void dht_post_packet(void * comp, - struct shm_du_buff * sdb) +static void dht_kv_update_replication_times(const uint8_t * key, + struct list_head * repl, + struct list_head * rebl, + const struct timespec * now) { - struct cmd * cmd; - struct dht * dht = (struct dht *) comp; + struct dht_entry * e; + struct list_head * p; + struct list_head * h; + struct val_entry * v; - if (dht_get_state(dht) == DHT_SHUTDOWN) { -#ifndef __DHT_TEST__ - ipcp_sdb_release(sdb); -#endif + assert(key != NULL); + assert(repl != NULL); + assert(rebl != NULL); + assert(now != NULL); + + pthread_rwlock_wrlock(&dht.db.lock); + + e = __dht_kv_find_entry(key); + if (e == NULL) { + pthread_rwlock_unlock(&dht.db.lock); return; } - cmd = malloc(sizeof(*cmd)); - if (cmd == NULL) { - log_err("Command failed. Out of memory."); - return; + 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; + } + + x->t_repl = now->tv_sec; + + list_del(&v->next); + val_entry_destroy(v); } - cmd->sdb = sdb; + 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; + } - pthread_mutex_lock(&dht->mtx); + x->t_repl = now->tv_sec; + if (v->t_exp > x->t_exp) { + x->t_exp = v->t_exp; /* update expiration time */ + } - list_add(&cmd->next, &dht->cmds); + list_del(&v->next); + val_entry_destroy(v); + } - pthread_cond_signal(&dht->cond); + pthread_rwlock_unlock(&dht.db.lock); +} - pthread_mutex_unlock(&dht->mtx); +static void __cleanup_value_list(void * o) +{ + return value_list_destroy((struct list_head *) o); } -void dht_destroy(void * dir) +static void dht_kv_replicate_values(const uint8_t * key, + struct list_head * repl, + struct list_head * rebl) { - struct dht * dht; + struct timespec now; struct list_head * p; struct list_head * h; - dht = (struct dht *) dir; - if (dht == NULL) - return; + clock_gettime(CLOCK_REALTIME_COARSE, &now); -#ifndef __DHT_TEST__ - tpm_stop(dht->tpm); + pthread_cleanup_push(__cleanup_value_list, repl); + pthread_cleanup_push(__cleanup_value_list, rebl); - 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); + 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); } - pthread_rwlock_wrlock(&dht->lock); - - list_for_each_safe(p, h, &dht->cmds) { - struct cmd * c = list_entry(p, struct cmd, next); - list_del(&c->next); -#ifndef __DHT_TEST__ - ipcp_sdb_release(c->sdb); -#endif - free(c); + 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); } - 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); - } + pthread_cleanup_pop(false); + pthread_cleanup_pop(false); - 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); - } + /* removes non-replicated items from the list */ + dht_kv_update_replication_times(key, repl, rebl, &now); - 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); + 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; } - list_for_each_safe(p, h, &dht->lookups) { - struct lookup * l = list_entry(p, struct lookup, next); - list_del(&l->next); - lookup_destroy(l); + list_head_init(&repl); + list_head_init(&rebl); + + pthread_cleanup_push(free, key); + + 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); + } + + if (!list_is_empty(&rebl)) { + log_warn(KEY_FMT " Republish items left.", + KEY_VAL(key)); + value_list_destroy(&rebl); + } } - pthread_rwlock_unlock(&dht->lock); + pthread_cleanup_pop(true); +} - if (dht->buckets != NULL) - bucket_destroy(dht->buckets); +static void dht_kv_refresh_contacts(void) +{ + struct list_head * p; + struct list_head * h; + struct list_head rl; /* refresh list */ + struct timespec now; + + list_head_init(&rl); - bmp_destroy(dht->cookies); + clock_gettime(CLOCK_REALTIME_COARSE, &now); - pthread_mutex_destroy(&dht->mtx); + pthread_rwlock_rdlock(&dht.db.lock); - pthread_rwlock_destroy(&dht->lock); + __dht_kv_bucket_refresh_list(dht.db.contacts.root, now.tv_sec, &rl); - free(dht->id); + pthread_rwlock_unlock(&dht.db.lock); - free(dht); + 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); + contact_destroy(c); + } + + assert(list_is_empty(&rl)); } -static void * join_thr(void * o) -{ - struct join_info * info = (struct join_info *) o; - struct lookup * lu; - size_t retr = 0; +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 +}; - assert(info); +static void * work(void * o) +{ + struct timespec now = TIMESPEC_INIT_MS(1); + time_t intv; + size_t n; /* number of tasks */ - while (kad_join(info->dht, info->addr)) { - if (dht_get_state(info->dht) == DHT_SHUTDOWN) { - log_dbg("DHT enrollment aborted."); - goto finish; - } + n = sizeof(tasks) / sizeof(tasks[0]) - 1; /* last is NULL */ - if (retr++ == KAD_JOIN_RETR) { - dht_set_state(info->dht, DHT_INIT); - log_warn("DHT enrollment attempt failed."); - goto finish; - } + (void) o; - sleep(KAD_JOIN_INTV); + 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); } - dht_set_state(info->dht, DHT_RUNNING); + intv = gcd(dht.t_exp, (dht.t_exp - DHT_N_REPUB * dht.t_repl)); + intv = gcd(intv, gcd(dht.t_repl, dht.t_refr)) / 2; + intv = MAX(1, intv / n); - lu = kad_lookup(info->dht, info->dht->id, KAD_FIND_NODE); - if (lu != NULL) - lookup_destroy(lu); + log_dbg("DHT worker starting %ld seconds interval.", intv * n); - finish: - free(info); + /* Flush names registered before we had peers to store them. */ + dht_kv_replicate(); + + while (true) { + int i = 0; + while (tasks[i] != NULL) { + tasks[i++](); + sleep(intv); + } + } return (void *) 0; } -static void handle_event(void * self, - int event, - const void * o) +int dht_start(void) { - struct dht * dht = (struct dht *) self; - - if (event == NOTIFY_DT_CONN_ADD) { - pthread_t thr; - struct join_info * inf; - struct conn * c = (struct conn *) o; - struct timespec slack = TIMESPEC_INIT_MS(DHT_ENROLL_SLACK); + dht.state = DHT_RUNNING; - /* Give the pff some time to update for the new link. */ - nanosleep(&slack, NULL); + if (tpm_start(dht.tpm)) + goto fail_tpm_start; - switch(dht_get_state(dht)) { - case DHT_INIT: - inf = malloc(sizeof(*inf)); - if (inf == NULL) - break; +#ifndef __DHT_TEST__ + if (pthread_create(&dht.worker, NULL, work, NULL)) { + log_err("Failed to create DHT worker thread."); + goto fail_worker; + } - 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.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; } -void * dht_create(void) +void dht_stop(void) { - struct dht * dht; - - dht = malloc(sizeof(*dht)); - if (dht == NULL) - goto fail_malloc; + assert(dht.state == DHT_RUNNING); - dht->buckets = NULL; +#ifndef __DHT_TEST__ + dt_unreg_comp(dht.eid); - 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_cancel(dht.worker); + pthread_join(dht.worker, NULL); +#endif + tpm_stop(dht.tpm); - if (pthread_rwlock_init(&dht->lock, NULL)) - goto fail_rwlock; + dht.state = DHT_INIT; +} - if (pthread_mutex_init(&dht->mtx, NULL)) - goto fail_mutex; +int dht_init(struct dir_dht_config * conf) +{ + struct timespec now; + pthread_condattr_t cattr; - if (pthread_cond_init(&dht->cond, NULL)) - goto fail_cond; + assert(conf != NULL); - dht->cookies = bmp_create(DHT_MAX_REQS, 1); - if (dht->cookies == NULL) - goto fail_bmp; + clock_gettime(CLOCK_REALTIME_COARSE, &now); - 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; + 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_exp = conf->params.t_expire; + dht.t_refr = 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; + } - if (tpm_start(dht->tpm)) - goto fail_tpm_start; + list_head_init(&dht.cmds.list); - dht->eid = dt_reg_comp(dht, &dht_post_packet, DHT); - if ((int) dht->eid < 0) - goto fail_tpm_start; + if (pthread_mutex_init(&dht.cmds.mtx, NULL)) { + log_err("Failed to initialize command mutex."); + goto fail_cmds_mutex; + } - if (notifier_reg(handle_event, dht)) - goto fail_notifier_reg; -#else - (void) handle_event; - (void) dht_handle_packet; - (void) dht_post_packet; + if (pthread_cond_init(&dht.cmds.cond, NULL)) { + log_err("Failed to initialize command condvar."); + goto fail_cmds_cond; + } + + llist_init(&dht.reqs.ll); + + if (pthread_mutex_init(&dht.reqs.mtx, NULL)) { + log_err("Failed to initialize request mutex."); + goto fail_reqs_mutex; + } + + 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 - dht->state = DHT_INIT; + if (pthread_cond_init(&dht.reqs.cond, &cattr)) { + log_err("Failed to initialize request condvar."); + goto fail_reqs_cond; + } + + llist_init(&dht.db.kv.ll); + dht.db.kv.vals = 0; + dht.db.kv.lvals = 0; + + if (pthread_rwlock_init(&dht.db.lock, NULL)) { + log_err("Failed to initialize store rwlock."); + goto fail_rwlock; + } + + dht.db.contacts.root = bucket_create(); + if (dht.db.contacts.root == NULL) { + log_err("Failed to create DHT buckets."); + goto fail_buckets; + } + + if (rib_reg(DHT, &r_ops) < 0) { + log_err("Failed to register DHT RIB operations."); + goto fail_rib_reg; + } + + 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."); - return (void *) dht; + pthread_condattr_destroy(&cattr); #ifndef __DHT_TEST__ - fail_notifier_reg: - tpm_stop(dht->tpm); - fail_tpm_start: - tpm_destroy(dht->tpm); - fail_tpm_create: - bmp_destroy(dht->cookies); + 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_exp=%ld, " + "t_refr=%ld, t_replicate=%ld.", + dht.alpha, dht.k, dht.t_exp, dht.t_refr, dht.t_repl); #endif - fail_bmp: - pthread_cond_destroy(&dht->cond); - fail_cond: - pthread_mutex_destroy(&dht->mtx); - fail_mutex: - pthread_rwlock_destroy(&dht->lock); + 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: - free(dht); - fail_malloc: - return NULL; + 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_fini(void) +{ + struct list_head * p; + struct list_head * h; + + rib_unreg(DHT); + + tpm_destroy(dht.tpm); + + pthread_mutex_lock(&dht.cmds.mtx); + + 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); + } + + pthread_mutex_unlock(&dht.cmds.mtx); + + pthread_cond_destroy(&dht.cmds.cond); + pthread_mutex_destroy(&dht.cmds.mtx); + + pthread_mutex_lock(&dht.reqs.mtx); + + llist_for_each_safe(p, h, &dht.reqs.ll) { + struct dht_req * r = list_entry(p, struct dht_req, next); + llist_del(&r->next, &dht.reqs.ll); + dht_req_destroy(r); + } + + pthread_mutex_unlock(&dht.reqs.mtx); + + pthread_cond_destroy(&dht.reqs.cond); + pthread_mutex_destroy(&dht.reqs.mtx); + + pthread_rwlock_wrlock(&dht.db.lock); + + llist_for_each_safe(p, h, &dht.db.kv.ll) { + struct dht_entry * e = list_entry(p, struct dht_entry, next); + llist_del(&e->next, &dht.db.kv.ll); + dht_entry_destroy(e); + } + + 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(llist_is_empty(&dht.db.kv.ll)); + assert(dht.db.kv.vals == 0); + assert(dht.db.kv.lvals == 0); + assert(llist_is_empty(&dht.reqs.ll)); + + freebuf(dht.id); } diff --git a/src/ipcpd/unicast/dir/dht.h b/src/ipcpd/unicast/dir/dht.h index 311c6b23..ed3bb9f0 100644 --- a/src/ipcpd/unicast/dir/dht.h +++ b/src/ipcpd/unicast/dir/dht.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * 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 index 4c5b06db..02b6b341 100644 --- a/src/ipcpd/unicast/dir/dht.proto +++ b/src/ipcpd/unicast/dir/dht.proto @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * DHT protocol, based on Kademlia * @@ -27,19 +27,32 @@ message dht_contact_msg { 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 uint32 cookie = 2; - required uint64 s_addr = 3; - optional bytes s_id = 4; - optional bytes key = 5; - repeated uint64 addrs = 6; - repeated dht_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; + 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/ops.h b/src/ipcpd/unicast/dir/ops.h index 6ff61ce6..6c336ee0 100644 --- a/src/ipcpd/unicast/dir/ops.h +++ b/src/ipcpd/unicast/dir/ops.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * 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 eae4b2e7..8ccf4f95 100644 --- a/src/ipcpd/unicast/dir/pol.h +++ b/src/ipcpd/unicast/dir/pol.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Directory policies * diff --git a/src/ipcpd/unicast/dir/tests/CMakeLists.txt b/src/ipcpd/unicast/dir/tests/CMakeLists.txt index c850e41d..eded823f 100644 --- a/src/ipcpd/unicast/dir/tests/CMakeLists.txt +++ b/src/ipcpd/unicast/dir/tests/CMakeLists.txt @@ -3,34 +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 ../dht.proto) -add_executable(${PARENT_DIR}_test EXCLUDE_FROM_ALL ${${PARENT_DIR}_tests} +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_link_libraries(${PARENT_DIR}_test ouroboros-common) -add_dependencies(check ${PARENT_DIR}_test) +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) -set(tests_to_run ${${PARENT_DIR}_tests}) -remove(tests_to_run test_suite.c) +target_link_libraries(${PARENT_DIR}_test ouroboros-common) +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/dir/tests/dht_test.c b/src/ipcpd/unicast/dir/tests/dht_test.c index bea2c3e7..ee6861a0 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 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * 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,1958 @@ */ #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 llist msgs; +} 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); + + 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; + + llist_add_tail(&m->next, &sink.msgs); + + 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) +{ + llist_init(&sink.msgs); + sink.enabled = true; +} + +static void sink_clear(void) +{ + struct list_head * p; + struct list_head * h; + + llist_for_each_safe(p, h, &sink.msgs) { + struct message * m = list_entry(p, struct message, next); + llist_del(&m->next, &sink.msgs); + dht_msg__free_unpacked((dht_msg_t *) m->msg, NULL); + free(m); + } + + assert(llist_is_empty(&sink.msgs)); +} + +static void sink_fini(void) +{ + sink_clear(); + sink.enabled = false; +} + +static dht_msg_t * sink_read(void) +{ + struct message * m; + dht_msg_t * msg; + + if (llist_is_empty(&sink.msgs)) + return NULL; + + m = llist_first_entry(&sink.msgs, struct message, next); + + llist_del(&m->next, &sink.msgs); + + 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; + + 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"); + goto fail_init; + } + + 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; + + 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; + } + + e = dht_entry_create(dht.id.data); + if (e == NULL) { + printf("Failed to create dht entry.\n"); + goto fail_entry; + } + + 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; + } + + 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 (v->t_exp != now.tv_sec + 15) { + printf("Expiration time in dht entry value not updated.\n"); + goto fail_get; + } + + 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_next_values(void) +{ + struct list_head repl; + struct list_head rebl; + uint8_t * key; + size_t n; + size_t i; + + TEST_START(); + + list_head_init(&repl); + list_head_init(&rebl); + + if (dht_init(&test_dht_config) < 0) { + printf("Failed to create dht.\n"); + goto fail_init; + } + + if (fill_store_with_random_values(NULL, sizeof(uint64_t), 3) < 0) { + printf("Failed to fill store with random values.\n"); + goto fail_fill; + } + + key = dht_dup_key(dht.id.data); + if (key == NULL) { + printf("Failed to duplicate DHT ID.\n"); + goto fail_fill; + } + + n = 0; + + for (i = 0; i < 5; ++i) { + if (dht_kv_next_values(key, &repl, &rebl) < 0) + break; + + ++n; + value_list_destroy(&repl); + value_list_destroy(&rebl); + } + + if (n != 3) { + printf("Failed to visit each entry once (%zu != 3).\n", n); + goto fail_next; + } + + free(key); + + dht_fini(); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + + fail_next: + free(key); + 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; - dht = dht_create(); - if (dht == NULL) { + 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 all 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"); - return -1; + 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 all 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; } - dht_destroy(dht); + buf = malloc(len); + if (buf == NULL) { + printf("Failed to malloc find_value_rsp buf.\n"); + goto fail_msg; + } - dht = dht_create(); - if (dht == NULL) { - printf("Failed to re-create dht.\n"); - return -1; + if (dht_msg__pack(msg, buf) != len) { + printf("Failed to pack find_value_rsp message.\n"); + goto fail_pack; } - if (dht_bootstrap(dht)) { - printf("Failed to bootstrap dht.\n"); - dht_destroy(dht); - return -1; + upk = dht_msg__unpack(NULL, len, buf); + if (upk == NULL) { + printf("Failed to unpack find_value_rsp message.\n"); + goto fail_unpack; } - dht_destroy(dht); + 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(); - dht = dht_create(); - if (dht == NULL) { - printf("Failed to re-create dht.\n"); - return -1; + 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; + values = malloc(sizeof(*values) * 8); + if (values == NULL) { + printf("Failed to malloc values.\n"); + goto fail_values; } - 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; + 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 (!llist_is_empty(&sink.msgs)) { + 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.msgs.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 (!llist_is_empty(&sink.msgs)) { + 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_next_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 2bb5ed2f..84e62f05 100644 --- a/src/ipcpd/unicast/dt.c +++ b/src/ipcpd/unicast/dt.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Data Transfer Component * @@ -31,19 +31,23 @@ #define DT "dt" #define OUROBOROS_PREFIX DT +#include <ouroboros/atomics.h> #include <ouroboros/bitmap.h> #include <ouroboros/errno.h> #include <ouroboros/logs.h> #include <ouroboros/dev.h> +#include <ouroboros/ipcp-dev.h> #include <ouroboros/notifier.h> #include <ouroboros/rib.h> #ifdef IPCP_FLOW_STATS #include <ouroboros/fccntl.h> #endif +#include "addr-auth.h" #include "common/comp.h" #include "common/connmgr.h" #include "ca.h" +#include "cap.h" #include "ipcp.h" #include "dt.h" #include "pff.h" @@ -59,7 +63,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 +71,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; }; @@ -76,12 +80,14 @@ struct comp_info { #define TTL_LEN 1 #define QOS_LEN 1 #define ECN_LEN 1 +#define CAP_LEN 1 struct dt_pci { uint64_t dst_addr; qoscube_t qc; uint8_t ttl; uint8_t ecn; + uint8_t cap; uint64_t eid; }; @@ -94,6 +100,7 @@ struct { size_t qc_o; size_t ttl_o; size_t ecn_o; + size_t cap_o; size_t eid_o; /* Initial TTL value */ @@ -113,6 +120,7 @@ static void dt_pci_ser(uint8_t * head, memcpy(head + dt_pci_info.qc_o, &dt_pci->qc, QOS_LEN); memcpy(head + dt_pci_info.ttl_o, &ttl, TTL_LEN); memcpy(head + dt_pci_info.ecn_o, &dt_pci->ecn, ECN_LEN); + memcpy(head + dt_pci_info.cap_o, &dt_pci->cap, CAP_LEN); memcpy(head + dt_pci_info.eid_o, &dt_pci->eid, dt_pci_info.eid_size); } @@ -131,22 +139,26 @@ static void dt_pci_des(uint8_t * head, memcpy(&dt_pci->qc, head + dt_pci_info.qc_o, QOS_LEN); memcpy(&dt_pci->ttl, head + dt_pci_info.ttl_o, TTL_LEN); memcpy(&dt_pci->ecn, head + dt_pci_info.ecn_o, ECN_LEN); + memcpy(&dt_pci->cap, head + dt_pci_info.cap_o, CAP_LEN); 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_pop(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 + /* Flow stats use lock-free atomics; stamp is the validity flag. */ struct { time_t stamp; uint64_t addr; @@ -164,31 +176,39 @@ struct { size_t w_drp_bytes[QOS_CUBE_MAX]; size_t f_nhp_pkt[QOS_CUBE_MAX]; size_t f_nhp_bytes[QOS_CUBE_MAX]; - pthread_mutex_t lock; - } stat[PROG_MAX_FLOWS]; + } stat[PROC_MAX_FLOWS]; size_t n_flows; #endif struct bmp * res_fds; - struct comp_info comps[PROG_RES_FDS]; + struct comp_info comps[PROC_RES_FDS]; pthread_rwlock_t lock; pthread_t listener; } dt; +#ifdef IPCP_FLOW_STATS +#define dt_stat_inc(idx, name, qc, len) \ + do { \ + FETCH_ADD_RELAXED(&dt.stat[idx].name ## _pkt[qc], 1); \ + FETCH_ADD_RELAXED(&dt.stat[idx].name ## _bytes[qc], (len)); \ + } while (0) +#define dt_stat_load(idx, field, qc) LOAD_RELAXED(&dt.stat[idx].field[qc]) + static int dt_rib_read(const char * path, char * buf, size_t len) { -#ifdef IPCP_FLOW_STATS int fd; int i; 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; + time_t stamp; + uint64_t addr; struct tm * tm; /* NOTE: we may need stronger checks. */ @@ -202,32 +222,31 @@ static int dt_rib_read(const char * path, buf[0] = '\0'; - pthread_mutex_lock(&dt.stat[fd].lock); - - if (dt.stat[fd].stamp == 0) { - pthread_mutex_unlock(&dt.stat[fd].lock); + stamp = LOAD_ACQUIRE(&dt.stat[fd].stamp); + if (stamp == 0) return 0; - } - if (dt.stat[fd].addr == ipcpi.dt_addr) + addr = LOAD_RELAXED(&dt.stat[fd].addr); + + if (addr == dt.addr) sprintf(addrstr, "%s", dt.comps[fd].name); else - sprintf(addrstr, "%" PRIu64, dt.stat[fd].addr); + sprintf(addrstr, ADDR_FMT32, ADDR_VAL32(&addr)); - tm = localtime(&dt.stat[fd].stamp); - strftime(tmstr, sizeof(tmstr), "%F %T", tm); + tm = gmtime(&stamp); + strftime(tmstr, sizeof(tmstr), RIB_TM_FORMAT, tm); - if (fd >= PROG_RES_FDS) { + if (fd >= PROC_RES_FDS) { fccntl(fd, FLOWGRXQLEN, &rxqlen); fccntl(fd, FLOWGTXQLEN, &txqlen); } 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" @@ -246,38 +265,29 @@ static int dt_rib_read(const char * path, " failed nhop (packets): %20zu\n" " failed nhop (bytes): %20zu\n", i, - dt.stat[fd].snd_pkt[i], - dt.stat[fd].snd_bytes[i], - dt.stat[fd].rcv_pkt[i], - dt.stat[fd].rcv_bytes[i], - dt.stat[fd].lcl_w_pkt[i], - dt.stat[fd].lcl_w_bytes[i], - dt.stat[fd].lcl_r_pkt[i], - dt.stat[fd].lcl_r_bytes[i], - dt.stat[fd].r_drp_pkt[i], - dt.stat[fd].r_drp_bytes[i], - dt.stat[fd].w_drp_pkt[i], - dt.stat[fd].w_drp_bytes[i], - dt.stat[fd].f_nhp_pkt[i], - dt.stat[fd].f_nhp_bytes[i] + dt_stat_load(fd, snd_pkt, i), + dt_stat_load(fd, snd_bytes, i), + dt_stat_load(fd, rcv_pkt, i), + dt_stat_load(fd, rcv_bytes, i), + dt_stat_load(fd, lcl_w_pkt, i), + dt_stat_load(fd, lcl_w_bytes, i), + dt_stat_load(fd, lcl_r_pkt, i), + dt_stat_load(fd, lcl_r_bytes, i), + dt_stat_load(fd, r_drp_pkt, i), + dt_stat_load(fd, r_drp_bytes, i), + dt_stat_load(fd, w_drp_pkt, i), + dt_stat_load(fd, w_drp_bytes, i), + dt_stat_load(fd, f_nhp_pkt, i), + dt_stat_load(fd, f_nhp_bytes, i) ); strcat(buf, str); } - pthread_mutex_unlock(&dt.stat[fd].lock); - return RIB_FILE_STRLEN; -#else - (void) path; - (void) buf; - (void) len; - return 0; -#endif } static int dt_rib_readdir(char *** buf) { -#ifdef IPCP_FLOW_STATS char entry[RIB_PATH_LEN + 1]; size_t i; int idx = 0; @@ -285,81 +295,64 @@ 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; - } - - for (i = 0; i < PROG_MAX_FLOWS; ++i) { - pthread_mutex_lock(&dt.stat[i].lock); + if (*buf == NULL) + goto fail_entries; - 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; - } + for (i = 0; i < PROC_MAX_FLOWS && idx < (int) dt.n_flows; ++i) { + if (LOAD_RELAXED(&dt.stat[i].stamp) == 0) + continue; /* n-1 flows start at PROC_RES_FDS */ 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; -#else - (void) buf; - return 0; -#endif + + fail_entry: + while (idx-- > 0) + free((*buf)[idx]); + + free(*buf); + fail_entries: + pthread_rwlock_unlock(&dt.lock); + return -ENOMEM; } static int dt_rib_getattr(const char * path, struct rib_attr * attr) { -#ifdef IPCP_FLOW_STATS int fd; char * entry; + time_t stamp; entry = strstr(path, RIB_SEPARATOR) + 1; assert(entry); fd = atoi(entry); - pthread_mutex_lock(&dt.stat[fd].lock); + stamp = LOAD_ACQUIRE(&dt.stat[fd].stamp); - if (dt.stat[fd].stamp != -1) { + if (stamp != -1) { attr->size = RIB_FILE_STRLEN; - attr->mtime = dt.stat[fd].stamp; + attr->mtime = stamp; } else { attr->size = 0; attr->mtime = 0; } - pthread_mutex_unlock(&dt.stat[fd].lock); -#else - (void) path; - (void) attr; -#endif return 0; } @@ -369,29 +362,49 @@ static struct rib_ops r_ops = { .getattr = dt_rib_getattr }; -#ifdef IPCP_FLOW_STATS static void stat_used(int fd, uint64_t addr) { struct timespec now; + int i; clock_gettime(CLOCK_REALTIME_COARSE, &now); - pthread_mutex_lock(&dt.stat[fd].lock); - - memset(&dt.stat[fd], 0, sizeof(dt.stat[fd])); + pthread_rwlock_wrlock(&dt.lock); - dt.stat[fd].stamp = (addr != INVALID_ADDR) ? now.tv_sec : 0; - dt.stat[fd].addr = addr; + STORE_RELEASE(&dt.stat[fd].stamp, 0); - pthread_mutex_unlock(&dt.stat[fd].lock); + /* Don't memset: incremented without locks in fast path. */ + for (i = 0; i < QOS_CUBE_MAX; ++i) { + STORE_RELAXED(&dt.stat[fd].snd_pkt[i], 0); + STORE_RELAXED(&dt.stat[fd].snd_bytes[i], 0); + STORE_RELAXED(&dt.stat[fd].rcv_pkt[i], 0); + STORE_RELAXED(&dt.stat[fd].rcv_bytes[i], 0); + STORE_RELAXED(&dt.stat[fd].lcl_r_pkt[i], 0); + STORE_RELAXED(&dt.stat[fd].lcl_r_bytes[i], 0); + STORE_RELAXED(&dt.stat[fd].lcl_w_pkt[i], 0); + STORE_RELAXED(&dt.stat[fd].lcl_w_bytes[i], 0); + STORE_RELAXED(&dt.stat[fd].r_drp_pkt[i], 0); + STORE_RELAXED(&dt.stat[fd].r_drp_bytes[i], 0); + STORE_RELAXED(&dt.stat[fd].w_drp_pkt[i], 0); + STORE_RELAXED(&dt.stat[fd].w_drp_bytes[i], 0); + STORE_RELAXED(&dt.stat[fd].f_nhp_pkt[i], 0); + STORE_RELAXED(&dt.stat[fd].f_nhp_bytes[i], 0); + } - pthread_rwlock_wrlock(&dt.lock); + STORE_RELAXED(&dt.stat[fd].addr, addr); - (addr != INVALID_ADDR) ? ++dt.n_flows : --dt.n_flows; + if (addr != INVALID_ADDR) { + STORE_RELEASE(&dt.stat[fd].stamp, now.tv_sec); + ++dt.n_flows; + } else { + --dt.n_flows; + } pthread_rwlock_unlock(&dt.lock); } +#else +#define dt_stat_inc(idx, name, qc, len) ((void) 0) #endif static void handle_event(void * self, @@ -411,6 +424,8 @@ static void handle_event(void * self, #ifdef IPCP_FLOW_STATS stat_used(fd, c->conn_info.addr); #endif + if (ipcp_flow_cap_arm(fd) < 0) + log_warn("Failed to arm capacity estimator."); psched_add(dt.psched, fd); log_dbg("Added fd %d to packet scheduler.", fd); break; @@ -427,46 +442,38 @@ static void handle_event(void * self, } } -static void packet_handler(int fd, - qoscube_t qc, - struct shm_du_buff * sdb) +static time_t packet_handler(int fd, + qoscube_t qc, + struct ssm_pk_buff * spb) { struct dt_pci dt_pci; int ret; int ofd; uint8_t * head; size_t len; + size_t qlen; + size_t mlen; + uint8_t lcap; + bool marks; - len = shm_du_buff_len(sdb); + len = ssm_pk_buff_len(spb); #ifndef IPCP_FLOW_STATS - (void) fd; -#else - pthread_mutex_lock(&dt.stat[fd].lock); - - ++dt.stat[fd].rcv_pkt[qc]; - dt.stat[fd].rcv_bytes[qc] += len; - - pthread_mutex_unlock(&dt.stat[fd].lock); + (void) fd; #endif + dt_stat_inc(fd, rcv, qc, len); + 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); -#ifdef IPCP_FLOW_STATS - pthread_mutex_lock(&dt.stat[fd].lock); - - ++dt.stat[fd].r_drp_pkt[qc]; - dt.stat[fd].r_drp_bytes[qc] += len; - - pthread_mutex_unlock(&dt.stat[fd].lock); -#endif - return; + ipcp_spb_release(spb); + dt_stat_inc(fd, r_drp, qc, len); + return 0; } /* FIXME: Use qoscube from PCI instead of incoming flow. */ @@ -474,75 +481,58 @@ static void packet_handler(int fd, if (ofd < 0) { log_dbg("No next hop for %" PRIu64 ".", dt_pci.dst_addr); - ipcp_sdb_release(sdb); -#ifdef IPCP_FLOW_STATS - pthread_mutex_lock(&dt.stat[fd].lock); + ipcp_spb_release(spb); + dt_stat_inc(fd, f_nhp, qc, len); + return 0; + } - ++dt.stat[fd].f_nhp_pkt[qc]; - dt.stat[fd].f_nhp_bytes[qc] += len; + marks = ca_marks_ecn(); + qlen = marks ? ipcp_flow_queued(ofd) : 0; + mlen = marks ? ipcp_flow_mean_len(ofd) : 0; + lcap = marks ? cap_enc(ipcp_flow_cap(ofd)) : 0; - pthread_mutex_unlock(&dt.stat[fd].lock); -#endif - return; - } + (void) ca_calc_ecn(qlen, head + dt_pci_info.ecn_o, qc, mlen); - (void) ca_calc_ecn(ofd, head + dt_pci_info.ecn_o, qc, len); + if (marks) + cap_stamp(head + dt_pci_info.cap_o, lcap); - 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); -#ifdef IPCP_FLOW_STATS - pthread_mutex_lock(&dt.stat[ofd].lock); - - ++dt.stat[ofd].w_drp_pkt[qc]; - dt.stat[ofd].w_drp_bytes[qc] += len; - - pthread_mutex_unlock(&dt.stat[ofd].lock); -#endif - return; + ipcp_spb_release(spb); + dt_stat_inc(ofd, w_drp, qc, len); + return 0; } -#ifdef IPCP_FLOW_STATS - pthread_mutex_lock(&dt.stat[ofd].lock); - ++dt.stat[ofd].snd_pkt[qc]; - dt.stat[ofd].snd_bytes[qc] += len; + dt_stat_inc(ofd, snd, qc, len); - pthread_mutex_unlock(&dt.stat[ofd].lock); -#endif + if (marks) + ipcp_flow_cap_update(ofd, qlen, len); } else { - dt_pci_shrink(sdb); - if (dt_pci.eid >= PROG_RES_FDS) { + dt_pci_shrink(spb); + if (dt_pci.eid >= PROC_RES_FDS) { uint8_t ecn = *(head + dt_pci_info.ecn_o); - fa_np1_rcv(dt_pci.eid, ecn, sdb); - return; + uint8_t cap = *(head + dt_pci_info.cap_o); + fa_np1_rcv(dt_pci.eid, ecn, cap, spb); + return 0; } if (dt.comps[dt_pci.eid].post_packet == NULL) { log_err("No registered component on eid %" PRIu64 ".", dt_pci.eid); - ipcp_sdb_release(sdb); - return; + ipcp_spb_release(spb); + return 0; } -#ifdef IPCP_FLOW_STATS - pthread_mutex_lock(&dt.stat[fd].lock); - - ++dt.stat[fd].lcl_r_pkt[qc]; - dt.stat[fd].lcl_r_bytes[qc] += len; + dt_stat_inc(fd, lcl_r, qc, len); + dt_stat_inc(dt_pci.eid, snd, qc, len); - pthread_mutex_unlock(&dt.stat[fd].lock); - pthread_mutex_lock(&dt.stat[dt_pci.eid].lock); - - ++dt.stat[dt_pci.eid].snd_pkt[qc]; - dt.stat[dt_pci.eid].snd_bytes[qc] += len; - - 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); } + + return 0; } static void * dt_conn_handle(void * o) @@ -569,17 +559,25 @@ int dt_init(struct dt_config cfg) { int i; int j; +#ifdef IPCP_FLOW_STATS char dtstr[RIB_NAME_STRLEN + 1]; - int pp; +#endif + 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 (cfg.eid_size != 8) { /* only support 64 bits from now */ log_warn("Invalid EID size. Only 64 bit is supported."); @@ -593,16 +591,16 @@ int dt_init(struct dt_config cfg) dt_pci_info.qc_o = dt_pci_info.addr_size; dt_pci_info.ttl_o = dt_pci_info.qc_o + QOS_LEN; dt_pci_info.ecn_o = dt_pci_info.ttl_o + TTL_LEN; - dt_pci_info.eid_o = dt_pci_info.ecn_o + ECN_LEN; + dt_pci_info.cap_o = dt_pci_info.ecn_o + ECN_LEN; + dt_pci_info.eid_o = dt_pci_info.cap_o + CAP_LEN; dt_pci_info.head_size = dt_pci_info.eid_o + dt_pci_info.eid_size; - if (connmgr_comp_init(COMPID_DT, &info)) { + if (connmgr_comp_init(COMPID_DT, &info) != 0) { log_err("Failed to register with connmgr."); goto fail_connmgr_comp_init; } - pp = routing_init(cfg.routing_type); - if (pp < 0) { + if (routing_init(&cfg.routing, &pp) < 0) { log_err("Failed to init routing."); goto fail_routing; } @@ -631,37 +629,27 @@ int dt_init(struct dt_config cfg) goto fail_rwlock_init; } - dt.res_fds = bmp_create(PROG_RES_FDS, 0); + dt.res_fds = bmp_create(PROC_RES_FDS, 0); if (dt.res_fds == NULL) goto fail_res_fds; #ifdef IPCP_FLOW_STATS memset(dt.stat, 0, sizeof(dt.stat)); - 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; - } - dt.n_flows = 0; -#endif - sprintf(dtstr, "%s.%" PRIu64, DT, ipcpi.dt_addr); + + 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; } +#endif return 0; - fail_rib_reg: #ifdef IPCP_FLOW_STATS - for (i = 0; i < PROG_MAX_FLOWS; ++i) - pthread_mutex_destroy(&dt.stat[i].lock); - fail_stat_lock: -#endif + fail_rib_reg: bmp_destroy(dt.res_fds); +#endif fail_res_fds: pthread_rwlock_destroy(&dt.lock); fail_rwlock_init: @@ -680,14 +668,14 @@ int dt_init(struct dt_config cfg) void dt_fini(void) { +#ifdef IPCP_FLOW_STATS char dtstr[RIB_NAME_STRLEN + 1]; +#endif int i; - sprintf(dtstr, "%s.%" PRIu64, DT, ipcpi.dt_addr); - rib_unreg(dtstr); #ifdef IPCP_FLOW_STATS - for (i = 0; i < PROG_MAX_FLOWS; ++i) - pthread_mutex_destroy(&dt.stat[i].lock); + sprintf(dtstr, "%s.%" PRIu64, DT, dt.addr); + rib_unreg(dtstr); #endif bmp_destroy(dt.res_fds); @@ -719,21 +707,31 @@ int dt_start(void) 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); @@ -743,12 +741,12 @@ void dt_stop(void) } 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); @@ -769,103 +767,116 @@ 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 < PROC_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, + uint8_t * ecn) { struct dt_pci dt_pci; int fd; int ret; uint8_t * head; size_t len; + size_t qlen; + size_t mlen; + uint8_t lcap; + bool marks; - assert(sdb); - assert(dst_addr != ipcpi.dt_addr); - - len = shm_du_buff_len(sdb); + assert(spb); + assert(dst_addr != dt.addr); #ifdef IPCP_FLOW_STATS - if (eid < PROG_RES_FDS) { - pthread_mutex_lock(&dt.stat[eid].lock); - - ++dt.stat[eid].lcl_r_pkt[qc]; - dt.stat[eid].lcl_r_bytes[qc] += len; + len = ssm_pk_buff_len(spb); - pthread_mutex_unlock(&dt.stat[eid].lock); - } + if (eid < PROC_RES_FDS) + dt_stat_inc(eid, lcl_r, qc, len); #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); - - ++dt.stat[eid].lcl_r_pkt[qc]; - dt.stat[eid].lcl_r_bytes[qc] += len; - - pthread_mutex_unlock(&dt.stat[eid].lock); - } + if (eid < PROC_RES_FDS) + dt_stat_inc(eid, lcl_r, qc, len); #endif return -EPERM; } - head = shm_du_buff_head_alloc(sdb, dt_pci_info.head_size); + head = ssm_pk_buff_push(spb, dt_pci_info.head_size); if (head == NULL) { log_dbg("Failed to allocate DT header."); goto fail_write; } - len = shm_du_buff_len(sdb); + len = ssm_pk_buff_len(spb); dt_pci.dst_addr = dst_addr; dt_pci.qc = qc; dt_pci.eid = eid; dt_pci.ecn = 0; + dt_pci.cap = 0; + + marks = ca_marks_ecn(); + qlen = marks ? ipcp_flow_queued(fd) : 0; + mlen = marks ? ipcp_flow_mean_len(fd) : 0; + lcap = marks ? cap_enc(ipcp_flow_cap(fd)) : 0; - (void) ca_calc_ecn(fd, &dt_pci.ecn, qc, len); + (void) ca_calc_ecn(qlen, &dt_pci.ecn, qc, mlen); + + dt_pci.cap = lcap; + + if (ecn != NULL) + *ecn = dt_pci.ecn; 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); + log_dbg("Failed to write packet to fd %d: %d.", fd, ret); if (ret == -EFLOWDOWN) notifier_event(NOTIFY_DT_FLOW_DOWN, &fd); goto fail_write; } #ifdef IPCP_FLOW_STATS - pthread_mutex_lock(&dt.stat[fd].lock); - - if (dt_pci.eid < PROG_RES_FDS) { - ++dt.stat[fd].lcl_w_pkt[qc]; - dt.stat[fd].lcl_w_bytes[qc] += len; - } - ++dt.stat[fd].snd_pkt[qc]; - dt.stat[fd].snd_bytes[qc] += len; + if (dt_pci.eid < PROC_RES_FDS) + dt_stat_inc(fd, lcl_w, qc, len); - pthread_mutex_unlock(&dt.stat[fd].lock); + dt_stat_inc(fd, snd, qc, len); #endif + if (marks) + ipcp_flow_cap_update(fd, qlen, len); + return 0; fail_write: #ifdef IPCP_FLOW_STATS - pthread_mutex_lock(&dt.stat[fd].lock); - - if (eid < PROG_RES_FDS) { - ++dt.stat[fd].lcl_w_pkt[qc]; - dt.stat[fd].lcl_w_bytes[qc] += len; - } - ++dt.stat[fd].w_drp_pkt[qc]; - dt.stat[fd].w_drp_bytes[qc] += len; + if (eid < PROC_RES_FDS) + dt_stat_inc(fd, lcl_w, qc, len); - pthread_mutex_unlock(&dt.stat[fd].lock); + dt_stat_inc(fd, w_drp, qc, len); #endif return -1; } diff --git a/src/ipcpd/unicast/dt.h b/src/ipcpd/unicast/dt.h index 7198a013..a055efea 100644 --- a/src/ipcpd/unicast/dt.h +++ b/src/ipcpd/unicast/dt.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Data Transfer component * @@ -25,7 +25,7 @@ #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" @@ -39,13 +39,16 @@ 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, + uint8_t * ecn); #endif /* OUROBOROS_IPCPD_UNICAST_DT_H */ diff --git a/src/ipcpd/unicast/fa.c b/src/ipcpd/unicast/fa.c index 3631fd7b..1c939fab 100644 --- a/src/ipcpd/unicast/fa.c +++ b/src/ipcpd/unicast/fa.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Flow allocator of the IPC Process * @@ -31,22 +31,28 @@ #define FA "flow-allocator" #define OUROBOROS_PREFIX FA +#include <ouroboros/atomics.h> +#include <ouroboros/dev.h> #include <ouroboros/endian.h> -#include <ouroboros/logs.h> -#include <ouroboros/fqueue.h> #include <ouroboros/errno.h> -#include <ouroboros/dev.h> +#include <ouroboros/fqueue.h> #include <ouroboros/ipcp-dev.h> -#include <ouroboros/rib.h> -#include <ouroboros/random.h> +#include <ouroboros/logs.h> +#include <ouroboros/np1_flow.h> #include <ouroboros/pthread.h> +#include <ouroboros/qoscube.h> +#include <ouroboros/random.h> +#include <ouroboros/rib.h> +#include <ouroboros/time.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> @@ -56,12 +62,17 @@ #define CLOCK_REALTIME_COARSE CLOCK_REALTIME #endif -#define TIMEOUT 10 * MILLION /* nanoseconds */ +#define TIMEOUT 10 * MILLION /* nanoseconds */ +#define MSGBUFSZ 32768 + +#define FLOW_REQ 0 +#define FLOW_REPLY 1 +#define FLOW_UPDATE 2 +#define FLOW_IRM_UPDATE 3 +#define FLOW_HB 4 +#define FLOW_ACK 5 -#define FLOW_REQ 0 -#define FLOW_REPLY 1 -#define FLOW_UPDATE 2 -#define MSGBUFSZ 2048 +#define HB_ID_LEN 16 /* 128-bit unguessable heartbeat nonce */ #define STAT_FILE_LEN 0 @@ -69,24 +80,40 @@ 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 */ - uint32_t delay; uint64_t bandwidth; + int32_t response; + uint32_t delay; uint32_t loss; uint32_t ber; uint32_t max_gap; uint32_t timeout; - uint16_t cypher_s; + uint16_t ece; + uint8_t cap; + uint8_t code; uint8_t availability; - uint8_t in_order; + uint8_t service; + uint8_t hb_id[HB_ID_LEN]; /* heartbeat / ack nonce */ } __attribute__((packed)); struct cmd { struct list_head next; - struct shm_du_buff * sdb; + struct ssm_pk_buff * spb; +}; + +#define HB_TBL_TTL (4ULL * BILLION) /* drop unanswered heartbeats */ +#define HB_TBL_MAX 1024 /* cap outstanding heartbeats */ +#define HB_BUCKETS 256 /* nonce hash buckets (pow2) */ + +/* RIB flow entry: the CA stats string plus the flow header. */ +#define FA_RIB_STRLEN (CA_STATS_STRLEN + 512) + +/* Outstanding heartbeat: send time kept locally, keyed by the nonce. */ +struct hb_ent { + uint8_t id[HB_ID_LEN]; + uint64_t s_eid; /* originating flow (fd-reuse guard) */ + uint64_t t_snd; /* send timestamp (ns) */ + struct list_head hnext; /* nonce hash bucket chain */ + struct list_head qnext; /* expiry FIFO, oldest at head */ }; struct fa_flow { @@ -103,15 +130,17 @@ struct fa_flow { size_t u_snd; /* Flow updates sent */ size_t u_rcv; /* Flow updates received */ #endif - uint64_t s_eid; /* Local endpoint id */ - uint64_t r_eid; /* Remote endpoint id */ + uint64_t s_eid; /* Local PoA id */ + uint64_t r_eid; /* Remote PoA id */ uint64_t r_addr; /* Remote address */ void * ctx; /* Congestion avoidance context */ + uint64_t fair; /* SFQ virtual finish tag (bytes) */ + uint8_t l_ecn; /* Local first-hop mark (relaxed) */ }; struct { pthread_rwlock_t flows_lock; - struct fa_flow flows[PROG_MAX_FLOWS]; + struct fa_flow flows[PROC_MAX_FLOWS]; #ifdef IPCP_FLOW_STATS size_t n_flows; #endif @@ -122,21 +151,26 @@ struct { pthread_mutex_t mtx; pthread_t worker; + struct list_head hb_bkt[HB_BUCKETS]; /* nonce hash buckets */ + struct list_head hb_q; /* expiry FIFO, oldest at head */ + size_t n_hbs; + pthread_mutex_t hb_mtx; + struct psched * psched; } fa; +#ifdef IPCP_FLOW_STATS static int fa_rib_read(const char * path, char * buf, size_t len) { -#ifdef IPCP_FLOW_STATS struct fa_flow * flow; int fd; char r_addrstr[21]; char s_eidstr[21]; char r_eidstr[21]; - char tmstr[20]; - char castr[1024]; + char tmstr[RIB_TM_STRLEN]; + char castr[CA_STATS_STRLEN]; char * entry; struct tm * tm; @@ -145,10 +179,10 @@ static int fa_rib_read(const char * path, fd = atoi(entry); - if (fd < 0 || fd >= PROG_MAX_FLOWS) + if (fd < 0 || fd >= PROC_MAX_FLOWS) return -1; - if (len < 1536) + if (len < FA_RIB_STRLEN) return 0; flow = &fa.flows[fd]; @@ -166,16 +200,16 @@ 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); + ca_print_stats(flow->ctx, castr, CA_STATS_STRLEN); sprintf(buf, "Flow established at: %20s\n" "Remote address: %20s\n" - "Local endpoint ID: %20s\n" - "Remote endpoint ID: %20s\n" + "Local PoA ID: %20s\n" + "Remote PoA ID: %20s\n" "Sent (packets): %20zu\n" "Sent (bytes): %20zu\n" "Send failed (packets): %20zu\n" @@ -199,17 +233,10 @@ static int fa_rib_read(const char * path, pthread_rwlock_unlock(&fa.flows_lock); return strlen(buf); -#else - (void) path; - (void) buf; - (void) len; - return 0; -#endif } static int fa_rib_readdir(char *** buf) { -#ifdef IPCP_FLOW_STATS char entry[RIB_PATH_LEN + 1]; size_t i; int idx = 0; @@ -217,17 +244,15 @@ 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) { + for (i = 0; i < PROC_MAX_FLOWS; ++i) { struct fa_flow * flow; flow = &fa.flows[i]; @@ -237,32 +262,31 @@ 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; -#else - (void) buf; - return 0; -#endif + + fail_entry: + while (idx-- > 0) + free((*buf)[idx]); + + free(*buf); + fail_entries: + pthread_rwlock_unlock(&fa.flows_lock); + return -ENOMEM; } static int fa_rib_getattr(const char * path, struct rib_attr * attr) { -#ifdef IPCP_FLOW_STATS int fd; char * entry; struct fa_flow * flow; @@ -277,7 +301,7 @@ static int fa_rib_getattr(const char * path, pthread_rwlock_rdlock(&fa.flows_lock); if (flow->stamp != 0) { - attr->size = 1536; + attr->size = FA_RIB_STRLEN; attr->mtime = flow->stamp; } else { attr->size = 0; @@ -285,10 +309,7 @@ static int fa_rib_getattr(const char * path, } pthread_rwlock_unlock(&fa.flows_lock); -#else - (void) path; - (void) attr; -#endif + return 0; } @@ -297,6 +318,7 @@ static struct rib_ops r_ops = { .readdir = fa_rib_readdir, .getattr = fa_rib_getattr }; +#endif /* IPCP_FLOW_STATS */ static int eid_to_fd(uint64_t eid) { @@ -305,7 +327,7 @@ static int eid_to_fd(uint64_t eid) fd = eid & 0xFFFFFFFF; - if (fd < 0 || fd >= PROG_MAX_FLOWS) + if (fd < 0 || fd >= PROC_MAX_FLOWS) return -1; flow = &fa.flows[fd]; @@ -328,37 +350,166 @@ static uint64_t gen_eid(int fd) return ((uint64_t) rnd << 32) + fd; } -static void packet_handler(int fd, - qoscube_t qc, - struct shm_du_buff * sdb) +/* The nonce is uniformly random, so its low word is a fine hash. */ +static size_t fa_hb_hash(const uint8_t * id) +{ + uint32_t h; + + memcpy(&h, id, sizeof(h)); + + return h & (HB_BUCKETS - 1); +} + +/* Record an outstanding heartbeat; expire stale entries as we go. */ +static void fa_hb_record(const uint8_t * id, + uint64_t s_eid, + uint64_t t_snd) +{ + struct hb_ent * ent; + struct list_head * p; + struct list_head * h; + + ent = malloc(sizeof(*ent)); + if (ent == NULL) + return; + + memcpy(ent->id, id, HB_ID_LEN); + ent->s_eid = s_eid; + ent->t_snd = t_snd; + + pthread_mutex_lock(&fa.hb_mtx); + + /* The FIFO is time-ordered; stop at the first fresh entry. */ + list_for_each_safe(p, h, &fa.hb_q) { + struct hb_ent * e = list_entry(p, struct hb_ent, qnext); + if (t_snd - e->t_snd <= HB_TBL_TTL) + break; + list_del(&e->hnext); + list_del(&e->qnext); + free(e); + fa.n_hbs--; + } + + if (fa.n_hbs >= HB_TBL_MAX) { + pthread_mutex_unlock(&fa.hb_mtx); + free(ent); + return; + } + + list_add(&ent->hnext, &fa.hb_bkt[fa_hb_hash(id)]); + list_add_tail(&ent->qnext, &fa.hb_q); + fa.n_hbs++; + + pthread_mutex_unlock(&fa.hb_mtx); +} + +/* Consume a heartbeat nonce, returning the flow and send time it maps to. */ +static int fa_hb_match(const uint8_t * id, + uint64_t * s_eid, + uint64_t * t_snd) +{ + struct list_head * bkt; + struct list_head * p; + struct list_head * h; + + pthread_mutex_lock(&fa.hb_mtx); + + bkt = &fa.hb_bkt[fa_hb_hash(id)]; + list_for_each_safe(p, h, bkt) { + struct hb_ent * e = list_entry(p, struct hb_ent, hnext); + if (memcmp(e->id, id, HB_ID_LEN) == 0) { + *s_eid = e->s_eid; + *t_snd = e->t_snd; + list_del(&e->hnext); + list_del(&e->qnext); + free(e); + fa.n_hbs--; + pthread_mutex_unlock(&fa.hb_mtx); + return 0; + } + } + + pthread_mutex_unlock(&fa.hb_mtx); + + return -1; +} + +/* Send a bare control message (heartbeat or ack) carrying only a nonce. */ +static int fa_send_ctrl(uint64_t r_addr, + uint8_t code, + const uint8_t * id) +{ + struct fa_msg * msg; + struct ssm_pk_buff * spb; + qoscube_t qc = QOS_CUBE_BE; + + if (ipcp_spb_reserve(&spb, sizeof(*msg))) + return -1; + + msg = (struct fa_msg *) ssm_pk_buff_head(spb); + memset(msg, 0, sizeof(*msg)); + + msg->code = code; + msg->s_addr = hton64(addr_auth_address()); + memcpy(msg->hb_id, id, HB_ID_LEN); + + if (dt_write_packet(r_addr, qc, fa.eid, spb, NULL)) { + ipcp_spb_release(spb); + return -1; + } + + return 0; +} + +static time_t packet_handler(int fd, + qoscube_t qc, + struct ssm_pk_buff * spb) { struct fa_flow * flow; + struct timespec tv; + uint64_t now; uint64_t r_addr; uint64_t r_eid; - ca_wnd_t wnd; + uint64_t s_eid; + bool hb; + uint8_t nonce[HB_ID_LEN]; + time_t wait; size_t len; + uint8_t ecn; flow = &fa.flows[fd]; + ecn = 0; + + clock_gettime(PTHREAD_COND_CLOCK, &tv); + now = TS_TO_UINT64(tv); + pthread_rwlock_wrlock(&fa.flows_lock); - len = shm_du_buff_len(sdb); + len = ssm_pk_buff_len(spb); #ifdef IPCP_FLOW_STATS ++flow->p_snd; flow->b_snd += len; #endif - wnd = ca_ctx_update_snd(flow->ctx, len); + wait = ca_ctx_update_snd(flow->ctx, len, + LOAD_RELAXED(&flow->l_ecn), &flow->fair); + hb = ca_ctx_hb_due(flow->ctx, now); r_addr = flow->r_addr; r_eid = flow->r_eid; + s_eid = flow->s_eid; pthread_rwlock_unlock(&fa.flows_lock); - ca_wnd_wait(wnd); + if (hb && random_buffer(nonce, HB_ID_LEN) == 0) { + fa_hb_record(nonce, s_eid, now); + fa_send_ctrl(r_addr, FLOW_HB, nonce); + } - if (dt_write_packet(r_addr, qc, r_eid, sdb)) { - ipcp_sdb_release(sdb); + if (dt_write_packet(r_addr, qc, r_eid, spb, &ecn)) { + STORE_RELAXED(&flow->l_ecn, ecn); + ipcp_spb_release(spb); log_dbg("Failed to forward packet."); #ifdef IPCP_FLOW_STATS pthread_rwlock_wrlock(&fa.flows_lock); @@ -366,8 +517,12 @@ static void packet_handler(int fd, flow->b_snd_f += len; pthread_rwlock_unlock(&fa.flows_lock); #endif - return; + return wait; } + + STORE_RELAXED(&flow->l_ecn, ecn); + + return wait; } static int fa_flow_init(struct fa_flow * flow) @@ -381,9 +536,7 @@ static int fa_flow_init(struct fa_flow * flow) flow->s_eid = -1; flow->r_addr = INVALID_ADDR; - flow->ctx = ca_ctx_create(); - if (flow->ctx == NULL) - return -1; + /* ctx is acquired once (r_addr, qc) are known (ca_ctx_get). */ #ifdef IPCP_FLOW_STATS clock_gettime(CLOCK_REALTIME_COARSE, &now); @@ -397,7 +550,8 @@ static int fa_flow_init(struct fa_flow * flow) static void fa_flow_fini(struct fa_flow * flow) { - ca_ctx_destroy(flow->ctx); + if (flow->ctx != NULL) + ca_ctx_put(flow->ctx); memset(flow, 0, sizeof(*flow)); @@ -411,7 +565,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; @@ -422,11 +576,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); @@ -454,16 +608,16 @@ static size_t fa_wait_for_fa_msg(struct fa_msg * msg) pthread_cleanup_pop(true); - len = shm_du_buff_len(cmd->sdb); + 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 */ } - memcpy(msg, shm_du_buff_head(cmd->sdb), len); + memcpy(msg, ssm_pk_buff_head(cmd->spb), len); - ipcp_sdb_release(cmd->sdb); + ipcp_spb_release(cmd->spb); free(cmd); @@ -495,12 +649,15 @@ static int fa_handle_flow_req(struct fa_msg * msg, qs.availability = msg->availability; qs.loss = ntoh32(msg->loss); qs.ber = ntoh32(msg->ber); - qs.in_order = msg->in_order; + qs.service = msg->service; qs.max_gap = ntoh32(msg->max_gap); - qs.cypher_s = ntoh16(msg->cypher_s); qs.timeout = ntoh32(msg->timeout); - fd = ipcp_wait_flow_req_arr(dst, qs, IPCP_UNICAST_MPL, &data); + /* Ack the seed nonce now: a clean RTT, before any accept delay. */ + fa_send_ctrl(ntoh64(msg->s_addr), FLOW_ACK, msg->hb_id); + + fd = ipcp_wait_flow_req_arr(dst, qs, IPCP_UNICAST_MPL, + IPCP_UNICAST_MTU, &data); if (fd < 0) return fd; @@ -514,6 +671,13 @@ static int fa_handle_flow_req(struct fa_msg * msg, flow->r_eid = ntoh64(msg->s_eid); flow->r_addr = ntoh64(msg->s_addr); + flow->ctx = ca_ctx_get(flow->r_addr, qos_spec_to_cube(qs)); + if (flow->ctx == NULL) { + fa_flow_fini(flow); + pthread_rwlock_unlock(&fa.flows_lock); + return -ENOMEM; + } + pthread_rwlock_unlock(&fa.flows_lock); return fd; @@ -526,8 +690,10 @@ static int fa_handle_flow_reply(struct fa_msg * msg, struct fa_flow * flow; buffer_t data; /* Piggbacked data on flow alloc request. */ time_t mpl = IPCP_UNICAST_MPL; + int response; - assert(len >= sizeof(*msg)); + if (len < sizeof(*msg)) + return -EINVAL; data.data = (uint8_t *) msg + sizeof(*msg); data.len = len - sizeof(*msg); @@ -545,15 +711,20 @@ static int fa_handle_flow_reply(struct fa_msg * msg, flow = &fa.flows[fd]; flow->r_eid = ntoh64(msg->s_eid); + response = ntoh32(msg->response); + + log_dbg("IPCP received msg response %d for flow on fd %d.", + response, fd); - if (msg->response < 0) + if (response < 0) fa_flow_fini(flow); else psched_add(fa.psched, fd); pthread_rwlock_unlock(&fa.flows_lock); - if (ipcp_flow_alloc_reply(fd, msg->response, mpl, &data) < 0) { + if (ipcp_flow_alloc_reply(fd, response, mpl, + IPCP_UNICAST_MTU, &data) < 0) { log_err("Failed to reply for flow allocation on fd %d.", fd); return -EIRMD; } @@ -567,8 +738,8 @@ static int fa_handle_flow_update(struct fa_msg * msg, struct fa_flow * flow; int fd; - (void) len; - assert(len >= sizeof(*msg)); + if (len < sizeof(*msg)) + return -EINVAL; pthread_rwlock_wrlock(&fa.flows_lock); @@ -584,13 +755,93 @@ static int fa_handle_flow_update(struct fa_msg * msg, #ifdef IPCP_FLOW_STATS flow->u_rcv++; #endif - ca_ctx_update_ece(flow->ctx, ntoh16(msg->ece)); + ca_ctx_update_ece(flow->ctx, ntoh16(msg->ece), msg->cap); pthread_rwlock_unlock(&fa.flows_lock); return 0; } +/* Heartbeat: reflect the nonce straight back to the sender's address. */ +static int fa_handle_flow_hb(struct fa_msg * msg, + size_t len) +{ + if (len < sizeof(*msg)) + return -EINVAL; + + return fa_send_ctrl(ntoh64(msg->s_addr), FLOW_ACK, msg->hb_id); +} + +/* Ack: the reflected nonce yields an RTT sample for its path. */ +static int fa_handle_flow_ack(struct fa_msg * msg, + size_t len) +{ + struct timespec tv; + struct fa_flow * flow; + uint64_t now; + uint64_t t_snd; + uint64_t s_eid; + int fd; + + if (len < sizeof(*msg)) + return -EINVAL; + + if (fa_hb_match(msg->hb_id, &s_eid, &t_snd) < 0) + return 0; /* unknown or stale nonce */ + + clock_gettime(PTHREAD_COND_CLOCK, &tv); + now = TS_TO_UINT64(tv); + + pthread_rwlock_wrlock(&fa.flows_lock); + + fd = eid_to_fd(s_eid); + if (fd >= 0 && now > t_snd) { + flow = &fa.flows[fd]; + ca_ctx_rtt(flow->ctx, now, now - t_snd); + } + + pthread_rwlock_unlock(&fa.flows_lock); + + return 0; +} + +static int fa_handle_flow_irm_update(struct fa_msg * msg, + size_t len) +{ + buffer_t data; + int fd; + int flow_id; + + if (len < sizeof(*msg)) + return -EINVAL; + + data.data = (uint8_t *) msg + sizeof(*msg); + data.len = len - sizeof(*msg); + + pthread_rwlock_rdlock(&fa.flows_lock); + + fd = eid_to_fd(ntoh64(msg->r_eid)); + + pthread_rwlock_unlock(&fa.flows_lock); + + if (fd < 0) { + log_err("Flow update for unknown EID %" PRIu64 ".", + ntoh64(msg->r_eid)); + return -ENOTALLOC; + } + + flow_id = np1_flow_id(fd); + if (flow_id < 0) + return -ENOTALLOC; + + if (ipcp_flow_update_arr(flow_id, &data) < 0) { + log_err("Failed to relay flow update on fd %d.", fd); + return -EIRMD; + } + + return 0; +} + static void * fa_handle_packet(void * o) { (void) o; @@ -619,6 +870,18 @@ static void * fa_handle_packet(void * o) if (fa_handle_flow_update(msg, len) < 0) log_err("Error handling flow update."); break; + case FLOW_IRM_UPDATE: + if (fa_handle_flow_irm_update(msg, len) < 0) + log_err("Error handling flow update."); + break; + case FLOW_HB: + if (fa_handle_flow_hb(msg, len) < 0) + log_err("Error handling heartbeat."); + break; + case FLOW_ACK: + if (fa_handle_flow_ack(msg, len) < 0) + log_err("Error handling heartbeat ack."); + break; default: log_warn("Recieved unknown flow allocation message."); break; @@ -628,67 +891,104 @@ static void * fa_handle_packet(void * o) return (void *) 0; } -int fa_init(void) +int fa_init(uint16_t max_rtt) { pthread_condattr_t cattr; + size_t i; + + ipcp_flow_set_max_rtt(max_rtt); - if (pthread_rwlock_init(&fa.flows_lock, NULL)) + if (pthread_rwlock_init(&fa.flows_lock, NULL) != 0) goto fail_rwlock; - if (pthread_mutex_init(&fa.mtx, NULL)) + if (pthread_mutex_init(&fa.mtx, NULL) != 0) goto fail_mtx; - if (pthread_condattr_init(&cattr)) + if (pthread_mutex_init(&fa.hb_mtx, NULL) != 0) + goto fail_hb_mtx; + + if (pthread_condattr_init(&cattr) != 0) goto fail_cattr; #ifndef __APPLE__ pthread_condattr_setclock(&cattr, PTHREAD_COND_CLOCK); #endif - if (pthread_cond_init(&fa.cond, &cattr)) + if (pthread_cond_init(&fa.cond, &cattr) != 0) goto fail_cond; - pthread_condattr_destroy(&cattr); - - list_head_init(&fa.cmds); - - if (rib_reg(FA, &r_ops)) +#ifdef IPCP_FLOW_STATS + if (rib_reg(FA, &r_ops) != 0) goto fail_rib_reg; +#endif 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); + for (i = 0; i < HB_BUCKETS; i++) + list_head_init(&fa.hb_bkt[i]); + + list_head_init(&fa.hb_q); + fa.n_hbs = 0; + + pthread_condattr_destroy(&cattr); return 0; + fail_dt_reg: +#ifdef IPCP_FLOW_STATS + rib_unreg(FA); fail_rib_reg: +#endif pthread_cond_destroy(&fa.cond); fail_cond: pthread_condattr_destroy(&cattr); fail_cattr: + pthread_mutex_destroy(&fa.hb_mtx); + fail_hb_mtx: pthread_mutex_destroy(&fa.mtx); fail_mtx: pthread_rwlock_destroy(&fa.flows_lock); fail_rwlock: - return -1; } void fa_fini(void) { + struct list_head * p; + struct list_head * h; + +#ifdef IPCP_FLOW_STATS rib_unreg(FA); +#endif + list_for_each_safe(p, h, &fa.hb_q) { + struct hb_ent * e = list_entry(p, struct hb_ent, qnext); + list_del(&e->qnext); + free(e); + } pthread_cond_destroy(&fa.cond);; + pthread_mutex_destroy(&fa.hb_mtx); pthread_mutex_destroy(&fa.mtx); 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, np1_flow_read); + fa.psched = psched_create(packet_handler, np1_flow_read_fa); if (fa.psched == NULL) { log_err("Failed to start packet scheduler."); goto fail_psched; @@ -699,6 +999,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; @@ -716,12 +1017,15 @@ 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: @@ -742,12 +1046,14 @@ int fa_alloc(int fd, 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; size_t len; uint64_t eid; + struct timespec tv; + uint8_t nonce[HB_ID_LEN]; addr = dir_query(dst); if (addr == 0) @@ -755,34 +1061,40 @@ int fa_alloc(int fd, len = sizeof(*msg) + ipcp_dir_hash_len(); - if (ipcp_sdb_reserve(&sdb, len + data->len)) + 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; msg->loss = hton32(qs.loss); msg->ber = hton32(qs.ber); - msg->in_order = qs.in_order; + msg->service = qs.service; msg->max_gap = hton32(qs.max_gap); - msg->cypher_s = hton16(qs.cypher_s); msg->timeout = hton32(qs.timeout); + /* Seed an early RTT sample: peer acks this nonce on arrival. */ + if (random_buffer(nonce, HB_ID_LEN) == 0) { + memcpy(msg->hb_id, nonce, HB_ID_LEN); + clock_gettime(PTHREAD_COND_CLOCK, &tv); + fa_hb_record(nonce, eid, TS_TO_UINT64(tv)); + } + memcpy(msg + 1, dst, ipcp_dir_hash_len()); if (data->len > 0) - memcpy(shm_du_buff_head(sdb) + len, data->data, data->len); + memcpy(ssm_pk_buff_head(spb) + len, data->data, data->len); - if (dt_write_packet(addr, qc, fa.eid, sdb)) { + if (dt_write_packet(addr, qc, fa.eid, spb, NULL)) { log_err("Failed to send flow allocation request packet."); - ipcp_sdb_release(sdb); + ipcp_spb_release(spb); return -1; } @@ -794,6 +1106,13 @@ int fa_alloc(int fd, flow->r_addr = addr; flow->s_eid = eid; + flow->ctx = ca_ctx_get(addr, qos_spec_to_cube(qs)); + if (flow->ctx == NULL) { + fa_flow_fini(flow); + pthread_rwlock_unlock(&fa.flows_lock); + return -1; + } + pthread_rwlock_unlock(&fa.flows_lock); return 0; @@ -804,7 +1123,7 @@ int fa_alloc_resp(int fd, const buffer_t * data) { struct fa_msg * msg; - struct shm_du_buff * sdb; + struct ssm_pk_buff * spb; struct fa_flow * flow; qoscube_t qc = QOS_CUBE_BE; @@ -815,17 +1134,17 @@ int fa_alloc_resp(int fd, goto fail_alloc_resp; } - if (ipcp_sdb_reserve(&sdb, sizeof(*msg) + data->len)) { - log_err("Failed to reserve sdb (%zu bytes).", + if (ipcp_spb_reserve(&spb, sizeof(*msg) + data->len)) { + log_err("Failed to reserve spb (%zu bytes).", sizeof(*msg) + data->len); goto fail_reserve; } - msg = (struct fa_msg *) shm_du_buff_head(sdb); + msg = (struct fa_msg *) ssm_pk_buff_head(spb); memset(msg, 0, sizeof(*msg)); msg->code = FLOW_REPLY; - msg->response = response; + msg->response = hton32(response); if (data->len > 0) memcpy(msg + 1, data->data, data->len); @@ -836,13 +1155,13 @@ int fa_alloc_resp(int fd, pthread_rwlock_unlock(&fa.flows_lock); - if (dt_write_packet(flow->r_addr, qc, fa.eid, sdb)) { + if (dt_write_packet(flow->r_addr, qc, fa.eid, spb, NULL)) { log_err("Failed to send flow allocation response packet."); goto fail_packet; } if (response < 0) { - pthread_rwlock_rdlock(&fa.flows_lock); + pthread_rwlock_wrlock(&fa.flows_lock); fa_flow_fini(flow); pthread_rwlock_unlock(&fa.flows_lock); } else { @@ -852,7 +1171,7 @@ int fa_alloc_resp(int fd, return 0; fail_packet: - ipcp_sdb_release(sdb); + ipcp_spb_release(spb); fail_reserve: pthread_rwlock_wrlock(&fa.flows_lock); fa_flow_fini(flow); @@ -861,6 +1180,44 @@ int fa_alloc_resp(int fd, return -1; } +int fa_irm_update(int fd, + const buffer_t * data) +{ + struct fa_msg * msg; + struct ssm_pk_buff * spb; + struct fa_flow * flow; + qoscube_t qc = QOS_CUBE_BE; + uint64_t r_addr; + + flow = &fa.flows[fd]; + + if (ipcp_spb_reserve(&spb, sizeof(*msg) + data->len)) + return -1; + + msg = (struct fa_msg *) ssm_pk_buff_head(spb); + memset(msg, 0, sizeof(*msg)); + + msg->code = FLOW_IRM_UPDATE; + 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); + r_addr = flow->r_addr; + + pthread_rwlock_unlock(&fa.flows_lock); + + if (dt_write_packet(r_addr, qc, fa.eid, spb, NULL)) { + log_err("Failed to send flow update packet."); + ipcp_spb_release(spb); + return -1; + } + + return 0; +} + int fa_dealloc(int fd) { if (ipcp_flow_fini(fd) < 0) @@ -880,20 +1237,21 @@ int fa_dealloc(int fd) } static int fa_update_remote(int fd, - uint16_t ece) + uint16_t ece, + uint8_t cap) { 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))) { - log_err("Failed to reserve sdb (%zu bytes).", 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)); @@ -904,6 +1262,7 @@ static int fa_update_remote(int fd, msg->code = FLOW_UPDATE; msg->r_eid = hton64(flow->r_eid); msg->ece = hton16(ece); + msg->cap = cap; r_addr = flow->r_addr; #ifdef IPCP_FLOW_STATS @@ -912,9 +1271,9 @@ static int fa_update_remote(int fd, pthread_rwlock_unlock(&fa.flows_lock); - if (dt_write_packet(r_addr, qc, fa.eid, sdb)) { + if (dt_write_packet(r_addr, qc, fa.eid, spb, NULL)) { log_err("Failed to send flow update packet."); - ipcp_sdb_release(sdb); + ipcp_spb_release(spb); return -1; } @@ -923,15 +1282,17 @@ static int fa_update_remote(int fd, void fa_np1_rcv(uint64_t eid, uint8_t ecn, - struct shm_du_buff * sdb) + uint8_t cap, + struct ssm_pk_buff * spb) { struct fa_flow * flow; bool update; uint16_t ece; + uint8_t fcap; int fd; size_t len; - len = shm_du_buff_len(sdb); + len = ssm_pk_buff_len(spb); pthread_rwlock_wrlock(&fa.flows_lock); @@ -939,7 +1300,7 @@ void fa_np1_rcv(uint64_t eid, if (fd < 0) { pthread_rwlock_unlock(&fa.flows_lock); log_dbg("Received packet for unknown EID %" PRIu64 ".", eid); - ipcp_sdb_release(sdb); + ipcp_spb_release(spb); return; } @@ -949,13 +1310,13 @@ void fa_np1_rcv(uint64_t eid, ++flow->p_rcv; flow->b_rcv += len; #endif - update = ca_ctx_update_rcv(flow->ctx, len, ecn, &ece); + update = ca_ctx_update_rcv(flow->ctx, len, ecn, cap, &ece, &fcap); pthread_rwlock_unlock(&fa.flows_lock); - if (ipcp_flow_write(fd, sdb) < 0) { + if (np1_flow_write(fd, spb, NP1_GET_POOL(fd)) < 0) { log_dbg("Failed to write to flow %d.", fd); - ipcp_sdb_release(sdb); + ipcp_spb_release(spb); #ifdef IPCP_FLOW_STATS pthread_rwlock_wrlock(&fa.flows_lock); ++flow->p_rcv_f; @@ -965,5 +1326,5 @@ void fa_np1_rcv(uint64_t eid, } if (update) - fa_update_remote(eid, ece); + fa_update_remote(eid, ece, fcap); } diff --git a/src/ipcpd/unicast/fa.h b/src/ipcpd/unicast/fa.h index 1e716966..504d67d3 100644 --- a/src/ipcpd/unicast/fa.h +++ b/src/ipcpd/unicast/fa.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Flow allocator of the IPC Process * @@ -26,7 +26,7 @@ #include <ouroboros/qos.h> #include <ouroboros/utils.h> -int fa_init(void); +int fa_init(uint16_t max_rtt); void fa_fini(void); @@ -45,8 +45,12 @@ int fa_alloc_resp(int fd, int fa_dealloc(int fd); +int fa_irm_update(int fd, + const buffer_t * data); + void fa_np1_rcv(uint64_t eid, uint8_t ecn, - struct shm_du_buff * sdb); + uint8_t cap, + 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 e6cb2994..320ce165 100644 --- a/src/ipcpd/unicast/main.c +++ b/src/ipcpd/unicast/main.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Unicast IPC Process * @@ -35,6 +35,7 @@ #include <ouroboros/ipcp-dev.h> #include <ouroboros/logs.h> #include <ouroboros/notifier.h> +#include <ouroboros/qos.h> #include <ouroboros/random.h> #include <ouroboros/rib.h> #include <ouroboros/time.h> @@ -55,13 +56,8 @@ #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) { - strcpy(ipcpi.layer_name, conf->layer_info.name); - ipcpi.dir_hash_algo = (enum hash_algo) conf->layer_info.dir_hash_algo; - assert(ipcp_dir_hash_len() != 0); if (addr_auth_init(conf->unicast.addr_auth_type, @@ -70,15 +66,9 @@ static int initialize_components(const struct ipcp_config * conf) 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_info("IPCP got address %" PRIu64 ".", ipcpi.dt_addr); - - if (ca_init(conf->unicast.cong_avoid)) { + if (ca_init(conf->unicast.cong_avoid, conf->unicast.dt.max_rtt)) { log_err("Failed to initialize congestion avoidance."); goto fail_ca; } @@ -88,23 +78,25 @@ static int initialize_components(const struct ipcp_config * conf) 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(conf->unicast.dt.max_rtt)) { + 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(); @@ -116,10 +108,10 @@ static int initialize_components(const struct ipcp_config * conf) static void finalize_components(void) { - dir_fini(); - fa_fini(); + dir_fini(); + dt_fini(); ca_fini(); @@ -129,6 +121,11 @@ static void finalize_components(void) static int start_components(void) { + if (connmgr_start() < 0) { + log_err("Failed to start AP connection manager."); + goto fail_connmgr_start; + } + if (dt_start() < 0) { log_err("Failed to start data transfer."); goto fail_dt_start; @@ -144,27 +141,29 @@ static int start_components(void) goto fail_enroll_start; } - if (connmgr_start() < 0) { - 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) { - connmgr_stop(); + dir_stop(); enroll_stop(); @@ -172,24 +171,19 @@ 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) +static int unicast_ipcp_enroll(const char * dst, + const struct poa_addr * addr, + struct layer_info * info) { - struct conn conn; - uint8_t id[ENROLL_ID_LEN]; + struct ipcp_config * conf; + struct conn conn; + uint8_t id[ENROLL_ID_LEN]; + qosspec_t qs = qos_msg; if (random_buffer(id, ENROLL_ID_LEN) < 0) { log_err("Failed to generate enrollment ID."); @@ -198,7 +192,7 @@ static int unicast_ipcp_enroll(const char * dst, log_info_id(id, "Requesting enrollment."); - if (connmgr_alloc(COMPID_ENROLL, dst, NULL, &conn) < 0) { + if (connmgr_alloc(COMPID_ENROLL, dst, &qs, addr, &conn) < 0) { log_err_id(id, "Failed to get connection."); goto fail_id; } @@ -209,7 +203,11 @@ static int unicast_ipcp_enroll(const char * dst, goto fail_enroll_boot; } - if (initialize_components(enroll_get_conf()) < 0) { + 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; } @@ -227,9 +225,6 @@ static int unicast_ipcp_enroll(const char * dst, log_info_id(id, "Enrolled with %s.", dst); - info->dir_hash_algo = (enum pol_dir_hash) ipcpi.dir_hash_algo; - strcpy(info->name, ipcpi.layer_name); - return 0; fail_start_comp: @@ -240,32 +235,25 @@ static int unicast_ipcp_enroll(const char * dst, 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) < 0) { log_err("Failed to init IPCP components."); goto fail_init; } + enroll_bootstrap(conf); + if (start_components() < 0) { log_err("Failed to init IPCP components."); goto fail_start; } - if (bootstrap_components() < 0) { - log_err("Failed to bootstrap IPCP components."); - goto fail_bootstrap; - } - return 0; - fail_bootstrap: - stop_components(); fail_start: finalize_components(); fail_init: @@ -288,7 +276,8 @@ static struct ipcp_ops unicast_ops = { .ipcp_flow_alloc = fa_alloc, .ipcp_flow_join = NULL, .ipcp_flow_alloc_resp = fa_alloc_resp, - .ipcp_flow_dealloc = fa_dealloc + .ipcp_flow_dealloc = fa_dealloc, + .ipcp_flow_update = fa_irm_update }; int main(int argc, @@ -322,12 +311,13 @@ int main(int argc, ipcp_sigwait(); if (ipcp_get_state() == IPCP_SHUTDOWN) { + ipcp_stop(); stop_components(); finalize_components(); + } else { + ipcp_stop(); } - ipcp_stop(); - enroll_fini(); connmgr_fini(); diff --git a/src/ipcpd/unicast/pff.c b/src/ipcpd/unicast/pff.c index 9b2aa2b4..c8c3126f 100644 --- a/src/ipcpd/unicast/pff.c +++ b/src/ipcpd/unicast/pff.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * PDU Forwarding Function * diff --git a/src/ipcpd/unicast/pff.h b/src/ipcpd/unicast/pff.h index f44e5531..2eb42ce6 100644 --- a/src/ipcpd/unicast/pff.h +++ b/src/ipcpd/unicast/pff.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * PDU Forwarding Function * diff --git a/src/ipcpd/unicast/pff/alternate.c b/src/ipcpd/unicast/pff/alternate.c index 85e85914..1c508c1b 100644 --- a/src/ipcpd/unicast/pff/alternate.c +++ b/src/ipcpd/unicast/pff/alternate.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Policy for PFF with alternate next hops * @@ -211,7 +211,7 @@ struct pff_i * alternate_pff_create(void) if (pthread_rwlock_init(&tmp->lock, NULL)) goto fail_lock; - tmp->pft = pft_create(PFT_SIZE, false); + tmp->pft = pft_create(PFT_SIZE); if (tmp->pft == NULL) goto fail_pft; diff --git a/src/ipcpd/unicast/pff/alternate.h b/src/ipcpd/unicast/pff/alternate.h index 96207e74..ae3758dc 100644 --- a/src/ipcpd/unicast/pff/alternate.h +++ b/src/ipcpd/unicast/pff/alternate.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Policy for PFF with alternate next hops * diff --git a/src/ipcpd/unicast/pff/multipath.c b/src/ipcpd/unicast/pff/multipath.c index cbab0f5f..c2c21078 100644 --- a/src/ipcpd/unicast/pff/multipath.c +++ b/src/ipcpd/unicast/pff/multipath.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Policy for PFF supporting multipath routing * @@ -49,7 +49,7 @@ struct pff_ops multipath_pff_ops = { .del = multipath_pff_del, .flush = multipath_pff_flush, .nhop = multipath_pff_nhop, - .flow_state_change = NULL + .flow_state_change = multipath_pff_flow_state_change }; struct pff_i * multipath_pff_create(void) @@ -63,7 +63,7 @@ struct pff_i * multipath_pff_create(void) if (pthread_rwlock_init(&tmp->lock, NULL)) goto fail_rwlock; - tmp->pft = pft_create(PFT_SIZE, false); + tmp->pft = pft_create(PFT_SIZE); if (tmp->pft == NULL) goto fail_pft; @@ -170,6 +170,24 @@ void multipath_pff_flush(struct pff_i * pff_i) pft_flush(pff_i->pft); } +int multipath_pff_flow_state_change(struct pff_i * pff_i, + int fd, + bool up) +{ + assert(pff_i); + + if (up) + return 0; + + pthread_rwlock_wrlock(&pff_i->lock); + + pft_del_fd(pff_i->pft, fd); + + pthread_rwlock_unlock(&pff_i->lock); + + return 0; +} + int multipath_pff_nhop(struct pff_i * pff_i, uint64_t addr) { diff --git a/src/ipcpd/unicast/pff/multipath.h b/src/ipcpd/unicast/pff/multipath.h index 0eb03476..123030b6 100644 --- a/src/ipcpd/unicast/pff/multipath.h +++ b/src/ipcpd/unicast/pff/multipath.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Policy for PFF supporting multipath routing * @@ -53,6 +53,10 @@ void multipath_pff_flush(struct pff_i * pff_i); int multipath_pff_nhop(struct pff_i * pff_i, uint64_t addr); +int multipath_pff_flow_state_change(struct pff_i * pff_i, + int fd, + bool up); + extern struct pff_ops multipath_pff_ops; #endif /* OUROBOROS_IPCPD_UNICAST_MULTIPATH_PFF_H */ diff --git a/src/ipcpd/unicast/pff/ops.h b/src/ipcpd/unicast/pff/ops.h index 16a31273..e4cabd11 100644 --- a/src/ipcpd/unicast/pff/ops.h +++ b/src/ipcpd/unicast/pff/ops.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Pff policy ops * diff --git a/src/ipcpd/unicast/pff/pft.c b/src/ipcpd/unicast/pff/pft.c index 8c436113..2a295a40 100644 --- a/src/ipcpd/unicast/pff/pft.c +++ b/src/ipcpd/unicast/pff/pft.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Packet forwarding table (PFT) with chaining on collisions * @@ -43,12 +43,10 @@ struct pft_entry { struct pft { struct list_head * buckets; - bool hash_key; uint64_t buckets_size; }; -struct pft * pft_create(uint64_t buckets, - bool hash_key) +struct pft * pft_create(uint64_t buckets) { struct pft * tmp; unsigned int i; @@ -69,7 +67,6 @@ struct pft * pft_create(uint64_t buckets, if (tmp == NULL) return NULL; - tmp->hash_key = hash_key; tmp->buckets_size = buckets; tmp->buckets = malloc(buckets * sizeof(*tmp->buckets)); @@ -94,18 +91,36 @@ void pft_destroy(struct pft * pft) free(pft); } -void pft_flush(struct pft * pft) +void pft_del_fd(struct pft * pft, + int fd) { unsigned int i; struct list_head * p; struct list_head * h; struct pft_entry * entry; + size_t j; + size_t n; assert(pft); for (i = 0; i < pft->buckets_size; i++) { list_for_each_safe(p, h, &(pft->buckets[i])) { entry = list_entry(p, struct pft_entry, next); + + n = 0; + for (j = 0; j < entry->len; j++) { + if (entry->fds[j] != fd) + entry->fds[n++] = entry->fds[j]; + } + + if (n == entry->len) + continue; + + if (n > 0) { + entry->len = n; + continue; + } + list_del(&entry->next); free(entry->fds); free(entry); @@ -113,22 +128,29 @@ void pft_flush(struct pft * pft) } } -static uint64_t hash(uint64_t key) +void pft_flush(struct pft * pft) { - uint64_t res[2]; + unsigned int i; + struct list_head * p; + struct list_head * h; + struct pft_entry * entry; - mem_hash(HASH_MD5, res, (uint8_t *) &key, sizeof(key)); + assert(pft); - return res[0]; + for (i = 0; i < pft->buckets_size; i++) { + list_for_each_safe(p, h, &(pft->buckets[i])) { + entry = list_entry(p, struct pft_entry, next); + list_del(&entry->next); + free(entry->fds); + free(entry); + } + } } static uint64_t calc_key(struct pft * pft, uint64_t dst) { - if (pft->hash_key) - dst = hash(dst); - - return (dst & (pft->buckets_size - 1)); + return hash_mix64(dst) & (pft->buckets_size - 1); } int pft_insert(struct pft * pft, diff --git a/src/ipcpd/unicast/pff/pft.h b/src/ipcpd/unicast/pff/pft.h index 711dabcb..3517e0ef 100644 --- a/src/ipcpd/unicast/pff/pft.h +++ b/src/ipcpd/unicast/pff/pft.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Packet forwarding table (PFT) with chaining on collisions * @@ -24,19 +24,20 @@ #define OUROBOROS_PFT_H #include <stdint.h> -#include <stdbool.h> #include <stdlib.h> struct pft; /* Buckets is rounded up to the nearest power of 2 */ -struct pft * pft_create(uint64_t buckets, - bool hash_key); +struct pft * pft_create(uint64_t buckets); void pft_destroy(struct pft * table); void pft_flush(struct pft * table); +void pft_del_fd(struct pft * table, + int fd); + /* Passes ownership of the block of memory */ int pft_insert(struct pft * pft, uint64_t dst, diff --git a/src/ipcpd/unicast/pff/pol.h b/src/ipcpd/unicast/pff/pol.h index 245b03c4..e31c2794 100644 --- a/src/ipcpd/unicast/pff/pol.h +++ b/src/ipcpd/unicast/pff/pol.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * PDU Forwarding Function policies * diff --git a/src/ipcpd/unicast/pff/simple.c b/src/ipcpd/unicast/pff/simple.c index 5f95e3ce..4347dcba 100644 --- a/src/ipcpd/unicast/pff/simple.c +++ b/src/ipcpd/unicast/pff/simple.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Simple PDU Forwarding Function * @@ -47,7 +47,7 @@ struct pff_ops simple_pff_ops = { .del = simple_pff_del, .flush = simple_pff_flush, .nhop = simple_pff_nhop, - .flow_state_change = NULL + .flow_state_change = simple_pff_flow_state_change }; struct pff_i * simple_pff_create(void) @@ -63,7 +63,7 @@ struct pff_i * simple_pff_create(void) return NULL; } - tmp->pft = pft_create(PFT_SIZE, false); + tmp->pft = pft_create(PFT_SIZE); if (tmp->pft == NULL) { pthread_rwlock_destroy(&tmp->lock); free(tmp); @@ -170,6 +170,24 @@ void simple_pff_flush(struct pff_i * pff_i) pft_flush(pff_i->pft); } +int simple_pff_flow_state_change(struct pff_i * pff_i, + int fd, + bool up) +{ + assert(pff_i); + + if (up) + return 0; + + pthread_rwlock_wrlock(&pff_i->lock); + + pft_del_fd(pff_i->pft, fd); + + pthread_rwlock_unlock(&pff_i->lock); + + return 0; +} + int simple_pff_nhop(struct pff_i * pff_i, uint64_t addr) { diff --git a/src/ipcpd/unicast/pff/simple.h b/src/ipcpd/unicast/pff/simple.h index 0966a186..b72aba21 100644 --- a/src/ipcpd/unicast/pff/simple.h +++ b/src/ipcpd/unicast/pff/simple.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Simple policy for PFF * @@ -52,6 +52,10 @@ void simple_pff_flush(struct pff_i * pff_i); int simple_pff_nhop(struct pff_i * pff_i, uint64_t addr); +int simple_pff_flow_state_change(struct pff_i * pff_i, + int fd, + bool up); + extern struct pff_ops simple_pff_ops; #endif /* OUROBOROS_IPCPD_UNICAST_SIMPLE_PFF_H */ 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 18287fb8..0b4a165b 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 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Test of the hash table * @@ -22,105 +22,321 @@ #include "pft.c" +#include <test/test.h> + #include <stdio.h> #define TBL_SIZE 256 #define INT_TEST 4 -int pft_test(int argc, - char ** argv) +/* Next hops used by the del_fd tests. */ +#define FD_GONE 7 +#define FD_KEEP 8 +#define FD_OTHER 9 + +static int pft_add(struct pft * pft, + uint64_t dst, + const int * fds, + size_t len) +{ + int * blk; + size_t i; + + blk = malloc(sizeof(*blk) * len); + if (blk == NULL) + return -1; + + for (i = 0; i < len; i++) + blk[i] = fds[i]; + + if (pft_insert(pft, dst, blk, len)) { + free(blk); + return -1; + } + + return 0; +} + +static int test_pft_create_destroy(void) { struct pft * pft; - int i; - int * j; - size_t len; - (void) argc; - (void) argv; + TEST_START(); - pft = pft_create(TBL_SIZE, true); + pft = pft_create(TBL_SIZE); if (pft == NULL) { printf("Failed to create.\n"); - return -1; + goto fail; } pft_destroy(pft); - pft = pft_create(TBL_SIZE, false); + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_pft_insert_lookup(void) +{ + struct pft * pft; + int * j; + size_t len; + int i; + + TEST_START(); + + pft = pft_create(TBL_SIZE); if (pft == NULL) { printf("Failed to create.\n"); - return -1; + goto fail; } for (i = 0; i < TBL_SIZE + INT_TEST + 2; i++) { - j = malloc(sizeof(*j)); - if (j == NULL) { - printf("Failed to malloc.\n"); - pft_destroy(pft); - return -1; - } - *j = i; - - if (pft_insert(pft, i, j, 1)) { + if (pft_add(pft, i, &i, 1)) { printf("Failed to insert.\n"); - pft_destroy(pft); - free(j); - return -1; + goto fail_pft; } } if (pft_lookup(pft, INT_TEST, &j, &len)) { printf("Failed to lookup.\n"); - pft_destroy(pft); - return -1; + goto fail_pft; } if (*j != INT_TEST) { printf("Lookup returned wrong value (%d != %d).\n", INT_TEST, *j); - pft_destroy(pft); - return -1; + goto fail_pft; } if (pft_lookup(pft, TBL_SIZE + INT_TEST, &j, &len)) { - printf("Failed to lookup.\n"); - pft_destroy(pft); - return -1; + printf("Failed to lookup on a shared bucket.\n"); + goto fail_pft; } if (*j != TBL_SIZE + INT_TEST) { printf("Lookup returned wrong value (%d != %d).\n", - INT_TEST, *j); - pft_destroy(pft); - return -1; + TBL_SIZE + INT_TEST, *j); + goto fail_pft; + } + + pft_destroy(pft); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_pft: + pft_destroy(pft); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_pft_delete(void) +{ + struct pft * pft; + int * j; + size_t len; + int i; + + TEST_START(); + + pft = pft_create(TBL_SIZE); + if (pft == NULL) { + printf("Failed to create.\n"); + goto fail; + } + + for (i = 0; i < TBL_SIZE + INT_TEST + 2; i++) { + if (pft_add(pft, i, &i, 1)) { + printf("Failed to insert.\n"); + goto fail_pft; + } } if (pft_delete(pft, INT_TEST)) { printf("Failed to delete.\n"); - pft_destroy(pft); - return -1; + goto fail_pft; } if (pft_lookup(pft, INT_TEST, &j, &len) == 0) { printf("Failed to delete properly.\n"); - pft_destroy(pft); - return -1; + goto fail_pft; } if (pft_lookup(pft, TBL_SIZE + INT_TEST, &j, &len)) { printf("Failed to lookup after deletion.\n"); - pft_destroy(pft); - return -1; + goto fail_pft; } if (*j != TBL_SIZE + INT_TEST) { printf("Lookup returned wrong value (%d != %d).\n", - INT_TEST, *j); - pft_destroy(pft); - return -1; + TBL_SIZE + INT_TEST, *j); + goto fail_pft; } pft_destroy(pft); - return 0; + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_pft: + pft_destroy(pft); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_pft_del_fd_sole(void) +{ + struct pft * pft; + int * j; + size_t len; + int fds[] = {FD_GONE}; + + TEST_START(); + + pft = pft_create(TBL_SIZE); + if (pft == NULL) { + printf("Failed to create.\n"); + goto fail; + } + + if (pft_add(pft, INT_TEST, fds, 1)) { + printf("Failed to insert.\n"); + goto fail_pft; + } + + pft_del_fd(pft, FD_GONE); + + if (pft_lookup(pft, INT_TEST, &j, &len) == 0) { + printf("Route without a next hop survived.\n"); + goto fail_pft; + } + + pft_destroy(pft); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_pft: + pft_destroy(pft); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_pft_del_fd_shared(void) +{ + struct pft * pft; + int * j; + size_t len; + int fds[] = {FD_KEEP, FD_GONE, FD_OTHER}; + + TEST_START(); + + pft = pft_create(TBL_SIZE); + if (pft == NULL) { + printf("Failed to create.\n"); + goto fail; + } + + if (pft_add(pft, INT_TEST, fds, 3)) { + printf("Failed to insert.\n"); + goto fail_pft; + } + + pft_del_fd(pft, FD_GONE); + + if (pft_lookup(pft, INT_TEST, &j, &len)) { + printf("Route with next hops left was dropped.\n"); + goto fail_pft; + } + + if (len != 2) { + printf("Expected 2 next hops, got %zu.\n", len); + goto fail_pft; + } + + if (j[0] != FD_KEEP || j[1] != FD_OTHER) { + printf("Next hops not preserved in order (%d, %d).\n", + j[0], j[1]); + goto fail_pft; + } + + pft_destroy(pft); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_pft: + pft_destroy(pft); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_pft_del_fd_untouched(void) +{ + struct pft * pft; + int * j; + size_t len; + int fds[] = {FD_KEEP}; + + TEST_START(); + + pft = pft_create(TBL_SIZE); + if (pft == NULL) { + printf("Failed to create.\n"); + goto fail; + } + + if (pft_add(pft, INT_TEST, fds, 1)) { + printf("Failed to insert.\n"); + goto fail_pft; + } + + pft_del_fd(pft, FD_GONE); + + if (pft_lookup(pft, INT_TEST, &j, &len)) { + printf("Unrelated route was dropped.\n"); + goto fail_pft; + } + + if (len != 1 || *j != FD_KEEP) { + printf("Unrelated route was modified.\n"); + goto fail_pft; + } + + pft_destroy(pft); + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail_pft: + pft_destroy(pft); + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int pft_test(int argc, + char ** argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + + ret |= test_pft_create_destroy(); + ret |= test_pft_insert_lookup(); + ret |= test_pft_delete(); + ret |= test_pft_del_fd_sole(); + ret |= test_pft_del_fd_shared(); + ret |= test_pft_del_fd_untouched(); + + return ret; } diff --git a/src/ipcpd/unicast/psched.c b/src/ipcpd/unicast/psched.c index 7e12148b..dce85120 100644 --- a/src/ipcpd/unicast/psched.c +++ b/src/ipcpd/unicast/psched.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Packet scheduler component * @@ -30,6 +30,7 @@ #include <ouroboros/errno.h> #include <ouroboros/notifier.h> +#include <ouroboros/time.h> #include "common/connmgr.h" #include "ipcp.h" @@ -41,14 +42,16 @@ #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]; + fset_t * set[QOS_CUBE_MAX * IPCP_SCHED_THR_MUL]; next_packet_fn_t callback; read_fn_t read; pthread_t readers[QOS_CUBE_MAX * IPCP_SCHED_THR_MUL]; @@ -57,23 +60,162 @@ struct psched { struct sched_info { struct psched * sch; qoscube_t qc; + size_t idx; }; +/* Map an FD to one reader's set: one FD, one thread (no shared FDs). */ +static size_t fd_set_idx(int fd, qoscube_t qc) +{ + return qc + ((size_t) fd % IPCP_SCHED_THR_MUL) * QOS_CUBE_MAX; +} + static void cleanup_reader(void * o) { fqueue_destroy((fqueue_t *) o); } +/* + * Per-reader deadline scheduler: a paced flow is served, then deferred + * to its next-send deadline instead of blocking the thread, so it never + * stalls its thread-mates. + */ +struct dsched { + uint64_t deadline[PROC_MAX_FLOWS]; /* absolute ns, per tracked fd */ + int active[PROC_MAX_FLOWS]; /* compact list of tracked fds */ + int posn[PROC_MAX_FLOWS]; /* fd -> active index, -1 = none */ + size_t n; +}; + +static void cleanup_dsched(void * o) +{ + free(o); +} + +static void dsched_track(struct dsched * d, + int fd, + uint64_t deadline) +{ + if (d->posn[fd] >= 0) + return; + + d->deadline[fd] = deadline; + d->posn[fd] = (int) d->n; + d->active[d->n++] = fd; +} + +static void dsched_untrack(struct dsched * d, + int fd) +{ + int i = d->posn[fd]; + + if (i < 0) + return; + + d->active[i] = d->active[--d->n]; + d->posn[d->active[i]] = i; + d->posn[fd] = -1; +} + +/* Fold a deadline into the earliest pending one (0 = none yet). */ +static uint64_t dmin_fold(uint64_t dmin, + uint64_t deadline) +{ + if (dmin == 0 || deadline < dmin) + return deadline; + + return dmin; +} + +static uint64_t dsched_serve(struct dsched * d, + struct psched * sched, + qoscube_t qc, + uint64_t now) +{ + struct ssm_pk_buff * spb; + uint64_t dmin = 0; + size_t i; + int fd; + int ret; + time_t wait; + bool served; + + /* Round-robin one packet per flow so none monopolises egress. */ + do { + served = false; + + for (i = 0; i < d->n; ) { + fd = d->active[i]; + + if (d->deadline[fd] > now) { + dmin = dmin_fold(dmin, d->deadline[fd]); + ++i; + continue; + } + + ret = sched->read(fd, &spb); + if (ret == -EAGAIN) { /* empty now, keep it */ + ++i; + continue; + } + + if (ret < 0) { + dsched_untrack(d, fd); + continue; + } + + wait = sched->callback(fd, qc, spb); + served = true; + + if (wait > 0) { + d->deadline[fd] = now + (uint64_t) wait; + dmin = dmin_fold(dmin, d->deadline[fd]); + } + + ++i; + } + } while (served); + + return dmin; +} + +static void dsched_events(struct dsched * d, + fqueue_t * fq, + uint64_t now) +{ + int fd; + + while ((fd = fqueue_next(fq)) >= 0) { + switch (fqueue_type(fq)) { + case FLOW_DEALLOC: + dsched_untrack(d, fd); + notifier_event(NOTIFY_DT_FLOW_DEALLOC, &fd); + break; + case FLOW_DOWN: + notifier_event(NOTIFY_DT_FLOW_DOWN, &fd); + break; + case FLOW_UP: + notifier_event(NOTIFY_DT_FLOW_UP, &fd); + break; + case FLOW_PKT: + dsched_track(d, fd, now); + break; + default: + break; + } + } +} + static void * packet_reader(void * o) { - struct psched * sched; - struct shm_du_buff * sdb; - int fd; - fqueue_t * fq; - qoscube_t qc; + struct psched * sched; + struct dsched * d; + fqueue_t * fq; + qoscube_t qc; + size_t idx; sched = ((struct sched_info *) o)->sch; qc = ((struct sched_info *) o)->qc; + idx = ((struct sched_info *) o)->idx; ipcp_lock_to_core(); @@ -83,37 +225,51 @@ static void * packet_reader(void * o) if (fq == NULL) return (void *) -1; + d = malloc(sizeof(*d)); + if (d == NULL) { + fqueue_destroy(fq); + return (void *) -1; + } + + memset(d, 0, sizeof(*d)); + memset(d->posn, 0xFF, sizeof(d->posn)); /* -1: nothing tracked yet */ + + pthread_cleanup_push(cleanup_dsched, d); pthread_cleanup_push(cleanup_reader, fq); while (true) { - int ret = fevent(sched->set[qc], fq, NULL); + struct timespec now_ts; + struct timespec to; + struct timespec * timeo; + uint64_t now; + uint64_t dmin; + uint64_t delta; + int ret; + + clock_gettime(PTHREAD_COND_CLOCK, &now_ts); + + now = TS_TO_UINT64(now_ts); + + dmin = dsched_serve(d, sched, qc, now); + + if (dmin == 0) { + timeo = NULL; + } else { + delta = dmin > now ? dmin - now : 1; + to.tv_sec = (time_t) (delta / BILLION); + to.tv_nsec = (long) (delta % BILLION); + timeo = &to; + } + + ret = fevent(sched->set[idx], fq, timeo); if (ret < 0) continue; - while ((fd = fqueue_next(fq)) >= 0) { - switch (fqueue_type(fq)) { - case FLOW_DEALLOC: - notifier_event(NOTIFY_DT_FLOW_DEALLOC, &fd); - break; - case FLOW_DOWN: - notifier_event(NOTIFY_DT_FLOW_DOWN, &fd); - break; - case FLOW_UP: - notifier_event(NOTIFY_DT_FLOW_UP, &fd); - break; - case FLOW_PKT: - if (sched->read(fd, &sdb) < 0) - continue; - - sched->callback(fd, qc, sdb); - break; - default: - break; - } - } + dsched_events(d, fq, now); } pthread_cleanup_pop(true); + pthread_cleanup_pop(true); return (void *) 0; } @@ -135,7 +291,7 @@ struct psched * psched_create(next_packet_fn_t callback, psched->callback = callback; psched->read = read; - for (i = 0; i < QOS_CUBE_MAX; ++i) { + for (i = 0; i < QOS_CUBE_MAX * IPCP_SCHED_THR_MUL; ++i) { psched->set[i] = fset_create(); if (psched->set[i] == NULL) { for (j = 0; j < i; ++j) @@ -153,6 +309,7 @@ struct psched * psched_create(next_packet_fn_t callback, } infos[i]->sch = psched; infos[i]->qc = i % QOS_CUBE_MAX; + infos[i]->idx = i; } for (i = 0; i < QOS_CUBE_MAX * IPCP_SCHED_THR_MUL; ++i) { @@ -168,6 +325,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; @@ -185,16 +343,20 @@ 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) + for (j = 0; j < QOS_CUBE_MAX * IPCP_SCHED_THR_MUL; ++j) fset_destroy(psched->set[j]); fail_flow_set: free(psched); @@ -213,7 +375,7 @@ void psched_destroy(struct psched * psched) pthread_join(psched->readers[i], NULL); } - for (i = 0; i < QOS_CUBE_MAX; ++i) + for (i = 0; i < QOS_CUBE_MAX * IPCP_SCHED_THR_MUL; ++i) fset_destroy(psched->set[i]); free(psched); @@ -227,7 +389,7 @@ void psched_add(struct psched * psched, assert(psched); ipcp_flow_get_qoscube(fd, &qc); - fset_add(psched->set[qc], fd); + fset_add(psched->set[fd_set_idx(fd, qc)], fd); } void psched_del(struct psched * psched, @@ -238,5 +400,5 @@ void psched_del(struct psched * psched, assert(psched); ipcp_flow_get_qoscube(fd, &qc); - fset_del(psched->set[qc], fd); + fset_del(psched->set[fd_set_idx(fd, qc)], fd); } diff --git a/src/ipcpd/unicast/psched.h b/src/ipcpd/unicast/psched.h index 831f8084..8c2914b3 100644 --- a/src/ipcpd/unicast/psched.h +++ b/src/ipcpd/unicast/psched.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Packet scheduler component * @@ -26,12 +26,12 @@ #include <ouroboros/ipcp-dev.h> #include <ouroboros/fqueue.h> -typedef void (* next_packet_fn_t)(int fd, - qoscube_t qc, - struct shm_du_buff * sdb); +typedef time_t (* next_packet_fn_t)(int fd, + qoscube_t qc, + struct ssm_pk_buff * spb); typedef int (* read_fn_t)(int fd, - struct shm_du_buff ** sdb); + struct ssm_pk_buff ** spb); struct psched * psched_create(next_packet_fn_t callback, read_fn_t read); diff --git a/src/ipcpd/unicast/routing.c b/src/ipcpd/unicast/routing.c index f5417c24..1a4e4372 100644 --- a/src/ipcpd/unicast/routing.c +++ b/src/ipcpd/unicast/routing.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * 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 d5d833ae..8d721095 100644 --- a/src/ipcpd/unicast/routing.h +++ b/src/ipcpd/unicast/routing.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * 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 32f3e6fb..c168eb7d 100644 --- a/src/ipcpd/unicast/routing/graph.c +++ b/src/ipcpd/unicast/routing/graph.c @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Undirected graph structure * @@ -57,8 +57,8 @@ struct edge { }; struct graph { - size_t nr_vertices; - struct list_head vertices; + struct llist vertices; + pthread_mutex_t lock; }; @@ -67,7 +67,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 +85,7 @@ static struct vertex * find_vertex_by_addr(struct graph * graph, assert(graph); - list_for_each(p, &graph->vertices) { + llist_for_each(p, &graph->vertices) { struct vertex * e = list_entry(p, struct vertex, next); if (e->addr == addr) return e; @@ -99,8 +99,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 +139,7 @@ static struct vertex * add_vertex(struct graph * graph, vertex->addr = addr; /* Keep them ordered on address. */ - list_for_each(p, &graph->vertices) { + llist_for_each(p, &graph->vertices) { struct vertex * v = list_entry(p, struct vertex, next); if (v->addr > addr) break; @@ -148,46 +148,50 @@ static struct vertex * add_vertex(struct graph * graph, vertex->index = i; - list_add_tail(&vertex->next, p); + llist_add_tail_at(&vertex->next, p, &graph->vertices); /* 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++; - return vertex; } +static void free_edges(struct list_head * edges) +{ + struct list_head * p; + struct list_head * h; + + list_for_each_safe(p, h, edges) { + struct edge * e = list_entry(p, struct edge, next); + list_del(&e->next); + free(e); + } +} + static void del_vertex(struct graph * graph, struct vertex * vertex) { struct list_head * p; - struct list_head * h; - assert(graph); - assert(vertex); + assert(graph != NULL); + assert(vertex != NULL); - list_del(&vertex->next); + llist_del(&vertex->next, &graph->vertices); /* Decrease the index of the vertices to the right. */ - list_for_each(p, &graph->vertices) { + llist_for_each(p, &graph->vertices) { struct vertex * v = list_entry(p, struct vertex, next); if (v->addr > vertex->addr) v->index--; } - list_for_each_safe(p, h, &vertex->edges) { - struct edge * e = list_entry(p, struct edge, next); - del_edge(e); - } + free_edges(&vertex->edges); free(vertex); - - graph->nr_vertices--; } struct graph * graph_create(void) @@ -203,8 +207,7 @@ struct graph * graph_create(void) return NULL; } - graph->nr_vertices = 0; - list_head_init(&graph->vertices); + llist_init(&graph->vertices); 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) { + llist_for_each_safe(p, n, &graph->vertices) { 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(llist_is_empty(&graph->vertices)); + 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) { + llist_for_each(p, &graph->vertices) { 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) { + llist_for_each(p, &graph->vertices) { 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) { + llist_for_each(p, &graph->vertices) { v = list_entry(p, struct vertex, next); /* This is the src */ @@ -613,9 +603,9 @@ static int graph_routing_table_lfa(struct graph * graph, struct list_head * table, int ** dist) { - int * n_dist[PROG_MAX_FLOWS]; - uint64_t addrs[PROG_MAX_FLOWS]; - int n_index[PROG_MAX_FLOWS]; + int * n_dist[PROC_MAX_FLOWS]; + uint64_t addrs[PROC_MAX_FLOWS]; + int n_index[PROC_MAX_FLOWS]; struct list_head * p; struct list_head * q; struct vertex * v; @@ -628,13 +618,13 @@ static int graph_routing_table_lfa(struct graph * graph, if (graph_routing_table_simple(graph, s_addr, table, dist)) goto fail_table; - for (j = 0; j < PROG_MAX_FLOWS; j++) { + for (j = 0; j < PROC_MAX_FLOWS; j++) { n_dist[j] = NULL; n_index[j] = -1; addrs[j] = -1; } - list_for_each(p, &graph->vertices) { + llist_for_each(p, &graph->vertices) { 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) { + llist_for_each(p, &graph->vertices) { v = list_entry(p, struct vertex, next); if (v->addr == s_addr) @@ -705,7 +695,6 @@ static int graph_routing_table_ecmp(struct graph * graph, { struct vertex ** nhops; struct list_head * p; - struct list_head * h; size_t i; struct vertex * v; struct vertex * src_v; @@ -717,14 +706,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,16 +734,15 @@ static int graph_routing_table_ecmp(struct graph * graph, free(nhops); - list_for_each(h, &graph->vertices) { - v = list_entry(h, struct vertex, next); - if (tmp_dist[v->index] + 1 == (*dist)[v->index]) { + for (i = 0; i < graph->vertices.len; ++i) { + if (tmp_dist[i] + 1 == (*dist)[i]) { n = malloc(sizeof(*n)); if (n == NULL) { free(tmp_dist); goto fail_src_v; } n->nhop = e->nb->addr; - list_add_tail(&n->next, &forwarding[v->index]); + list_add_tail(&n->next, &forwarding[i]); } } @@ -763,38 +751,34 @@ static int graph_routing_table_ecmp(struct graph * graph, list_head_init(table); i = 0; - list_for_each(p, &graph->vertices) { + llist_for_each(p, &graph->vertices) { v = list_entry(p, struct vertex, next); - if (v->addr == s_addr) { + if (v->addr == s_addr || list_is_empty(&forwarding[i])) { ++i; continue; } t = malloc(sizeof(*t)); if (t == NULL) - goto fail_t; + goto fail_malloc; t->dst = v->addr; list_head_init(&t->nhops); - if (&forwarding[i] != forwarding[i].nxt) { - t->nhops.nxt = forwarding[i].nxt; - t->nhops.prv = forwarding[i].prv; - forwarding[i].prv->nxt = &t->nhops; - forwarding[i].nxt->prv = &t->nhops; - } + t->nhops.nxt = forwarding[i].nxt; + t->nhops.prv = forwarding[i].prv; + forwarding[i].prv->nxt = &t->nhops; + forwarding[i].nxt->prv = &t->nhops; list_add(&t->next, table); ++i; } - free(*dist); - *dist = NULL; free(forwarding); return 0; - fail_t: + fail_malloc: free_routing_table(table); fail_src_v: free(*dist); diff --git a/src/ipcpd/unicast/routing/graph.h b/src/ipcpd/unicast/routing/graph.h index 8190cc6c..f3766771 100644 --- a/src/ipcpd/unicast/routing/graph.h +++ b/src/ipcpd/unicast/routing/graph.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Undirected graph structure * diff --git a/src/ipcpd/unicast/routing/link-state.c b/src/ipcpd/unicast/routing/link-state.c index 57c0c7cb..4fba2f05 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 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * 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 lsdb "lsdb" #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,37 @@ 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; - pthread_rwlock_t db_lock; + struct { + struct llist nbs; + struct llist db; + 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 +149,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 +160,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 +181,9 @@ static struct adjacency * get_adj(const char * path) assert(path); - list_for_each(p, &ls.db) { + llist_for_each(p, &ls.db) { 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; } @@ -194,7 +206,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,7 +217,7 @@ static int lsdb_rib_getattr(const char * path, attr->size = 0; } - pthread_rwlock_unlock(&ls.db_lock); + pthread_rwlock_unlock(&ls.lock); return 0; } @@ -223,9 +235,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 (llist_is_empty(&ls.db) && llist_is_empty(&ls.nbs)) goto fail; a = get_adj(entry); @@ -236,11 +248,11 @@ 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; } @@ -250,60 +262,52 @@ static int lsdb_rib_readdir(char *** buf) 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 (llist_is_empty(&ls.db) && llist_is_empty(&ls.nbs)) { + *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; + + llist_for_each(p, &ls.nbs) { 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; - } + if ((*buf)[idx] == NULL) + goto fail_entry; - strcpy((*buf)[idx], entry); - - idx++; + strcpy((*buf)[idx++], entry); } - list_for_each(p, &ls.db) { + llist_for_each(p, &ls.db) { 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; - } + if ((*buf)[idx] == NULL) + goto fail_entry; - strcpy((*buf)[idx], 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 = { @@ -319,28 +323,28 @@ static int lsdb_add_nb(uint64_t addr, struct list_head * p; struct nb * nb; - pthread_rwlock_wrlock(&ls.db_lock); + pthread_rwlock_wrlock(&ls.lock); - list_for_each(p, &ls.nbs) { + llist_for_each(p, &ls.nbs) { 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; } @@ -348,14 +352,12 @@ static int lsdb_add_nb(uint64_t addr, nb->fd = fd; nb->type = type; - list_add_tail(&nb->next, p); - - ++ls.nbs_len; + llist_add_tail_at(&nb->next, p, &ls.nbs); - 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; } @@ -366,22 +368,22 @@ static int lsdb_del_nb(uint64_t addr, 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) { + llist_for_each_safe(p, h, &ls.nbs) { 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; + + llist_del(&nb->next, &ls.nbs); + 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 +393,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) { + llist_for_each(p, &ls.nbs) { 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; } @@ -413,12 +415,11 @@ static void calculate_pff(struct routing_i * instance) struct list_head table; struct list_head * p; struct list_head * q; - int fds[PROG_MAX_FLOWS]; + int fds[PROC_MAX_FLOWS]; 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 +454,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,7 +464,7 @@ 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, @@ -480,9 +481,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) { + llist_for_each(p, &ls.db) { struct adjacency * a = list_entry(p, struct adjacency, next); if (a->dst == dst && a->src == src) { if (a->seqno < seqno) { @@ -490,7 +491,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 +501,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; } @@ -509,14 +510,12 @@ static int lsdb_add_link(uint64_t src, adj->seqno = seqno; adj->stamp = now.tv_sec; - list_add_tail(&adj->next, p); - - ls.db_len++; + llist_add_tail_at(&adj->next, p, &ls.db); 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); @@ -529,25 +528,23 @@ static int lsdb_del_link(uint64_t src, 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) { + llist_for_each_safe(p, h, &ls.db) { struct adjacency * a = list_entry(p, struct adjacency, next); if (a->dst == dst && a->src == src) { - list_del(&a->next); + llist_del(&a->next, &ls.db); if (graph_del_edge(ls.graph, src, dst)) log_warn("Failed to delete edge from graph."); - 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 +567,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,10 +584,20 @@ static void send_lsm(uint64_t src, lsm.s_addr = hton64(src); lsm.seqno = hton64(seqno); - list_for_each(p, &ls.nbs) { + llist_for_each(p, &ls.nbs) { 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 } } @@ -604,9 +611,9 @@ 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); + pthread_rwlock_rdlock(&ls.lock); - list_for_each(p, &ls.db) { + llist_for_each(p, &ls.db) { struct adjacency * adj; struct adjacency * cpy; adj = list_entry(p, struct adjacency, next); @@ -623,7 +630,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 +656,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) { + llist_for_each_safe(p, h, &ls.db) { struct adjacency * adj; adj = list_entry(p, struct adjacency, next); - if (now.tv_sec - adj->stamp > LS_TIMEO) { - list_del(&adj->next); - log_dbg("%" PRIu64 " - %" PRIu64" timed out.", - adj->src, adj->dst); + if (now.tv_sec > adj->stamp + ls.conf.t_timeo) { + llist_del(&adj->next, &ls.db); + 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 +674,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 +683,7 @@ static void * lsupdate(void * o) pthread_cleanup_pop(true); - sleep(LS_UPDATE_TIME); + sleep(ls.conf.t_update); } return (void *) 0; @@ -708,15 +715,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) { + llist_for_each(p, &ls.nbs) { 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 +757,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 +786,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 (lsdb_add_link(msg.s_addr, + msg.d_addr, + msg.seqno, &qs)) continue; @@ -787,14 +822,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,50 +851,56 @@ 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."); + log_dbg("Failed to add neighbor to lsdb."); - if (lsdb_add_link(ipcpi.dt_addr, c->conn_info.addr, 0, &qs)) - log_dbg("Failed to add new adjacency to LSDB."); + if (lsdb_add_link(ls.addr, c->conn_info.addr, 0, &qs)) + log_dbg("Failed to add new adjacency to lsdb."); 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."); + log_dbg("Failed to delete neighbor from lsdb."); - if (lsdb_del_link(ipcpi.dt_addr, c->conn_info.addr)) - log_dbg("Local link was not in LSDB."); + if (lsdb_del_link(ls.addr, c->conn_info.addr)) + log_dbg("Local link was not in lsdb."); break; case NOTIFY_DT_CONN_QOS: log_dbg("QoS changes currently unsupported."); break; case NOTIFY_DT_CONN_UP: flow_event(c->flow_info.fd, true); + + if (lsdb_add_link(ls.addr, c->conn_info.addr, 0, &qs) < 0) + log_dbg("Failed to re-add adjacency to lsdb."); break; case NOTIFY_DT_CONN_DOWN: flow_event(c->flow_info.fd, false); + + if (lsdb_del_link(ls.addr, c->conn_info.addr) < 0) + log_dbg("Local link was not in lsdb."); break; case NOTIFY_MGMT_CONN_ADD: 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."); + log_warn("Failed to add mgmt neighbor to lsdb."); /* replicate the entire lsdb */ lsdb_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."); + log_warn("Failed to delete mgmt neighbor from lsdb."); break; default: break; @@ -886,11 +927,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 +947,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 +962,143 @@ 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; + llist_init(&ls.db); + llist_init(&ls.nbs); + list_head_init(&ls.instances.list); - if (rib_reg(LSDB, &r_ops)) + if (rib_reg(lsdb, &r_ops)) goto fail_rib_reg; - 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 +1109,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(lsdb); fset_destroy(ls.mgmt_set); @@ -1039,17 +1117,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) { + llist_for_each_safe(p, h, &ls.db) { struct adjacency * a = list_entry(p, struct adjacency, next); - list_del(&a->next); + llist_del(&a->next, &ls.db); 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 d77d72df..38e19065 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 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * 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 8a79b7ec..b19c5176 100644 --- a/src/ipcpd/unicast/routing/ops.h +++ b/src/ipcpd/unicast/routing/ops.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * 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 b6a6f150..545f5df2 100644 --- a/src/ipcpd/unicast/routing/pol.h +++ b/src/ipcpd/unicast/routing/pol.h @@ -1,5 +1,5 @@ /* - * Ouroboros - Copyright (C) 2016 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * 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 d805640c..40a744ff 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 - 2024 + * Ouroboros - Copyright (C) 2016 - 2026 * * Test of the graph structure * diff --git a/src/ipcpd/unicast/tests/CMakeLists.txt b/src/ipcpd/unicast/tests/CMakeLists.txt new file mode 100644 index 00000000..2e35ed66 --- /dev/null +++ b/src/ipcpd/unicast/tests/CMakeLists.txt @@ -0,0 +1,34 @@ +get_filename_component(CURRENT_SOURCE_PARENT_DIR + ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY) +get_filename_component(CURRENT_BINARY_PARENT_DIR + ${CMAKE_CURRENT_BINARY_DIR} DIRECTORY) + +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 + cap_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} + ${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 +) + +disable_test_logging_for_target(${PARENT_DIR}_test) +target_link_libraries(${PARENT_DIR}_test PRIVATE ouroboros-common) + +add_dependencies(build_tests ${PARENT_DIR}_test) + +ouroboros_register_tests(TARGET ${PARENT_DIR}_test TESTS ${${PARENT_DIR}_tests}) diff --git a/src/ipcpd/unicast/tests/cap_test.c b/src/ipcpd/unicast/tests/cap_test.c new file mode 100644 index 00000000..e3c3f8b3 --- /dev/null +++ b/src/ipcpd/unicast/tests/cap_test.c @@ -0,0 +1,177 @@ +/* + * Ouroboros - Copyright (C) 2016 - 2026 + * + * Unit tests for link capacity codes + * + * 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 "cap.c" + +#include <test/test.h> + +/* Exact roundtrip holds for codes >= 32 (rates >= 256 B/s). */ +static int test_cap_codec_roundtrip(void) +{ + unsigned c; + + TEST_START(); + + for (c = 32; c <= 255; c++) { + if (cap_enc(cap_dec((uint8_t) c)) != c) { + printf("Code %u does not roundtrip.\n", c); + goto fail; + } + + if (cap_dec((uint8_t) c) <= cap_dec((uint8_t) (c - 1))) { + printf("Decode not monotone at %u.\n", c); + goto fail; + } + } + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_cap_codec_bounds(void) +{ + TEST_START(); + + if (cap_enc(0) != 0 || cap_dec(0) != 0) { + printf("Zero is not unknown.\n"); + goto fail; + } + + if (cap_enc(1) != 1) { + printf("Rate 1 encoded as %u.\n", cap_enc(1)); + goto fail; + } + + if (cap_enc(UINT64_MAX) != 255) { + printf("Max rate encoded as %u.\n", cap_enc(UINT64_MAX)); + goto fail; + } + + if (cap_dec(255) <= cap_dec(254)) { + printf("Top code does not decode.\n"); + goto fail; + } + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_cap_min(void) +{ + TEST_START(); + + if (cap_min(0, 42) != 42 || cap_min(42, 0) != 42) { + printf("Unknown not skipped in min.\n"); + goto fail; + } + + if (cap_min(0, 0) != 0) { + printf("Two unknowns not unknown.\n"); + goto fail; + } + + if (cap_min(97, 42) != 42 || cap_min(42, 97) != 42) { + printf("Min not taken.\n"); + goto fail; + } + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +static int test_cap_stamp(void) +{ + uint8_t pci; + + TEST_START(); + + pci = 42; + + cap_stamp(&pci, 0); + + if (pci != 42) { + printf("Unknown own code overwrote the byte.\n"); + goto fail; + } + + pci = 0; + + cap_stamp(&pci, 97); + + if (pci != 97) { + printf("Own code not written into unknown.\n"); + goto fail; + } + + pci = 97; + + cap_stamp(&pci, 42); + + if (pci != 42) { + printf("Lower own code did not lower the byte.\n"); + goto fail; + } + + pci = 42; + + cap_stamp(&pci, 97); + + if (pci != 42) { + printf("Higher own code raised the byte.\n"); + goto fail; + } + + TEST_SUCCESS(); + + return TEST_RC_SUCCESS; + fail: + TEST_FAIL(); + return TEST_RC_FAIL; +} + +int cap_test(int argc, + char ** argv) +{ + int ret = 0; + + (void) argc; + (void) argv; + + ret |= test_cap_codec_roundtrip(); + ret |= test_cap_codec_bounds(); + ret |= test_cap_min(); + ret |= test_cap_stamp(); + + return ret; +} |
