summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt4
-rw-r--r--src/ipcpd/CMakeLists.txt72
-rw-r--r--src/ipcpd/broadcast/CMakeLists.txt41
-rw-r--r--src/ipcpd/broadcast/connmgr.c2
-rw-r--r--src/ipcpd/broadcast/dt.c62
-rw-r--r--src/ipcpd/broadcast/dt.h2
-rw-r--r--src/ipcpd/broadcast/main.c2
-rw-r--r--src/ipcpd/common/comp.h2
-rw-r--r--src/ipcpd/common/connmgr.c2
-rw-r--r--src/ipcpd/common/connmgr.h2
-rw-r--r--src/ipcpd/common/enroll.c2
-rw-r--r--src/ipcpd/common/enroll.h2
-rw-r--r--src/ipcpd/config.h.in17
-rw-r--r--src/ipcpd/eth/CMakeLists.txt155
-rw-r--r--src/ipcpd/eth/dix.c2
-rw-r--r--src/ipcpd/eth/eth.c75
-rw-r--r--src/ipcpd/eth/llc.c2
-rw-r--r--src/ipcpd/ipcp.c60
-rw-r--r--src/ipcpd/ipcp.h2
-rw-r--r--src/ipcpd/local/CMakeLists.txt43
-rw-r--r--src/ipcpd/local/main.c38
-rw-r--r--src/ipcpd/np1.h41
-rw-r--r--src/ipcpd/shim-data.c2
-rw-r--r--src/ipcpd/shim-data.h2
-rw-r--r--src/ipcpd/udp/CMakeLists.txt89
-rw-r--r--src/ipcpd/udp/udp.c35
-rw-r--r--src/ipcpd/udp/udp4.c2
-rw-r--r--src/ipcpd/udp/udp6.c2
-rw-r--r--src/ipcpd/unicast/CMakeLists.txt69
-rw-r--r--src/ipcpd/unicast/addr-auth.c2
-rw-r--r--src/ipcpd/unicast/addr-auth.h2
-rw-r--r--src/ipcpd/unicast/addr-auth/flat.c2
-rw-r--r--src/ipcpd/unicast/addr-auth/flat.h2
-rw-r--r--src/ipcpd/unicast/addr-auth/ops.h2
-rw-r--r--src/ipcpd/unicast/addr-auth/pol.h2
-rw-r--r--src/ipcpd/unicast/ca.c2
-rw-r--r--src/ipcpd/unicast/ca.h2
-rw-r--r--src/ipcpd/unicast/ca/mb-ecn.c2
-rw-r--r--src/ipcpd/unicast/ca/mb-ecn.h2
-rw-r--r--src/ipcpd/unicast/ca/nop.c2
-rw-r--r--src/ipcpd/unicast/ca/nop.h2
-rw-r--r--src/ipcpd/unicast/ca/ops.h2
-rw-r--r--src/ipcpd/unicast/ca/pol.h2
-rw-r--r--src/ipcpd/unicast/connmgr.c2
-rw-r--r--src/ipcpd/unicast/dir.c2
-rw-r--r--src/ipcpd/unicast/dir.h2
-rw-r--r--src/ipcpd/unicast/dir/dht.c424
-rw-r--r--src/ipcpd/unicast/dir/dht.h2
-rw-r--r--src/ipcpd/unicast/dir/dht.proto2
-rw-r--r--src/ipcpd/unicast/dir/ops.h2
-rw-r--r--src/ipcpd/unicast/dir/pol.h2
-rw-r--r--src/ipcpd/unicast/dir/tests/CMakeLists.txt44
-rw-r--r--src/ipcpd/unicast/dir/tests/dht_test.c47
-rw-r--r--src/ipcpd/unicast/dt.c48
-rw-r--r--src/ipcpd/unicast/dt.h8
-rw-r--r--src/ipcpd/unicast/fa.c85
-rw-r--r--src/ipcpd/unicast/fa.h4
-rw-r--r--src/ipcpd/unicast/main.c2
-rw-r--r--src/ipcpd/unicast/pff.c2
-rw-r--r--src/ipcpd/unicast/pff.h2
-rw-r--r--src/ipcpd/unicast/pff/alternate.c2
-rw-r--r--src/ipcpd/unicast/pff/alternate.h2
-rw-r--r--src/ipcpd/unicast/pff/multipath.c2
-rw-r--r--src/ipcpd/unicast/pff/multipath.h2
-rw-r--r--src/ipcpd/unicast/pff/ops.h2
-rw-r--r--src/ipcpd/unicast/pff/pft.c2
-rw-r--r--src/ipcpd/unicast/pff/pft.h2
-rw-r--r--src/ipcpd/unicast/pff/pol.h2
-rw-r--r--src/ipcpd/unicast/pff/simple.c2
-rw-r--r--src/ipcpd/unicast/pff/simple.h2
-rw-r--r--src/ipcpd/unicast/pff/tests/CMakeLists.txt38
-rw-r--r--src/ipcpd/unicast/pff/tests/pft_test.c2
-rw-r--r--src/ipcpd/unicast/psched.c14
-rw-r--r--src/ipcpd/unicast/psched.h6
-rw-r--r--src/ipcpd/unicast/routing.c2
-rw-r--r--src/ipcpd/unicast/routing.h2
-rw-r--r--src/ipcpd/unicast/routing/graph.c84
-rw-r--r--src/ipcpd/unicast/routing/graph.h2
-rw-r--r--src/ipcpd/unicast/routing/link-state.c88
-rw-r--r--src/ipcpd/unicast/routing/link-state.h2
-rw-r--r--src/ipcpd/unicast/routing/ops.h2
-rw-r--r--src/ipcpd/unicast/routing/pol.h2
-rw-r--r--src/ipcpd/unicast/routing/tests/CMakeLists.txt38
-rw-r--r--src/ipcpd/unicast/routing/tests/graph_test.c2
-rw-r--r--src/irmd/CMakeLists.txt137
-rw-r--r--src/irmd/config.h.in12
-rw-r--r--src/irmd/configfile.c2
-rw-r--r--src/irmd/configfile.h2
-rw-r--r--src/irmd/ipcp.c8
-rw-r--r--src/irmd/ipcp.h2
-rw-r--r--src/irmd/irmd.h2
-rw-r--r--src/irmd/main.c1075
-rw-r--r--src/irmd/oap.c292
-rw-r--r--src/irmd/oap.h95
-rw-r--r--src/irmd/oap/auth.c252
-rw-r--r--src/irmd/oap/auth.h (renamed from src/irmd/tests/irm_test.c)22
-rw-r--r--src/irmd/oap/cli.c576
-rw-r--r--src/irmd/oap/hdr.c465
-rw-r--r--src/irmd/oap/hdr.h159
-rw-r--r--src/irmd/oap/internal.h118
-rw-r--r--src/irmd/oap/io.c138
-rw-r--r--src/irmd/oap/io.h40
-rw-r--r--src/irmd/oap/srv.c498
-rw-r--r--src/irmd/oap/tests/CMakeLists.txt64
-rw-r--r--src/irmd/oap/tests/common.c457
-rw-r--r--src/irmd/oap/tests/common.h100
-rw-r--r--src/irmd/oap/tests/oap_test.c1181
-rw-r--r--src/irmd/oap/tests/oap_test_ml_dsa.c447
-rw-r--r--src/irmd/reg/CMakeLists.txt9
-rw-r--r--src/irmd/reg/flow.c54
-rw-r--r--src/irmd/reg/flow.h22
-rw-r--r--src/irmd/reg/ipcp.c3
-rw-r--r--src/irmd/reg/ipcp.h2
-rw-r--r--src/irmd/reg/name.c63
-rw-r--r--src/irmd/reg/name.h17
-rw-r--r--src/irmd/reg/pool.c97
-rw-r--r--src/irmd/reg/pool.h48
-rw-r--r--src/irmd/reg/proc.c38
-rw-r--r--src/irmd/reg/proc.h11
-rw-r--r--src/irmd/reg/prog.c24
-rw-r--r--src/irmd/reg/prog.h5
-rw-r--r--src/irmd/reg/reg.c542
-rw-r--r--src/irmd/reg/reg.h24
-rw-r--r--src/irmd/reg/tests/CMakeLists.txt38
-rw-r--r--src/irmd/reg/tests/flow_test.c59
-rw-r--r--src/irmd/reg/tests/ipcp_test.c4
-rw-r--r--src/irmd/reg/tests/name_test.c12
-rw-r--r--src/irmd/reg/tests/proc_test.c25
-rw-r--r--src/irmd/reg/tests/prog_test.c8
-rw-r--r--src/irmd/reg/tests/reg_test.c306
-rw-r--r--src/irmd/tests/CMakeLists.txt27
-rw-r--r--src/irmd/tests/oap_test.c285
-rw-r--r--src/lib/CMakeLists.txt397
-rw-r--r--src/lib/bitmap.c2
-rw-r--r--src/lib/btree.c2
-rw-r--r--src/lib/cep.c2
-rw-r--r--src/lib/config.h.in22
-rw-r--r--src/lib/crc32.c2
-rw-r--r--src/lib/crypt.c751
-rw-r--r--src/lib/crypt/openssl.c1396
-rw-r--r--src/lib/crypt/openssl.h108
-rw-r--r--src/lib/dev.c992
-rw-r--r--src/lib/frct.c56
-rw-r--r--src/lib/hash.c2
-rw-r--r--src/lib/irm.c2
-rw-r--r--src/lib/list.c78
-rw-r--r--src/lib/lockfile.c2
-rw-r--r--src/lib/logs.c2
-rw-r--r--src/lib/md5.c2
-rw-r--r--src/lib/notifier.c2
-rw-r--r--src/lib/pb/cep.proto2
-rw-r--r--src/lib/pb/enroll.proto2
-rw-r--r--src/lib/pb/ipcp.proto3
-rw-r--r--src/lib/pb/ipcp_config.proto2
-rw-r--r--src/lib/pb/irm.proto11
-rw-r--r--src/lib/pb/model.proto15
-rw-r--r--src/lib/protobuf.c22
-rw-r--r--src/lib/qoscube.c2
-rw-r--r--src/lib/random.c2
-rw-r--r--src/lib/rib.c2
-rw-r--r--src/lib/serdes-irm.c30
-rw-r--r--src/lib/serdes-oep.c2
-rw-r--r--src/lib/sha3.c2
-rw-r--r--src/lib/shm_rbuff.c215
-rw-r--r--src/lib/shm_rbuff_ll.c249
-rw-r--r--src/lib/shm_rbuff_pthr.c304
-rw-r--r--src/lib/shm_rdrbuff.c610
-rw-r--r--src/lib/sockets.c2
-rw-r--r--src/lib/ssm/flow_set.c (renamed from src/lib/shm_flow_set.c)55
-rw-r--r--src/lib/ssm/pool.c935
-rw-r--r--src/lib/ssm/rbuff.c449
-rw-r--r--src/lib/ssm/ssm.h.in171
-rw-r--r--src/lib/ssm/tests/CMakeLists.txt21
-rw-r--r--src/lib/ssm/tests/flow_set_test.c255
-rw-r--r--src/lib/ssm/tests/pool_sharding_test.c501
-rw-r--r--src/lib/ssm/tests/pool_test.c1060
-rw-r--r--src/lib/ssm/tests/rbuff_test.c675
-rw-r--r--src/lib/tests/CMakeLists.txt25
-rw-r--r--src/lib/tests/auth_test.c161
-rw-r--r--src/lib/tests/auth_test_ml_dsa.c356
-rw-r--r--src/lib/tests/auth_test_slh_dsa.c367
-rw-r--r--src/lib/tests/bitmap_test.c2
-rw-r--r--src/lib/tests/btree_test.c2
-rw-r--r--src/lib/tests/crc32_test.c2
-rw-r--r--src/lib/tests/crypt_test.c385
-rw-r--r--src/lib/tests/hash_test.c4
-rw-r--r--src/lib/tests/kex_test.c844
-rw-r--r--src/lib/tests/kex_test_ml_kem.c549
-rw-r--r--src/lib/tests/md5_test.c2
-rw-r--r--src/lib/tests/sha3_test.c2
-rw-r--r--src/lib/tests/shm_rbuff_test.c113
-rw-r--r--src/lib/tests/sockets_test.c8
-rw-r--r--src/lib/tests/time_test.c4
-rw-r--r--src/lib/tests/tpm_test.c4
-rw-r--r--src/lib/timerwheel.c60
-rw-r--r--src/lib/tpm.c4
-rw-r--r--src/lib/utils.c94
-rw-r--r--src/tools/CMakeLists.txt80
-rw-r--r--src/tools/irm/CMakeLists.txt44
-rw-r--r--src/tools/irm/irm.c2
-rw-r--r--src/tools/irm/irm_bind.c2
-rw-r--r--src/tools/irm/irm_bind_ipcp.c2
-rw-r--r--src/tools/irm/irm_bind_process.c2
-rw-r--r--src/tools/irm/irm_bind_program.c2
-rw-r--r--src/tools/irm/irm_ipcp.c2
-rw-r--r--src/tools/irm/irm_ipcp_bootstrap.c2
-rw-r--r--src/tools/irm/irm_ipcp_connect.c2
-rw-r--r--src/tools/irm/irm_ipcp_create.c2
-rw-r--r--src/tools/irm/irm_ipcp_destroy.c2
-rw-r--r--src/tools/irm/irm_ipcp_disconnect.c2
-rw-r--r--src/tools/irm/irm_ipcp_enroll.c2
-rw-r--r--src/tools/irm/irm_ipcp_list.c2
-rw-r--r--src/tools/irm/irm_name.c2
-rw-r--r--src/tools/irm/irm_name_create.c6
-rw-r--r--src/tools/irm/irm_name_destroy.c2
-rw-r--r--src/tools/irm/irm_name_list.c2
-rw-r--r--src/tools/irm/irm_name_reg.c2
-rw-r--r--src/tools/irm/irm_name_unreg.c2
-rw-r--r--src/tools/irm/irm_ops.h2
-rw-r--r--src/tools/irm/irm_unbind.c2
-rw-r--r--src/tools/irm/irm_unbind_ipcp.c2
-rw-r--r--src/tools/irm/irm_unbind_process.c2
-rw-r--r--src/tools/irm/irm_unbind_program.c2
-rw-r--r--src/tools/irm/irm_utils.c2
-rw-r--r--src/tools/irm/irm_utils.h2
-rw-r--r--src/tools/obc/CMakeLists.txt16
-rw-r--r--src/tools/obc/obc.c2
-rw-r--r--src/tools/ocbr/CMakeLists.txt21
-rw-r--r--src/tools/ocbr/ocbr.c2
-rw-r--r--src/tools/ocbr/ocbr_client.c2
-rw-r--r--src/tools/ocbr/ocbr_server.c2
-rw-r--r--src/tools/oecho/CMakeLists.txt16
-rw-r--r--src/tools/oecho/oecho.c2
-rw-r--r--src/tools/operf/CMakeLists.txt26
-rw-r--r--src/tools/operf/operf.c3
-rw-r--r--src/tools/operf/operf_client.c2
-rw-r--r--src/tools/operf/operf_server.c2
-rw-r--r--src/tools/oping/CMakeLists.txt28
-rw-r--r--src/tools/oping/oping.c26
-rw-r--r--src/tools/oping/oping_client.c253
-rw-r--r--src/tools/oping/oping_server.c75
-rw-r--r--src/tools/ovpn/CMakeLists.txt21
-rw-r--r--src/tools/ovpn/ovpn.c2
-rw-r--r--src/tools/time_utils.h2
244 files changed, 16747 insertions, 6053 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
deleted file mode 100644
index 54fdd8ab..00000000
--- a/src/CMakeLists.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-add_subdirectory(lib)
-add_subdirectory(ipcpd)
-add_subdirectory(irmd)
-add_subdirectory(tools)
diff --git a/src/ipcpd/CMakeLists.txt b/src/ipcpd/CMakeLists.txt
index b3b049e3..609da54a 100644
--- a/src/ipcpd/CMakeLists.txt
+++ b/src/ipcpd/CMakeLists.txt
@@ -1,60 +1,30 @@
-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(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)
+if(HAVE_ETH)
+ add_subdirectory(eth)
+endif()
+add_subdirectory(udp)
diff --git a/src/ipcpd/broadcast/CMakeLists.txt b/src/ipcpd/broadcast/CMakeLists.txt
index 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..30e89a4f 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
*
@@ -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 151b38c8..b3cbdc79 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
*
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 1bb8c932..48ad79ba 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
*
diff --git a/src/ipcpd/common/connmgr.h b/src/ipcpd/common/connmgr.h
index 0710dbbf..f48ecd1b 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
*
diff --git a/src/ipcpd/common/enroll.c b/src/ipcpd/common/enroll.c
index 8e5384a5..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
*
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 d2af6440..0b4252e5 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
*
@@ -29,11 +29,9 @@
#define SOCKET_TIMEOUT @SOCKET_TIMEOUT@
#define CONNECT_TIMEOUT @CONNECT_TIMEOUT@
-#define SHM_BUFFER_SIZE @SHM_BUFFER_SIZE@
-#define SHM_RDRB_BLOCK_SIZE @SHM_RDRB_BLOCK_SIZE@
+#define SSM_POOL_BLOCK_SIZE @SSM_POOL_BLOCK_SIZE@
#define DU_BUFF_HEADSPACE @DU_BUFF_HEADSPACE@
#define DU_BUFF_TAILSPACE @DU_BUFF_TAILSPACE@
-#cmakedefine SHM_RDRB_MULTI_BLOCK
#define IPCP_MIN_THREADS @IPCP_MIN_THREADS@
#define IPCP_ADD_THREADS @IPCP_ADD_THREADS@
@@ -47,11 +45,11 @@
#define QOS_PRIO_VOICE @IPCP_QOS_CUBE_VOICE_PRIO@
#define IPCP_SCHED_THR_MUL @IPCP_SCHED_THR_MUL@
#define PFT_SIZE @PFT_SIZE@
-#define DHT_ENROLL_SLACK @DHT_ENROLL_SLACK@
#define IPCP_UNICAST_MPL @IPCP_UNICAST_MPL@
#define CONNMGR_RCV_TIMEOUT @CONNMGR_RCV_TIMEOUT@
#cmakedefine DISABLE_CORE_LOCK
+#cmakedefine BUILD_CONTAINER
#cmakedefine IPCP_FLOW_STATS
#cmakedefine IPCP_DEBUG_LOCAL
#ifdef CONFIG_OUROBOROS_DEBUG
@@ -73,10 +71,11 @@
#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@
+#define IPCP_ETH_RD_THR @IPCP_ETH_RD_THR@
+#define IPCP_ETH_WR_THR @IPCP_ETH_WR_THR@
+#define IPCP_ETH_LO_MTU @IPCP_ETH_LO_MTU@
+#define IPCP_ETH_MGMT_FRAME_SIZE @IPCP_ETH_MGMT_FRAME_SIZE@
+#define IPCP_ETH_MPL @IPCP_ETH_MPL@
/* local */
#define IPCP_LOCAL_MPL @IPCP_LOCAL_MPL@
diff --git a/src/ipcpd/eth/CMakeLists.txt b/src/ipcpd/eth/CMakeLists.txt
index 44299a59..5a36352d 100644
--- a/src/ipcpd/eth/CMakeLists.txt
+++ b/src/ipcpd/eth/CMakeLists.txt
@@ -1,134 +1,21 @@
-get_filename_component(CURRENT_SOURCE_PARENT_DIR
- ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY)
-get_filename_component(CURRENT_BINARY_PARENT_DIR
- ${CMAKE_CURRENT_BINARY_DIR} DIRECTORY)
-
-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 100 CACHE STRING
- "Default maximum packet lifetime for the Ethernet IPCPs, in ms")
-
- set(ETH_LLC_SOURCES
- # Add source files here
- llc.c
- )
-
- set(ETH_DIX_SOURCES
- # Add source files here
- dix.c
- )
-
- set(IPCP_ETH_LLC_TARGET ipcpd-eth-llc CACHE INTERNAL "")
- set(IPCP_ETH_DIX_TARGET ipcpd-eth-dix CACHE INTERNAL "")
-
- add_executable(ipcpd-eth-llc ${ETH_LLC_SOURCES} ${IPCP_SOURCES})
- add_executable(ipcpd-eth-dix ${ETH_DIX_SOURCES} ${IPCP_SOURCES})
-
- if (HAVE_BPF AND NOT APPLE)
- target_include_directories(ipcpd-eth-llc PUBLIC ${BPF_C_INCLUDE_DIR})
- target_include_directories(ipcpd-eth-dix PUBLIC ${BPF_C_INCLUDE_DIR})
- endif ()
-
- if (HAVE_NETMAP AND NOT APPLE)
- set_target_properties(ipcpd-eth-llc PROPERTIES
- COMPILE_FLAGS "${CMAKE_C_FLAGS} -std=c99")
- set_target_properties(ipcpd-eth-dix PROPERTIES
- COMPILE_FLAGS "${CMAKE_C_FLAGS} -std=c99")
- target_include_directories(ipcpd-eth-llc PUBLIC
- ${NETMAP_C_INCLUDE_DIR})
- target_include_directories(ipcpd-eth-dix PUBLIC
- ${NETMAP_C_INCLUDE_DIR})
- endif ()
-
- target_link_libraries(ipcpd-eth-llc LINK_PUBLIC ouroboros-dev)
- target_link_libraries(ipcpd-eth-dix LINK_PUBLIC ouroboros-dev)
-
- include(AddCompileFlags)
- if (CMAKE_BUILD_TYPE MATCHES "Debug*")
- add_compile_flags(ipcpd-eth-llc -DCONFIG_OUROBOROS_DEBUG)
- add_compile_flags(ipcpd-eth-dix -DCONFIG_OUROBOROS_DEBUG)
- endif ()
-
- install(TARGETS ipcpd-eth-llc ipcpd-eth-dix RUNTIME DESTINATION
- ${CMAKE_INSTALL_SBINDIR})
-endif ()
+# Ethernet IPCPs build configuration (LLC and DIX)
+# HAVE_ETH detection is in cmake/dependencies.cmake
+
+add_executable(${IPCP_ETH_LLC_TARGET} llc.c ${IPCP_SOURCES})
+add_executable(${IPCP_ETH_DIX_TARGET} dix.c ${IPCP_SOURCES})
+
+foreach(target ${IPCP_ETH_LLC_TARGET} ${IPCP_ETH_DIX_TARGET})
+ target_include_directories(${target} PRIVATE ${IPCP_INCLUDE_DIRS})
+ if(HAVE_BPF AND NOT APPLE)
+ target_include_directories(${target} PRIVATE ${BPF_C_INCLUDE_DIR})
+ endif()
+ if(HAVE_NETMAP AND NOT APPLE)
+ target_compile_options(${target} PRIVATE -std=c99)
+ target_include_directories(${target} PRIVATE ${NETMAP_C_INCLUDE_DIR})
+ endif()
+ target_link_libraries(${target} PRIVATE ouroboros-dev)
+ ouroboros_target_debug_definitions(${target})
+endforeach()
+
+install(TARGETS ${IPCP_ETH_LLC_TARGET} ${IPCP_ETH_DIX_TARGET}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR})
diff --git a/src/ipcpd/eth/dix.c b/src/ipcpd/eth/dix.c
index 37b9896d..cf8253bd 100644
--- a/src/ipcpd/eth/dix.c
+++ b/src/ipcpd/eth/dix.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* IPC processes over Ethernet - DIX
*
diff --git a/src/ipcpd/eth/eth.c b/src/ipcpd/eth/eth.c
index 0b6a91fb..4be7775e 100644
--- a/src/ipcpd/eth/eth.c
+++ b/src/ipcpd/eth/eth.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* IPC processes over Ethernet
*
@@ -52,6 +52,7 @@
#include <ouroboros/pthread.h>
#include "ipcp.h"
+#include "np1.h"
#include "shim-data.h"
#include <signal.h>
@@ -138,7 +139,7 @@
#define NAME_QUERY_TIMEO 2000 /* ms */
#define MGMT_TIMEO 100 /* ms */
-#define MGMT_FRAME_SIZE 2048
+#define MGMT_FRAME_SIZE IPCP_ETH_MGMT_FRAME_SIZE
#define FLOW_REQ 0
#define FLOW_REPLY 1
@@ -474,6 +475,8 @@ static int eth_ipcp_alloc(const uint8_t * dst_addr,
if (buf == NULL)
return -1;
+ memset(buf, 0, len + ETH_HEADER_TOT_SIZE + data->len);
+
msg = (struct mgmt_msg *) (buf + ETH_HEADER_TOT_SIZE);
msg->code = FLOW_REQ;
#if defined(BUILD_ETH_DIX)
@@ -526,6 +529,8 @@ static int eth_ipcp_alloc_resp(uint8_t * dst_addr,
if (buf == NULL)
return -1;
+ memset(buf, 0, sizeof(*msg) + ETH_HEADER_TOT_SIZE + data->len);
+
msg = (struct mgmt_msg *) (buf + ETH_HEADER_TOT_SIZE);
msg->code = FLOW_REPLY;
@@ -664,6 +669,8 @@ static int eth_ipcp_name_query_req(const uint8_t * hash,
if (buf == NULL)
return -1;
+ memset(buf, 0, len + ETH_HEADER_TOT_SIZE);
+
msg = (struct mgmt_msg *) (buf + ETH_HEADER_TOT_SIZE);
msg->code = NAME_QUERY_REPLY;
@@ -833,7 +840,7 @@ static void * eth_ipcp_packet_reader(void * o)
#if defined(HAVE_NETMAP)
struct nm_pkthdr hdr;
#else
- struct shm_du_buff * sdb;
+ struct ssm_pk_buff * spb;
fd_set fds;
int frame_len;
#endif
@@ -865,21 +872,21 @@ static void * eth_ipcp_packet_reader(void * o)
if (select(eth_data.bpf + 1, &fds, NULL, NULL, NULL))
continue;
assert(FD_ISSET(eth_data.bpf, &fds));
- if (ipcp_sdb_reserve(&sdb, BPF_LEN))
+ if (ipcp_spb_reserve(&spb, BPF_LEN))
continue;
- buf = shm_du_buff_head(sdb);
+ buf = ssm_pk_buff_head(spb);
frame_len = read(eth_data.bpf, buf, BPF_BLEN);
#elif defined(HAVE_RAW_SOCKETS)
FD_SET(eth_data.s_fd, &fds);
if (select(eth_data.s_fd + 1, &fds, NULL, NULL, NULL) < 0)
continue;
assert(FD_ISSET(eth_data.s_fd, &fds));
- if (ipcp_sdb_reserve(&sdb, ETH_MTU))
+ if (ipcp_spb_reserve(&spb, ETH_MTU))
continue;
- buf = shm_du_buff_head_alloc(sdb, ETH_HEADER_TOT_SIZE);
+ buf = ssm_pk_buff_head_alloc(spb, ETH_HEADER_TOT_SIZE);
if (buf == NULL) {
log_dbg("Failed to allocate header.");
- ipcp_sdb_release(sdb);
+ ipcp_spb_release(spb);
continue;
}
frame_len = recv(eth_data.s_fd, buf,
@@ -887,7 +894,7 @@ static void * eth_ipcp_packet_reader(void * o)
#endif
if (frame_len <= 0) {
log_dbg("Failed to receive frame.");
- ipcp_sdb_release(sdb);
+ ipcp_spb_release(spb);
continue;
}
#endif
@@ -929,7 +936,13 @@ static void * eth_ipcp_packet_reader(void * o)
if (ssap == MGMT_SAP && dsap == MGMT_SAP) {
#endif
- ipcp_sdb_release(sdb); /* No need for the N+1 buffer. */
+ ipcp_spb_release(spb); /* No need for the N+1 buffer. */
+
+ if (length > MGMT_FRAME_SIZE) {
+ log_warn("Management frame size %u exceeds %u.",
+ length, MGMT_FRAME_SIZE);
+ goto fail_frame;
+ }
frame = malloc(sizeof(*frame));
if (frame == NULL) {
@@ -969,22 +982,22 @@ static void * eth_ipcp_packet_reader(void * o)
pthread_rwlock_unlock(&eth_data.flows_lock);
#ifndef HAVE_NETMAP
- shm_du_buff_head_release(sdb, ETH_HEADER_TOT_SIZE);
- shm_du_buff_truncate(sdb, length);
+ ssm_pk_buff_head_release(spb, ETH_HEADER_TOT_SIZE);
+ ssm_pk_buff_truncate(spb, length);
#else
- if (ipcp_sdb_reserve(&sdb, length))
+ if (ipcp_spb_reserve(&spb, length))
continue;
- buf = shm_du_buff_head(sdb);
+ buf = ssm_pk_buff_head(spb);
memcpy(buf, &e_frame->payload, length);
#endif
- if (np1_flow_write(fd, sdb) < 0)
- ipcp_sdb_release(sdb);
+ if (np1_flow_write(fd, spb, NP1_GET_POOL(fd)) < 0)
+ ipcp_spb_release(spb);
continue;
fail_frame:
#ifndef HAVE_NETMAP
- ipcp_sdb_release(sdb);
+ ipcp_spb_release(spb);
#endif
}
}
@@ -1000,7 +1013,7 @@ static void cleanup_writer(void * o)
static void * eth_ipcp_packet_writer(void * o)
{
int fd;
- struct shm_du_buff * sdb;
+ struct ssm_pk_buff * spb;
size_t len;
#if defined(BUILD_ETH_DIX)
uint16_t deid;
@@ -1028,17 +1041,17 @@ static void * eth_ipcp_packet_writer(void * o)
if (fqueue_type(fq) != FLOW_PKT)
continue;
- if (np1_flow_read(fd, &sdb)) {
+ if (np1_flow_read(fd, &spb, NP1_GET_POOL(fd))) {
log_dbg("Bad read from fd %d.", fd);
continue;
}
- len = shm_du_buff_len(sdb);
+ len = ssm_pk_buff_len(spb);
- if (shm_du_buff_head_alloc(sdb, ETH_HEADER_TOT_SIZE)
+ if (ssm_pk_buff_head_alloc(spb, ETH_HEADER_TOT_SIZE)
== NULL) {
log_dbg("Failed to allocate header.");
- ipcp_sdb_release(sdb);
+ ipcp_spb_release(spb);
continue;
}
@@ -1061,10 +1074,10 @@ static void * eth_ipcp_packet_writer(void * o)
#elif defined(BUILD_ETH_LLC)
dsap, ssap,
#endif
- shm_du_buff_head(sdb),
+ ssm_pk_buff_head(spb),
len))
log_dbg("Failed to send frame.");
- ipcp_sdb_release(sdb);
+ ipcp_spb_release(spb);
}
}
@@ -1330,14 +1343,7 @@ static int eth_set_mtu(struct ifreq * ifr)
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);
return 0;
@@ -1491,9 +1497,6 @@ static int eth_ipcp_bootstrap(struct ipcp_config * conf)
char ifn[IFNAMSIZ];
#endif /* HAVE_NETMAP */
-#ifndef SHM_RDRB_MULTI_BLOCK
- size_t maxsz;
-#endif
assert(conf);
assert(conf->type == THIS_TYPE);
@@ -1650,6 +1653,8 @@ static int eth_ipcp_query(const uint8_t * hash)
if (buf == NULL)
return -1;
+ memset(buf, 0, len + ETH_HEADER_TOT_SIZE);
+
msg = (struct mgmt_msg *) (buf + ETH_HEADER_TOT_SIZE);
msg->code = NAME_QUERY_REQ;
diff --git a/src/ipcpd/eth/llc.c b/src/ipcpd/eth/llc.c
index c900dcab..a772e86e 100644
--- a/src/ipcpd/eth/llc.c
+++ b/src/ipcpd/eth/llc.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* IPC processes over Ethernet - LLC
*
diff --git a/src/ipcpd/ipcp.c b/src/ipcpd/ipcp.c
index ebb9b1c5..5ad2401f 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
*
@@ -52,6 +52,7 @@
#include <ouroboros/utils.h>
#include "ipcp.h"
+#include "np1.h"
#include <signal.h>
#include <string.h>
@@ -131,6 +132,8 @@ struct {
pthread_t acceptor;
} ipcpd;
+struct np1_state np1;
+
struct cmd {
struct list_head next;
@@ -205,7 +208,7 @@ 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;
@@ -633,9 +636,11 @@ 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));
@@ -662,6 +667,17 @@ static void do_flow_alloc(pid_t pid,
return;
}
+ if (uid != 0) {
+ pool = ssm_pool_open(uid);
+ if (pool == NULL) {
+ log_err("Failed to open PUP for uid %d.", uid);
+ ret_msg->result = -ENOMEM;
+ return;
+ }
+ }
+
+ NP1_SET_POOL(fd, pool);
+
ret_msg->result = ipcpd.ops->ipcp_flow_alloc(fd, dst, qs, data);
log_info("Finished allocating flow %d to " HASH_FMT32 ".",
@@ -672,9 +688,11 @@ static void do_flow_alloc(pid_t pid,
static void do_flow_join(pid_t pid,
int flow_id,
const uint8_t * dst,
+ uid_t uid,
ipcp_msg_t * ret_msg)
{
- int fd;
+ int fd;
+ struct ssm_pool * pool = NULL;
log_info("Joining layer " HASH_FMT32 ".", HASH_VAL32(dst));
@@ -699,6 +717,17 @@ static void do_flow_join(pid_t pid,
return;
}
+ if (uid != 0) {
+ pool = ssm_pool_open(uid);
+ if (pool == NULL) {
+ log_err("Failed to open PUP for uid %d.", uid);
+ ret_msg->result = -ENOMEM;
+ return;
+ }
+ }
+
+ NP1_SET_POOL(fd, pool);
+
ret_msg->result = ipcpd.ops->ipcp_flow_join(fd, dst);
log_info("Finished joining layer " HASH_FMT32 ".", HASH_VAL32(dst));
@@ -706,10 +735,12 @@ static void do_flow_join(pid_t pid,
static void do_flow_alloc_resp(int resp,
int flow_id,
+ uid_t uid,
const buffer_t * data,
ipcp_msg_t * ret_msg)
{
- int fd = -1;
+ int fd = -1;
+ struct ssm_pool * pool = NULL;
log_info("Responding %d to alloc on flow_id %d.", resp, flow_id);
@@ -737,6 +768,17 @@ static void do_flow_alloc_resp(int resp,
return;
}
+ if (uid != 0) {
+ pool = ssm_pool_open(uid);
+ if (pool == NULL) {
+ log_err("Failed to open PUP for uid %d.", uid);
+ ret_msg->result = -ENOMEM;
+ return;
+ }
+ }
+
+ NP1_SET_POOL(fd, pool);
+
ret_msg->result = ipcpd.ops->ipcp_flow_alloc_resp(fd, resp, data);
log_info("Finished responding %d to allocation request.",
@@ -857,12 +899,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());
do_flow_join(msg->pid, msg->flow_id,
- msg->hash.data, &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
@@ -870,7 +912,7 @@ static void * mainloop(void * o)
data.len = msg->pk.len;
data.data = msg->pk.data;
do_flow_alloc_resp(msg->response, msg->flow_id,
- &data, &ret_msg);
+ msg->uid, &data, &ret_msg);
break;
case IPCP_MSG_CODE__IPCP_FLOW_DEALLOC:
do_flow_dealloc(msg->flow_id, msg->timeo_sec, &ret_msg);
@@ -1035,6 +1077,8 @@ int ipcp_init(int argc,
ipcpd.alloc_id = -1;
+ memset(&np1, 0, sizeof(np1));
+
pthread_condattr_destroy(&cattr);
ipcp_set_state(IPCP_INIT);
diff --git a/src/ipcpd/ipcp.h b/src/ipcpd/ipcp.h
index e8c31a32..26a780a3 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
*
diff --git a/src/ipcpd/local/CMakeLists.txt b/src/ipcpd/local/CMakeLists.txt
index f661e9ae..91f300a3 100644
--- a/src/ipcpd/local/CMakeLists.txt
+++ b/src/ipcpd/local/CMakeLists.txt
@@ -1,36 +1,17 @@
-get_filename_component(CURRENT_SOURCE_PARENT_DIR
- ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY)
-get_filename_component(CURRENT_BINARY_PARENT_DIR
- ${CMAKE_CURRENT_BINARY_DIR} DIRECTORY)
+# Local IPCP build configuration
-include_directories(${CMAKE_CURRENT_SOURCE_DIR})
-include_directories(${CMAKE_CURRENT_BINARY_DIR})
+add_executable(${IPCP_LOCAL_TARGET}
+ main.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 100 CACHE STRING
- "Default maximum packet lifetime for the Ethernet IPCPs, in ms")
+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
-# if(BUILD_TESTS)
-# add_subdirectory(tests)
-# endif ()
+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 ffa6dc5a..377a7df3 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
*
@@ -40,6 +40,7 @@
#include <ouroboros/local-dev.h>
#include "ipcp.h"
+#include "np1.h"
#include "shim-data.h"
#include <string.h>
@@ -103,34 +104,41 @@ static void local_data_fini(void){
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));
}
}
diff --git a/src/ipcpd/np1.h b/src/ipcpd/np1.h
new file mode 100644
index 00000000..b7792cb9
--- /dev/null
+++ b/src/ipcpd/np1.h
@@ -0,0 +1,41 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * N+1 flow pool tracking for IPCPs
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#ifndef OUROBOROS_IPCPD_NP1_H
+#define OUROBOROS_IPCPD_NP1_H
+
+#include "config.h"
+
+#include <ouroboros/ssm_pool.h>
+
+#define NP1_LOAD(ptr) (__atomic_load_n((ptr), __ATOMIC_ACQUIRE))
+#define NP1_STORE(ptr, v) (__atomic_store_n((ptr), (v), __ATOMIC_RELEASE))
+#define NP1_GET_POOL(fd) (NP1_LOAD(&np1.pool[(fd)]))
+#define NP1_SET_POOL(fd, p) (NP1_STORE(&np1.pool[(fd)], (p)))
+
+struct np1_state {
+ struct ssm_pool * pool[SYS_MAX_FLOWS];
+};
+
+extern struct np1_state np1;
+
+#endif /* OUROBOROS_IPCPD_NP1_H */
diff --git a/src/ipcpd/shim-data.c b/src/ipcpd/shim-data.c
index 8801213a..90a676da 100644
--- a/src/ipcpd/shim-data.c
+++ b/src/ipcpd/shim-data.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* IPC process utilities
*
diff --git a/src/ipcpd/shim-data.h b/src/ipcpd/shim-data.h
index ea4ce413..fbadb4d4 100644
--- a/src/ipcpd/shim-data.h
+++ b/src/ipcpd/shim-data.h
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Utitilies for building IPC processes
*
diff --git a/src/ipcpd/udp/CMakeLists.txt b/src/ipcpd/udp/CMakeLists.txt
index 27e32094..a98f0919 100644
--- a/src/ipcpd/udp/CMakeLists.txt
+++ b/src/ipcpd/udp/CMakeLists.txt
@@ -1,81 +1,14 @@
-get_filename_component(CURRENT_SOURCE_PARENT_DIR
- ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY)
-get_filename_component(CURRENT_BINARY_PARENT_DIR
- ${CMAKE_CURRENT_BINARY_DIR} DIRECTORY)
+# UDP IPCPs build configuration (UDP4 and UDP6)
+# DDNS detection is in cmake/dependencies/udp/ddns.cmake
-include_directories(${CMAKE_CURRENT_SOURCE_DIR})
-include_directories(${CMAKE_CURRENT_BINARY_DIR})
+add_executable(${IPCP_UDP4_TARGET} udp4.c ${IPCP_SOURCES})
+add_executable(${IPCP_UDP6_TARGET} udp6.c ${IPCP_SOURCES})
-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_UDP4_TARGET ipcpd-udp4 CACHE INTERNAL "")
-set(IPCP_UDP6_TARGET ipcpd-udp6 CACHE INTERNAL "")
-
-set(UDP4_SOURCES
- # Add source files here
- udp4.c
-)
-
-set(UDP6_SOURCES
- # Add source files here
- udp6.c
-)
-
-add_executable(ipcpd-udp4 ${UDP4_SOURCES} ${IPCP_SOURCES})
-target_link_libraries(ipcpd-udp4 LINK_PUBLIC ouroboros-dev)
-
-add_executable(ipcpd-udp6 ${UDP6_SOURCES} ${IPCP_SOURCES})
-target_link_libraries(ipcpd-udp6 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 IPCPs")
-set(IPCP_UDP_WR_THR 3 CACHE STRING
- "Number of writer threads in UDP IPCPs")
-set(IPCP_UDP_MPL 5000 CACHE STRING
- "Default maximum packet lifetime for the UDP IPCPs, in ms")
-
-include(AddCompileFlags)
-if (CMAKE_BUILD_TYPE MATCHES "Debug*")
- add_compile_flags(ipcpd-udp4 -DCONFIG_OUROBOROS_DEBUG)
- add_compile_flags(ipcpd-udp6 -DCONFIG_OUROBOROS_DEBUG)
-endif ()
-
-install(TARGETS ipcpd-udp4 RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR})
-install(TARGETS ipcpd-udp6 RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR})
+foreach(target ${IPCP_UDP4_TARGET} ${IPCP_UDP6_TARGET})
+ target_include_directories(${target} PRIVATE ${IPCP_INCLUDE_DIRS})
+ target_link_libraries(${target} PRIVATE ouroboros-dev)
+ ouroboros_target_debug_definitions(${target})
+endforeach()
+install(TARGETS ${IPCP_UDP4_TARGET} ${IPCP_UDP6_TARGET}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR})
diff --git a/src/ipcpd/udp/udp.c b/src/ipcpd/udp/udp.c
index be8069a4..452bbc1a 100644
--- a/src/ipcpd/udp/udp.c
+++ b/src/ipcpd/udp/udp.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* IPC process over UDP
*
@@ -34,6 +34,7 @@
#include <ouroboros/pthread.h>
#include "ipcp.h"
+#include "np1.h"
#include "shim-data.h"
#include <string.h>
@@ -208,6 +209,8 @@ static int udp_ipcp_port_alloc(const struct __SOCKADDR * r_saddr,
if (buf == NULL)
return -1;
+ memset(buf, 0, len + data->len);
+
msg = (struct mgmt_msg *) buf;
msg->eid = hton32(MGMT_EID);
msg->code = FLOW_REQ;
@@ -251,6 +254,8 @@ static int udp_ipcp_port_alloc_resp(const struct __SOCKADDR * r_saddr,
if (msg == NULL)
return -1;
+ memset(msg, 0, sizeof(*msg) + data->len);
+
msg->eid = hton32(MGMT_EID);
msg->code = FLOW_REPLY;
msg->s_eid = hton32(s_eid);
@@ -439,7 +444,7 @@ static void * udp_ipcp_packet_reader(void * o)
struct mgmt_frame * frame;
struct __SOCKADDR r_saddr;
socklen_t len;
- struct shm_du_buff * sdb;
+ struct ssm_pk_buff * spb;
uint8_t * head;
len = sizeof(r_saddr);
@@ -483,13 +488,13 @@ static void * udp_ipcp_packet_reader(void * o)
n-= sizeof(eid);
- if (ipcp_sdb_reserve(&sdb, n))
+ if (ipcp_spb_reserve(&spb, n))
continue;
- head = shm_du_buff_head(sdb);
+ head = ssm_pk_buff_head(spb);
memcpy(head, data, n);
- if (np1_flow_write(eid, sdb) < 0)
- ipcp_sdb_release(sdb);
+ if (np1_flow_write(eid, spb, NP1_GET_POOL(eid)) < 0)
+ ipcp_spb_release(spb);
}
return (void *) 0;
@@ -500,9 +505,9 @@ static void cleanup_fqueue(void * fq)
fqueue_destroy((fqueue_t *) fq);
}
-static void cleanup_sdb(void * sdb)
+static void cleanup_spb(void * spb)
{
- ipcp_sdb_release((struct shm_du_buff *) sdb);
+ ipcp_spb_release((struct ssm_pk_buff *) spb);
}
static void * udp_ipcp_packet_writer(void * o)
@@ -525,29 +530,29 @@ static void * udp_ipcp_packet_writer(void * o)
int fd;
fevent(udp_data.np1_flows, fq, NULL);
while ((fd = fqueue_next(fq)) >= 0) {
- struct shm_du_buff * sdb;
+ struct ssm_pk_buff * spb;
uint8_t * buf;
uint16_t len;
if (fqueue_type(fq) != FLOW_PKT)
continue;
- if (np1_flow_read(fd, &sdb)) {
+ if (np1_flow_read(fd, &spb, NP1_GET_POOL(fd))) {
log_dbg("Bad read from fd %d.", fd);
continue;
}
- len = shm_du_buff_len(sdb);
+ len = ssm_pk_buff_len(spb);
if (len > IPCP_UDP_MAX_PACKET_SIZE) {
log_dbg("Packet length exceeds MTU.");
- ipcp_sdb_release(sdb);
+ ipcp_spb_release(spb);
continue;
}
- buf = shm_du_buff_head_alloc(sdb, OUR_HEADER_LEN);
+ buf = ssm_pk_buff_head_alloc(spb, OUR_HEADER_LEN);
if (buf == NULL) {
log_dbg("Failed to allocate header.");
- ipcp_sdb_release(sdb);
+ ipcp_spb_release(spb);
continue;
}
@@ -560,7 +565,7 @@ static void * udp_ipcp_packet_writer(void * o)
memcpy(buf, &eid, sizeof(eid));
- pthread_cleanup_push(cleanup_sdb, sdb);
+ pthread_cleanup_push(cleanup_spb, spb);
if (sendto(udp_data.s_fd, buf, len + OUR_HEADER_LEN,
SENDTO_FLAGS,
diff --git a/src/ipcpd/udp/udp4.c b/src/ipcpd/udp/udp4.c
index 07d5f818..ff57bc09 100644
--- a/src/ipcpd/udp/udp4.c
+++ b/src/ipcpd/udp/udp4.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* IPC process over UDP/IPv4
*
diff --git a/src/ipcpd/udp/udp6.c b/src/ipcpd/udp/udp6.c
index b7924a3f..2ceb95f0 100644
--- a/src/ipcpd/udp/udp6.c
+++ b/src/ipcpd/udp/udp6.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* IPC process over UDP/IPv6
*
diff --git a/src/ipcpd/unicast/CMakeLists.txt b/src/ipcpd/unicast/CMakeLists.txt
index a9155353..d3388112 100644
--- a/src/ipcpd/unicast/CMakeLists.txt
+++ b/src/ipcpd/unicast/CMakeLists.txt
@@ -1,42 +1,9 @@
-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 10000 CACHE STRING
- "Default maximum packet lifetime for the unicast IPCP, in ms")
-set(DEBUG_PROTO_DHT FALSE CACHE BOOL
- "Add DHT protocol message output to debug logging")
-set(DEBUG_PROTO_LS FALSE CACHE BOOL
- "Add link state protocol message output to debug logging")
-
-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(IPCP_UNICAST_SOURCE_FILES
- # Add source files here
+set(UNICAST_SOURCES
addr-auth.c
ca.c
connmgr.c
@@ -47,7 +14,6 @@ set(IPCP_UNICAST_SOURCE_FILES
pff.c
routing.c
psched.c
- # Add policies last
addr-auth/flat.c
ca/mb-ecn.c
ca/nop.c
@@ -58,21 +24,26 @@ set(IPCP_UNICAST_SOURCE_FILES
pff/pft.c
routing/link-state.c
routing/graph.c
- )
+)
+
+add_executable(${IPCP_UNICAST_TARGET}
+ ${UNICAST_SOURCES}
+ ${IPCP_SOURCES}
+ ${COMMON_SOURCES}
+ ${DHT_PROTO_SRCS}
+)
-add_executable(ipcpd-unicast ${IPCP_UNICAST_SOURCE_FILES} ${IPCP_SOURCES} ${COMMON_SOURCES}
- ${DHT_PROTO_SRCS} ${LAYER_CONFIG_PROTO_SRCS})
-target_link_libraries(ipcpd-unicast LINK_PUBLIC ouroboros-dev)
+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)
-include(AddCompileFlags)
-if (CMAKE_BUILD_TYPE MATCHES "Debug*")
- add_compile_flags(ipcpd-unicast -DCONFIG_OUROBOROS_DEBUG)
-endif ()
+ouroboros_target_debug_definitions(${IPCP_UNICAST_TARGET})
-install(TARGETS ipcpd-unicast RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR})
+install(TARGETS ${IPCP_UNICAST_TARGET} RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR})
if(BUILD_TESTS)
+ add_subdirectory(dir/tests)
add_subdirectory(pff/tests)
add_subdirectory(routing/tests)
- add_subdirectory(dir/tests)
-endif ()
+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 0d2cd4c0..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
*
diff --git a/src/ipcpd/unicast/addr-auth/flat.c b/src/ipcpd/unicast/addr-auth/flat.c
index 34ca1cef..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
*
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 1fcc9bb2..a1751672 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
*
diff --git a/src/ipcpd/unicast/ca.h b/src/ipcpd/unicast/ca.h
index ea803e17..47ea15a0 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
*
diff --git a/src/ipcpd/unicast/ca/mb-ecn.c b/src/ipcpd/unicast/ca/mb-ecn.c
index d9a204b0..b310c4fc 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
*
diff --git a/src/ipcpd/unicast/ca/mb-ecn.h b/src/ipcpd/unicast/ca/mb-ecn.h
index 9a2c8b49..1be27764 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
*
diff --git a/src/ipcpd/unicast/ca/nop.c b/src/ipcpd/unicast/ca/nop.c
index 617fc15b..e5cacf66 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
*
diff --git a/src/ipcpd/unicast/ca/nop.h b/src/ipcpd/unicast/ca/nop.h
index 248b198d..8b892e61 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
*
diff --git a/src/ipcpd/unicast/ca/ops.h b/src/ipcpd/unicast/ca/ops.h
index 3a7b7248..6d2ddf1d 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
*
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/connmgr.c b/src/ipcpd/unicast/connmgr.c
index 07568fb5..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
*
diff --git a/src/ipcpd/unicast/dir.c b/src/ipcpd/unicast/dir.c
index 2b305626..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
*
diff --git a/src/ipcpd/unicast/dir.h b/src/ipcpd/unicast/dir.h
index dbfde19f..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
*
diff --git a/src/ipcpd/unicast/dir/dht.c b/src/ipcpd/unicast/dir/dht.c
index 6b06def9..8eeea800 100644
--- a/src/ipcpd/unicast/dir/dht.c
+++ b/src/ipcpd/unicast/dir/dht.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Distributed Hash Table based on Kademlia
*
@@ -148,15 +148,8 @@ struct dht_entry {
uint8_t * key;
- struct {
- struct list_head list;
- size_t len;
- } vals; /* We don't own these, only replicate */
-
- struct {
- struct list_head list;
- size_t len;
- } lvals; /* We own these, must be republished */
+ struct llist vals; /* We don't own these, only replicate */
+ struct llist lvals; /* We own these, must be republished */
};
struct contact {
@@ -183,38 +176,24 @@ struct peer_entry {
struct dht_req {
struct list_head next;
- uint8_t * key;
- time_t t_exp;
+ uint8_t * key;
+ time_t t_exp;
- struct {
- struct list_head list;
- size_t len;
- } peers;
-
- struct {
- struct list_head list;
- size_t len;
- } cache;
+ struct llist peers;
+ struct llist cache;
};
struct bucket {
- struct {
- struct list_head list;
- size_t len;
- } contacts;
+ struct llist contacts;
+ struct llist alts;
- struct {
- struct list_head list;
- size_t len;
- } alts;
+ time_t t_refr;
- time_t t_refr;
+ size_t depth;
+ uint8_t mask;
- size_t depth;
- uint8_t mask;
-
- struct bucket * parent;
- struct bucket * children[1L << DHT_BETA];
+ struct bucket * parent;
+ struct bucket * children[1L << DHT_BETA];
};
struct cmd {
@@ -236,8 +215,8 @@ struct {
struct { /* Kademlia parameters */
uint32_t alpha; /* Number of concurrent requests */
size_t k; /* Number of replicas to store */
- time_t t_expire; /* Expiry time for values (s) */
- time_t t_refresh; /* Refresh time for contacts (s) */
+ time_t t_exp; /* Expiry time for values (s) */
+ time_t t_refr; /* Refresh time for contacts (s) */
time_t t_repl; /* Replication time for values (s) */
};
@@ -261,8 +240,7 @@ struct {
} contacts;
struct {
- struct list_head list;
- size_t len;
+ struct llist ll;
size_t vals;
size_t lvals;
} kv;
@@ -271,10 +249,9 @@ struct {
} db;
struct {
- struct list_head list;
- size_t len;
- pthread_cond_t cond;
- pthread_mutex_t mtx;
+ struct llist ll;
+ pthread_cond_t cond;
+ pthread_mutex_t mtx;
} reqs;
struct {
@@ -321,7 +298,7 @@ static int dht_rib_statfile(char * buf,
pthread_rwlock_rdlock(&dht.db.lock);
- keys = dht.db.kv.len;
+ keys = dht.db.kv.ll.len;
lvals = dht.db.kv.lvals;
vals = dht.db.kv.vals;
@@ -335,7 +312,7 @@ static int dht_rib_statfile(char * buf,
tmstr,
ADDR_VAL32(&dht.addr),
dht.alpha, dht.k,
- dht.t_expire, dht.t_refresh, dht.t_repl,
+ dht.t_exp, dht.t_refr, dht.t_repl,
keys, vals, lvals);
return strlen(buf);
@@ -350,14 +327,14 @@ static size_t dht_db_file_len(void)
pthread_rwlock_rdlock(&dht.db.lock);
- if (dht.db.kv.len == 0) {
+ if (llist_is_empty(&dht.db.kv.ll)) {
pthread_rwlock_unlock(&dht.db.lock);
sz += 14; /* No entries */
return sz;
}
sz += 39 * 3 + 1; /* tally + extra newline */
- sz += dht.db.kv.len * (25 + 19 + 23 + 1);
+ sz += dht.db.kv.ll.len * (25 + 19 + 23 + 1);
vals = dht.db.kv.vals + dht.db.kv.lvals;
@@ -382,7 +359,7 @@ static int dht_rib_dbfile(char * buf,
pthread_rwlock_rdlock(&dht.db.lock);
- if (dht.db.kv.len == 0) {
+ if (llist_is_empty(&dht.db.kv.ll)) {
i += snprintf(buf, len, " No entries.\n");
pthread_rwlock_unlock(&dht.db.lock);
return i;
@@ -393,9 +370,9 @@ static int dht_rib_dbfile(char * buf,
"Number of keys: %10zu\n"
"Number of local values: %10zu\n"
"Number of non-local values: %10zu\n\n",
- dht.db.kv.len, dht.db.kv.vals, dht.db.kv.lvals);
+ dht.db.kv.ll.len, dht.db.kv.vals, dht.db.kv.lvals);
- list_for_each(p, &dht.db.kv.list) {
+ llist_for_each(p, &dht.db.kv.ll) {
struct dht_entry * e = list_entry(p, struct dht_entry, next);
struct list_head * h;
@@ -403,7 +380,7 @@ static int dht_rib_dbfile(char * buf,
KEY_VAL(e->key));
i += snprintf(buf + i, len - i, " Local entries:\n");
- list_for_each(h, &e->vals.list) {
+ llist_for_each(h, &e->vals) {
struct val_entry * v;
v = list_entry(h, struct val_entry, next);
@@ -416,7 +393,7 @@ static int dht_rib_dbfile(char * buf,
i += snprintf(buf + i, len - i,
" " VAL_FMT
- ", t_replicated=%.*s, t_expire=%.*s\n",
+ ", t_replicated=%.*s, t_exp=%.*s\n",
VAL_VAL(v->val),
RIB_TM_STRLEN, tmstr,
RIB_TM_STRLEN, exstr);
@@ -426,7 +403,7 @@ static int dht_rib_dbfile(char * buf,
i += snprintf(buf + i, len - i, " Non-local entries:\n");
- list_for_each(h, &e->lvals.list) {
+ llist_for_each(h, &e->lvals) {
struct val_entry * v;
v= list_entry(h, struct val_entry, next);
@@ -439,7 +416,7 @@ static int dht_rib_dbfile(char * buf,
i += snprintf(buf + i, len - i,
" " VAL_FMT
- ", t_replicated=%.*s, t_expire=%.*s\n",
+ ", t_replicated=%.*s, t_exp=%.*s\n",
VAL_VAL(v->val),
RIB_TM_STRLEN, tmstr,
RIB_TM_STRLEN, exstr);
@@ -694,11 +671,8 @@ static struct dht_entry * dht_entry_create(const uint8_t * key)
goto fail_entry;
list_head_init(&e->next);
- list_head_init(&e->vals.list);
- list_head_init(&e->lvals.list);
-
- e->vals.len = 0;
- e->lvals.len = 0;
+ llist_init(&e->vals);
+ llist_init(&e->lvals);
e->key = dht_dup_key(key);
if (e->key == NULL)
@@ -718,25 +692,23 @@ static void dht_entry_destroy(struct dht_entry * e)
assert(e != NULL);
- list_for_each_safe(p, h, &e->vals.list) {
+ llist_for_each_safe(p, h, &e->vals) {
struct val_entry * v = list_entry(p, struct val_entry, next);
- list_del(&v->next);
+ llist_del(&v->next, &e->vals);
val_entry_destroy(v);
- --e->vals.len;
--dht.db.kv.vals;
}
- list_for_each_safe(p, h, &e->lvals.list) {
+ llist_for_each_safe(p, h, &e->lvals) {
struct val_entry * v = list_entry(p, struct val_entry, next);
- list_del(&v->next);
+ llist_del(&v->next, &e->lvals);
val_entry_destroy(v);
- --e->lvals.len;
--dht.db.kv.lvals;
}
free(e->key);
- assert(e->vals.len == 0 && e->lvals.len == 0);
+ assert(llist_is_empty(&e->vals) && llist_is_empty(&e->lvals));
free(e);
}
@@ -750,7 +722,7 @@ static struct val_entry * dht_entry_get_lval(const struct dht_entry * e,
assert(val.data != NULL);
assert(val.len > 0);
- list_for_each(p, &e->lvals.list) {
+ 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;
@@ -768,7 +740,7 @@ static struct val_entry * dht_entry_get_val(const struct dht_entry * e,
assert(val.data != NULL);
assert(val.len > 0);
- list_for_each(p, &e->vals.list) {
+ 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;
@@ -805,8 +777,7 @@ static int dht_entry_update_val(struct dht_entry * e,
if (v == NULL)
return -ENOMEM;
- list_add_tail(&v->next, &e->vals.list);
- ++e->vals.len;
+ llist_add_tail(&v->next, &e->vals);
++dht.db.kv.vals;
return 0;
@@ -833,12 +804,11 @@ static int dht_entry_update_lval(struct dht_entry * e,
v = dht_entry_get_lval(e, val);
if (v == NULL) {
log_dbg(KV_FMT " Adding lval.", KV_VAL(e->key, val));
- v = val_entry_create(val, now.tv_sec + dht.t_expire);
+ v = val_entry_create(val, now.tv_sec + dht.t_exp);
if (v == NULL)
return -ENOMEM;
- list_add_tail(&v->next, &e->lvals.list);
- ++e->lvals.len;
+ llist_add_tail(&v->next, &e->lvals);
++dht.db.kv.lvals;
return 0;
@@ -862,9 +832,8 @@ static int dht_entry_remove_lval(struct dht_entry * e,
log_dbg(KV_FMT " Removing lval.", KV_VAL(e->key, val));
- list_del(&v->next);
+ llist_del(&v->next, &e->lvals);
val_entry_destroy(v);
- --e->lvals.len;
--dht.db.kv.lvals;
return 0;
@@ -881,15 +850,14 @@ static void dht_entry_remove_expired_vals(struct dht_entry * e)
clock_gettime(CLOCK_REALTIME_COARSE, &now);
- list_for_each_safe(p, h, &e->vals.list) {
+ 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;
log_dbg(KV_FMT " Value expired." , KV_VAL(e->key, v->val));
- list_del(&v->next);
+ llist_del(&v->next, &e->vals);
val_entry_destroy(v);
- --e->vals.len;
--dht.db.kv.vals;
}
}
@@ -900,7 +868,7 @@ static struct dht_entry * __dht_kv_find_entry(const uint8_t * key)
assert(key != NULL);
- list_for_each(p, &dht.db.kv.list) {
+ 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;
@@ -919,16 +887,15 @@ static void dht_kv_remove_expired_entries(void)
pthread_rwlock_wrlock(&dht.db.lock);
- list_for_each_safe(p, h, &dht.db.kv.list) {
+ 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;
log_dbg(KEY_FMT " Entry removed. ", KEY_VAL(e->key));
- list_del(&e->next);
+ llist_del(&e->next, &dht.db.kv.ll);
dht_entry_destroy(e);
- --dht.db.kv.len;
}
pthread_rwlock_unlock(&dht.db.lock);
@@ -987,15 +954,13 @@ static struct dht_req * dht_req_create(const uint8_t * key)
req->t_exp = now.tv_sec + DHT_T_RESP;
- list_head_init(&req->peers.list);
- req->peers.len = 0;
+ llist_init(&req->peers);
req->key = dht_dup_key(key);
if (req->key == NULL)
goto fail_dup_key;
- list_head_init(&req->cache.list);
- req->cache.len = 0;
+ llist_init(&req->cache);
return req;
@@ -1013,34 +978,32 @@ static void dht_req_destroy(struct dht_req * req)
assert(req);
assert(req->key);
- list_for_each_safe(p, h, &req->peers.list) {
+ llist_for_each_safe(p, h, &req->peers) {
struct peer_entry * e = list_entry(p, struct peer_entry, next);
- list_del(&e->next);
+ llist_del(&e->next, &req->peers);
free(e->id);
free(e);
- --req->peers.len;
}
- list_for_each_safe(p, h, &req->cache.list) {
+ llist_for_each_safe(p, h, &req->cache) {
struct val_entry * e = list_entry(p, struct val_entry, next);
- list_del(&e->next);
+ llist_del(&e->next, &req->cache);
val_entry_destroy(e);
- --req->cache.len;
}
free(req->key);
- assert(req->peers.len == 0);
+ assert(llist_is_empty(&req->peers));
free(req);
}
-static struct peer_entry * dht_req_get_peer(struct dht_req * req,
- struct peer_entry * e)
+static struct peer_entry * dht_req_get_peer(struct dht_req * req,
+ const struct peer_entry * e)
{
struct list_head * p;
- list_for_each(p, &req->peers.list) {
+ llist_for_each(p, &req->peers) {
struct peer_entry * x = list_entry(p, struct peer_entry, next);
if (x->addr == e->addr)
return x;
@@ -1050,8 +1013,8 @@ static struct peer_entry * dht_req_get_peer(struct dht_req * req,
}
#define IS_MAGIC(peer) ((peer)->cookie == dht.magic)
-void dht_req_add_peer(struct dht_req * req,
- struct peer_entry * e)
+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 */
@@ -1063,16 +1026,17 @@ void dht_req_add_peer(struct dht_req * req,
/*
* Dedupe messages to the same peer, unless
- * 1) The previous request was FIND_NODE and now it's FIND_VALUE
- * 2) We urgently need contacts from emergency peer (magic cookie)
+ * 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))
- goto skip;
+ return -1;
/* Find how this contact ranks in distance to the key */
- list_for_each(p, &req->peers.list) {
- struct peer_entry * y = list_entry(p, struct peer_entry, next);
+ 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;
@@ -1080,36 +1044,32 @@ void dht_req_add_peer(struct dht_req * req,
break;
}
- /* Add a new peer to this request if we need to */
- if (pos < dht.alpha || !IS_MAGIC(e)) {
- x = malloc(sizeof(*x));
- if (x == NULL) {
- log_err("Failed to malloc peer entry.");
- goto skip;
- }
+ if (pos >= dht.alpha && IS_MAGIC(e))
+ return -1;
- x->cookie = e->cookie;
- x->addr = e->addr;
- x->code = e->code;
- x->t_sent = e->t_sent;
- x->id = dht_dup_key(e->id);
- if (x->id == NULL) {
- log_err("Failed to dup peer ID.");
- free(x);
- goto skip;
- }
+ x = malloc(sizeof(*x));
+ if (x == NULL) {
+ log_err("Failed to malloc peer entry.");
+ return -1;
+ }
- if (IS_MAGIC(e))
- list_add(&x->next, p);
- else
- list_add_tail(&x->next, p);
- ++req->peers.len;
- return;
+ x->cookie = e->cookie;
+ x->addr = e->addr;
+ x->code = e->code;
+ x->t_sent = e->t_sent;
+ x->id = dht_dup_key(e->id);
+ if (x->id == NULL) {
+ log_err("Failed to dup peer ID.");
+ free(x);
+ return -1;
}
- skip:
- list_del(&e->next);
- free(e->id);
- free(e);
+
+ if (IS_MAGIC(e))
+ llist_add_at(&x->next, p, &req->peers);
+ else
+ llist_add_tail_at(&x->next, p, &req->peers);
+
+ return 0;
}
static size_t dht_req_add_peers(struct dht_req * req,
@@ -1123,8 +1083,13 @@ static size_t dht_req_add_peers(struct dht_req * req,
assert(pl != NULL);
list_for_each_safe(p, h, pl) {
- struct peer_entry * e = list_entry(p, struct peer_entry, next);
- dht_req_add_peer(req, e);
+ 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;
@@ -1137,7 +1102,7 @@ static bool dht_req_has_peer(struct dht_req * req,
assert(req != NULL);
- list_for_each(p, &req->peers.list) {
+ llist_for_each(p, &req->peers) {
struct peer_entry * e = list_entry(p, struct peer_entry, next);
if (e->cookie == cookie)
return true;
@@ -1209,7 +1174,7 @@ static struct dht_req * __dht_kv_req_get_req(const uint8_t * key)
{
struct list_head * p;
- list_for_each(p, &dht.reqs.list) {
+ 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;
@@ -1228,7 +1193,7 @@ static struct dht_req * __dht_kv_get_req_cache(const uint8_t * key)
if (req == NULL)
return NULL;
- if (req->cache.len == 0)
+ if (llist_is_empty(&req->cache))
return NULL;
return req;
@@ -1244,8 +1209,7 @@ static void __dht_kv_req_remove(const uint8_t * key)
if (req == NULL)
return;
- list_del(&req->next);
- --dht.reqs.len;
+ llist_del(&req->next, &dht.reqs.ll);
dht_req_destroy(req);
}
@@ -1301,9 +1265,9 @@ static int dht_kv_update_req(const uint8_t * key,
req = __dht_kv_req_get_req(key);
if (req == NULL) {
- if (dht.reqs.len == DHT_MAX_REQS) {
+ if (dht.reqs.ll.len == DHT_MAX_REQS) {
log_err(KEY_FMT " Max reqs reached (%zu).",
- KEY_VAL(key), dht.reqs.len);
+ KEY_VAL(key), dht.reqs.ll.len);
peer_list_destroy(pl);
goto fail_req;
}
@@ -1312,8 +1276,7 @@ static int dht_kv_update_req(const uint8_t * key,
log_err(KEY_FMT "Failed to create req.", KEY_VAL(key));
goto fail_req;
}
- list_add_tail(&req->next, &dht.reqs.list);
- ++dht.reqs.len;
+ llist_add_tail(&req->next, &dht.reqs.ll);
}
if (req->cache.len > 0) /* Already have values */
@@ -1322,9 +1285,9 @@ static int dht_kv_update_req(const uint8_t * key,
dht_req_add_peers(req, pl);
req->t_exp = now.tv_sec + DHT_T_RESP;
- if (dht.reqs.len > DHT_WARN_REQS) {
+ if (dht.reqs.ll.len > DHT_WARN_REQS) {
log_warn("Number of outstanding requests (%zu) exceeds %u.",
- dht.reqs.len, DHT_WARN_REQS);
+ dht.reqs.ll.len, DHT_WARN_REQS);
}
pthread_mutex_unlock(&dht.reqs.mtx);
@@ -1368,8 +1331,7 @@ static int dht_kv_respond_req(uint8_t * key,
continue;
}
- list_add_tail(&e->next, &req->cache.list);
- ++req->cache.len;
+ llist_add_tail(&e->next, &req->cache);
}
pthread_cond_broadcast(&dht.reqs.cond);
@@ -1434,7 +1396,7 @@ static ssize_t dht_kv_wait_req(const uint8_t * key,
memset(*vals, 0, max * sizeof(**vals));
- list_for_each(p, &req->cache.list) {
+ llist_for_each(p, &req->cache) {
struct val_entry * v;
if (i == max)
break; /* We have enough values */
@@ -1535,10 +1497,10 @@ static ssize_t dht_kv_contact_list(const uint8_t * key,
goto fail_bucket;
}
- b->t_refr = t.tv_sec + dht.t_refresh;
+ b->t_refr = t.tv_sec + dht.t_refr;
if (b->contacts.len == dht.k || b->parent == NULL) {
- list_for_each(p, &b->contacts.list) {
+ llist_for_each(p, &b->contacts) {
struct contact * c;
struct contact * d;
c = list_entry(p, struct contact, next);
@@ -1554,7 +1516,7 @@ static ssize_t dht_kv_contact_list(const uint8_t * key,
} else {
struct bucket * d = b->parent;
for (i = 0; i < (1L << DHT_BETA) && len < dht.k; ++i) {
- list_for_each(p, &d->children[i]->contacts.list) {
+ llist_for_each(p, &d->children[i]->contacts) {
struct contact * c;
struct contact * d;
c = list_entry(p, struct contact, next);
@@ -1661,11 +1623,11 @@ static void __dht_kv_bucket_refresh_list(struct bucket * b,
__dht_kv_bucket_refresh_list(b->children[i], t, r);
}
- if (b->contacts.len == 0)
+ if (llist_is_empty(&b->contacts))
return;
- c = list_first_entry(&b->contacts.list, struct contact, next);
- if (t > c->t_seen + dht.t_refresh) {
+ 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);
@@ -1682,14 +1644,12 @@ static struct bucket * bucket_create(void)
if (b == NULL)
return NULL;
- list_head_init(&b->contacts.list);
- b->contacts.len = 0;
+ llist_init(&b->contacts);
- list_head_init(&b->alts.list);
- b->alts.len = 0;
+ llist_init(&b->alts);
clock_gettime(CLOCK_REALTIME_COARSE, &t);
- b->t_refr = t.tv_sec + dht.t_refresh;
+ b->t_refr = t.tv_sec + dht.t_refr;
for (i = 0; i < (1L << DHT_BETA); ++i)
b->children[i] = NULL;
@@ -1713,18 +1673,16 @@ static void bucket_destroy(struct bucket * b)
if (b->children[i] != NULL)
bucket_destroy(b->children[i]);
- list_for_each_safe(p, h, &b->contacts.list) {
+ 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->contacts.len;
}
- list_for_each_safe(p, h, &b->alts.list) {
+ 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->alts.len;
}
free(b);
@@ -1759,13 +1717,11 @@ static int move_contacts(struct bucket * b,
assert(b != NULL);
assert(c != NULL);
- list_for_each_safe(p, h, &b->contacts.list) {
+ llist_for_each_safe(p, h, &b->contacts) {
d = list_entry(p, struct contact, next);
if (bucket_has_id(c, d->id)) {
- list_del(&d->next);
- --b->contacts.len;
- list_add_tail(&d->next, &c->contacts.list);
- ++c->contacts.len;
+ llist_del(&d->next, &b->contacts);
+ llist_add_tail(&d->next, &c->contacts);
}
}
@@ -1779,8 +1735,8 @@ static int split_bucket(struct bucket * b)
size_t b_len;
assert(b);
- assert(b->alts.len == 0);
- assert(b->contacts.len != 0);
+ assert(llist_is_empty(&b->alts));
+ assert(!llist_is_empty(&b->contacts));
assert(b->children[0] == NULL);
b_len = b->contacts.len;
@@ -1836,39 +1792,33 @@ static int dht_kv_update_contacts(const uint8_t * id,
goto fail_update;
}
- list_for_each_safe(p, h, &b->contacts.list) {
+ 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->contacts.len;
}
}
if (b->contacts.len == dht.k) {
if (bucket_has_id(b, dht.id.data)) {
- list_add_tail(&c->next, &b->contacts.list);
- ++b->contacts.len;
+ 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->contacts.len;
}
} else if (b->alts.len == dht.k) {
struct contact * d;
- d = list_first_entry(&b->alts.list,
+ d = llist_first_entry(&b->alts,
struct contact, next);
- list_del(&d->next);
+ llist_del(&d->next, &b->alts);
contact_destroy(d);
- list_add_tail(&c->next, &b->alts.list);
- ++b->alts.len;
+ llist_add_tail(&c->next, &b->alts);
} else {
- list_add_tail(&c->next, &b->alts.list);
- ++b->alts.len;
+ llist_add_tail(&c->next, &b->alts);
}
} else {
- list_add_tail(&c->next, &b->contacts.list);
- ++b->contacts.len;
+ llist_add_tail(&c->next, &b->contacts);
}
pthread_rwlock_unlock(&dht.db.lock);
@@ -2116,7 +2066,7 @@ static ssize_t dht_kv_retrieve(const uint8_t * key,
i = 0;
- list_for_each(p, &e->vals.list) {
+ llist_for_each(p, &e->vals) {
struct val_entry * v;
if (i == n)
break; /* We have enough values */
@@ -2129,7 +2079,7 @@ static ssize_t dht_kv_retrieve(const uint8_t * key,
memcpy((*vals)[i++].data, v->val.data, v->val.len);
}
- list_for_each(p, &e->lvals.list) {
+ llist_for_each(p, &e->lvals) {
struct val_entry * v;
if (i == n)
break; /* We have enough values */
@@ -2266,7 +2216,7 @@ static int dht_send_msg(dht_msg_t * msg,
uint64_t addr)
{
size_t len;
- struct shm_du_buff * sdb;
+ struct ssm_pk_buff * spb;
if (msg == NULL)
return 0;
@@ -2279,21 +2229,21 @@ static int dht_send_msg(dht_msg_t * msg,
goto fail_msg;
}
- if (ipcp_sdb_reserve(&sdb, len)) {
- log_warn("%s failed to get sdb.", DHT_CODE(msg));
+ if (ipcp_spb_reserve(&spb, len)) {
+ log_warn("%s failed to get spb.", DHT_CODE(msg));
goto fail_msg;
}
- dht_msg__pack(msg, shm_du_buff_head(sdb));
+ dht_msg__pack(msg, ssm_pk_buff_head(spb));
- if (dt_write_packet(addr, QOS_CUBE_BE, dht.eid, sdb) < 0) {
+ if (dt_write_packet(addr, QOS_CUBE_BE, dht.eid, spb) < 0) {
log_warn("%s write failed", DHT_CODE(msg));
goto fail_send;
}
return 0;
fail_send:
- ipcp_sdb_release(sdb);
+ ipcp_spb_release(spb);
fail_msg:
return -1;
}
@@ -2584,15 +2534,14 @@ static void __add_dht_kv_entry(struct dht_entry * e)
assert(e != NULL);
- list_for_each(p, &dht.db.kv.list) {
+ 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;
}
- list_add_tail(&e->next, p);
- ++dht.db.kv.len;
+ llist_add_tail_at(&e->next, p, &dht.db.kv.ll);
}
/* incoming store message */
@@ -2629,9 +2578,8 @@ static int dht_kv_store(const uint8_t * key,
return 0;
fail_add:
if (new) {
- list_del(&e->next);
+ llist_del(&e->next, &dht.db.kv.ll);
dht_entry_destroy(e);
- --dht.db.kv.len;
}
fail:
pthread_rwlock_unlock(&dht.db.lock);
@@ -2669,14 +2617,13 @@ static int dht_kv_publish(const uint8_t * key,
pthread_rwlock_unlock(&dht.db.lock);
- dht_kv_store_remote(key, val, now.tv_sec + dht.t_expire);
+ dht_kv_store_remote(key, val, now.tv_sec + dht.t_exp);
return 0;
fail_add:
if (new) {
- list_del(&e->next);
+ llist_del(&e->next, &dht.db.kv.ll);
dht_entry_destroy(e);
- --dht.db.kv.len;
}
fail:
pthread_rwlock_unlock(&dht.db.lock);
@@ -2858,7 +2805,7 @@ static void do_dht_kv_store(const dht_store_msg_t * store)
key = store->key.data;
exp = store->exp;
- if (dht_kv_store(store->key.data, val, store->exp) < 0) {
+ if (dht_kv_store(key, val, store->exp) < 0) {
log_err(KV_FMT " Failed to store.", KV_VAL(key, val));
return;
}
@@ -3189,7 +3136,7 @@ static void * dht_handle_packet(void * o)
}
#ifndef __DHT_TEST__
static void dht_post_packet(void * comp,
- struct shm_du_buff * sdb)
+ struct ssm_pk_buff * spb)
{
struct cmd * cmd;
@@ -3201,17 +3148,17 @@ static void dht_post_packet(void * comp,
goto fail_cmd;
}
- cmd->cbuf.data = malloc(shm_du_buff_len(sdb));
+ cmd->cbuf.data = malloc(ssm_pk_buff_len(spb));
if (cmd->cbuf.data == NULL) {
log_err("Command buffer malloc failed.");
goto fail_buf;
}
- cmd->cbuf.len = shm_du_buff_len(sdb);
+ cmd->cbuf.len = ssm_pk_buff_len(spb);
- memcpy(cmd->cbuf.data, shm_du_buff_head(sdb), cmd->cbuf.len);
+ memcpy(cmd->cbuf.data, ssm_pk_buff_head(spb), cmd->cbuf.len);
- ipcp_sdb_release(sdb);
+ ipcp_spb_release(spb);
pthread_mutex_lock(&dht.cmds.mtx);
@@ -3226,7 +3173,7 @@ static void dht_post_packet(void * comp,
fail_buf:
free(cmd);
fail_cmd:
- ipcp_sdb_release(sdb);
+ ipcp_spb_release(spb);
return;
}
#endif
@@ -3449,15 +3396,14 @@ static void dht_kv_remove_expired_reqs(void)
pthread_mutex_lock(&dht.reqs.mtx);
- list_for_each_safe(p, h, &dht.reqs.list) {
+ 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));
- list_del(&e->next);
+ llist_del(&e->next, &dht.reqs.ll);
dht_req_destroy(e);
- --dht.reqs.len;
}
}
@@ -3489,7 +3435,7 @@ static void dht_entry_get_repl_lists(const struct dht_entry * e,
struct list_head * p;
struct val_entry * n;
- list_for_each(p, &e->vals.list) {
+ 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);
@@ -3500,11 +3446,11 @@ static void dht_entry_get_repl_lists(const struct dht_entry * e,
}
}
- list_for_each(p, &e->lvals.list) {
+ llist_for_each(p, &e->lvals) {
struct val_entry * v = list_entry(p, struct val_entry, next);
if (MUST_REPLICATE(v, now) && MUST_REPUBLISH(v, now)) {
/* Add expire time here, to allow creating val_entry */
- n = val_entry_create(v->val, now->tv_sec + dht.t_expire);
+ n = val_entry_create(v->val, now->tv_sec + dht.t_exp);
if (n == NULL)
continue;
@@ -3533,10 +3479,10 @@ static int dht_kv_next_values(uint8_t * key,
pthread_rwlock_rdlock(&dht.db.lock);
- if (dht.db.kv.len == 0)
+ if (llist_is_empty(&dht.db.kv.ll))
goto no_entries;
- list_for_each_safe(p, h, &dht.db.kv.list) {
+ llist_for_each_safe(p, h, &dht.db.kv.ll) {
e = list_entry(p, struct dht_entry, next);
if (IS_CLOSER(e->key, key))
continue; /* Already processed */
@@ -3578,7 +3524,7 @@ static void dht_kv_republish_value(const uint8_t * key,
assert(MUST_REPLICATE(v, now));
if (MUST_REPUBLISH(v, now))
- assert(v->t_exp >= now->tv_sec + dht.t_expire);
+ assert(v->t_exp >= now->tv_sec + dht.t_exp);
if (dht_kv_store_remote(key, v->val, v->t_exp) == 0) {
log_dbg(KV_FMT " Republished.", KV_VAL(key, v->val));
@@ -3786,8 +3732,8 @@ static void * work(void * o)
nanosleep(&now, NULL);
}
- intv = gcd(dht.t_expire, (dht.t_expire - DHT_N_REPUB * dht.t_repl));
- intv = gcd(intv, gcd(dht.t_repl, dht.t_refresh)) / 2;
+ intv = 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);
log_dbg("DHT worker starting %ld seconds interval.", intv * n);
@@ -3868,13 +3814,13 @@ int dht_init(struct dir_dht_config * conf)
dht.id.len = DHT_TEST_KEY_LEN;
dht.addr = DHT_TEST_ADDR;
#endif
- dht.t0 = now.tv_sec;
- dht.alpha = conf->params.alpha;
- dht.k = conf->params.k;
- dht.t_expire = conf->params.t_expire;
- dht.t_refresh = conf->params.t_refresh;
- dht.t_repl = conf->params.t_replicate;
- dht.peer = conf->peer;
+ dht.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();
@@ -3899,8 +3845,7 @@ int dht_init(struct dir_dht_config * conf)
goto fail_cmds_cond;
}
- list_head_init(&dht.reqs.list);
- dht.reqs.len = 0;
+ llist_init(&dht.reqs.ll);
if (pthread_mutex_init(&dht.reqs.mtx, NULL)) {
log_err("Failed to initialize request mutex.");
@@ -3922,8 +3867,7 @@ int dht_init(struct dir_dht_config * conf)
goto fail_reqs_cond;
}
- list_head_init(&dht.db.kv.list);
- dht.db.kv.len = 0;
+ llist_init(&dht.db.kv.ll);
dht.db.kv.vals = 0;
dht.db.kv.lvals = 0;
@@ -3960,9 +3904,9 @@ int dht_init(struct dir_dht_config * conf)
log_dbg(" address: " ADDR_FMT32 ".", ADDR_VAL32(&dht.addr));
log_dbg(" peer: " ADDR_FMT32 ".", ADDR_VAL32(&dht.peer));
log_dbg(" magic cookie: " HASH_FMT64 ".", HASH_VAL64(&dht.magic));
- log_info(" parameters: alpha=%u, k=%zu, t_expire=%ld, "
- "t_refresh=%ld, t_replicate=%ld.",
- dht.alpha, dht.k, dht.t_expire, dht.t_refresh, dht.t_repl);
+ 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
dht.state = DHT_INIT;
@@ -4015,11 +3959,10 @@ void dht_fini(void)
pthread_mutex_lock(&dht.reqs.mtx);
- list_for_each_safe(p, h, &dht.reqs.list) {
+ llist_for_each_safe(p, h, &dht.reqs.ll) {
struct dht_req * r = list_entry(p, struct dht_req, next);
- list_del(&r->next);
+ llist_del(&r->next, &dht.reqs.ll);
dht_req_destroy(r);
- dht.reqs.len--;
}
pthread_mutex_unlock(&dht.reqs.mtx);
@@ -4029,11 +3972,10 @@ void dht_fini(void)
pthread_rwlock_wrlock(&dht.db.lock);
- list_for_each_safe(p, h, &dht.db.kv.list) {
+ llist_for_each_safe(p, h, &dht.db.kv.ll) {
struct dht_entry * e = list_entry(p, struct dht_entry, next);
- list_del(&e->next);
+ llist_del(&e->next, &dht.db.kv.ll);
dht_entry_destroy(e);
- dht.db.kv.len--;
}
if (dht.db.contacts.root != NULL)
@@ -4043,10 +3985,10 @@ void dht_fini(void)
pthread_rwlock_destroy(&dht.db.lock);
- assert(dht.db.kv.len == 0);
+ assert(llist_is_empty(&dht.db.kv.ll));
assert(dht.db.kv.vals == 0);
assert(dht.db.kv.lvals == 0);
- assert(dht.reqs.len == 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 852a5130..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
*
diff --git a/src/ipcpd/unicast/dir/dht.proto b/src/ipcpd/unicast/dir/dht.proto
index ea74805f..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
*
diff --git a/src/ipcpd/unicast/dir/ops.h b/src/ipcpd/unicast/dir/ops.h
index 8c6e5eb5..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
*
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 897f1ec2..eded823f 100644
--- a/src/ipcpd/unicast/dir/tests/CMakeLists.txt
+++ b/src/ipcpd/unicast/dir/tests/CMakeLists.txt
@@ -3,38 +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)
+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})
-if(CMAKE_VERSION VERSION_LESS "3.29.0")
- remove(tests_to_run test_suite.c)
-else ()
- list(POP_FRONT tests_to_run)
-endif()
+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 cb6b0f9f..1f7026b3 100644
--- a/src/ipcpd/unicast/dir/tests/dht_test.c
+++ b/src/ipcpd/unicast/dir/tests/dht_test.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Unit tests of the DHT
*
@@ -27,7 +27,7 @@
#define _POSIX_C_SOURCE 200112L
#endif
-#include <ouroboros/test.h>
+#include <test/test.h>
#include <ouroboros/list.h>
#include <ouroboros/utils.h>
@@ -46,10 +46,9 @@
/* forward declare for use in the dht code */
/* Packet sink for DHT tests */
struct {
- bool enabled;
+ bool enabled;
- struct list_head list;
- size_t len;
+ struct llist msgs;
} sink;
struct message {
@@ -66,8 +65,6 @@ static int sink_send_msg(buffer_t * pkt,
assert(pkt != NULL);
assert(addr != 0);
- assert(!list_is_empty(&sink.list) || sink.len == 0);
-
if (!sink.enabled)
goto finish;
@@ -83,9 +80,8 @@ static int sink_send_msg(buffer_t * pkt,
m->dst = addr;
- list_add_tail(&m->next, &sink.list);
+ llist_add_tail(&m->next, &sink.msgs);
- ++sink.len;
finish:
freebuf(*pkt);
@@ -103,8 +99,7 @@ static int sink_send_msg(buffer_t * pkt,
static void sink_init(void)
{
- list_head_init(&sink.list);
- sink.len = 0;
+ llist_init(&sink.msgs);
sink.enabled = true;
}
@@ -113,22 +108,20 @@ static void sink_clear(void)
struct list_head * p;
struct list_head * h;
- list_for_each_safe(p, h, &sink.list) {
+ llist_for_each_safe(p, h, &sink.msgs) {
struct message * m = list_entry(p, struct message, next);
- list_del(&m->next);
+ llist_del(&m->next, &sink.msgs);
dht_msg__free_unpacked((dht_msg_t *) m->msg, NULL);
free(m);
- --sink.len;
}
- assert(list_is_empty(&sink.list));
+ assert(llist_is_empty(&sink.msgs));
}
static void sink_fini(void)
{
sink_clear();
-
- assert(list_is_empty(&sink.list) || sink.len != 0);
+ sink.enabled = false;
}
static dht_msg_t * sink_read(void)
@@ -136,16 +129,12 @@ static dht_msg_t * sink_read(void)
struct message * m;
dht_msg_t * msg;
- assert(!list_is_empty(&sink.list) || sink.len == 0);
-
- if (list_is_empty(&sink.list))
+ if (llist_is_empty(&sink.msgs))
return NULL;
- m = list_first_entry(&sink.list, struct message, next);
-
- --sink.len;
+ m = llist_first_entry(&sink.msgs, struct message, next);
- list_del(&m->next);
+ llist_del(&m->next, &sink.msgs);
msg = m->msg;
@@ -978,7 +967,7 @@ static int test_dht_kv_find_node_rsp_msg_contacts(void)
}
if ((size_t) n < dht.k) {
- printf("Failed to get enough contacts (%zu < %zu).\n", n, dht.k);
+ printf("Failed to get all contacts (%zu < %zu).\n", n, dht.k);
goto fail_fill;
}
@@ -1204,7 +1193,7 @@ static int test_dht_kv_find_value_rsp_msg_contacts(void)
}
if ((size_t) n < dht.k) {
- printf("Failed to get enough contacts (%zu < %zu).\n", n, dht.k);
+ printf("Failed to get all contacts (%zu < %zu).\n", n, dht.k);
goto fail_fill;
}
@@ -1591,7 +1580,7 @@ static int test_dht_reg_unreg(void)
goto fail_reg;
}
- if (sink.len != 0) {
+ if (!llist_is_empty(&sink.msgs)) {
printf("Packet sent without contacts!");
goto fail_msg;
}
@@ -1642,7 +1631,7 @@ static int test_dht_reg_unreg_contacts(void)
goto fail_reg;
}
- if (sink.len != dht.alpha) {
+ if (sink.msgs.len != dht.alpha) {
printf("Packet sent to too few contacts!\n");
goto fail_msg;
}
@@ -1784,7 +1773,7 @@ static int test_dht_query(void)
goto fail_get;
}
- if (sink.len != 0) {
+ if (!llist_is_empty(&sink.msgs)) {
printf("Packet sent without contacts!");
goto fail_test;
}
diff --git a/src/ipcpd/unicast/dt.c b/src/ipcpd/unicast/dt.c
index e2679ffe..252477f4 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
*
@@ -68,7 +68,7 @@
#endif
struct comp_info {
- void (* post_packet)(void * comp, struct shm_du_buff * sdb);
+ void (* post_packet)(void * comp, struct ssm_pk_buff * spb);
void * comp;
char * name;
};
@@ -135,11 +135,11 @@ static void dt_pci_des(uint8_t * head,
memcpy(&dt_pci->eid, head + dt_pci_info.eid_o, dt_pci_info.eid_size);
}
-static void dt_pci_shrink(struct shm_du_buff * sdb)
+static void dt_pci_shrink(struct ssm_pk_buff * spb)
{
- assert(sdb);
+ assert(spb);
- shm_du_buff_head_release(sdb, dt_pci_info.head_size);
+ ssm_pk_buff_head_release(spb, dt_pci_info.head_size);
}
struct {
@@ -429,7 +429,7 @@ static void handle_event(void * self,
static void packet_handler(int fd,
qoscube_t qc,
- struct shm_du_buff * sdb)
+ struct ssm_pk_buff * spb)
{
struct dt_pci dt_pci;
int ret;
@@ -437,7 +437,7 @@ static void packet_handler(int fd,
uint8_t * head;
size_t len;
- len = shm_du_buff_len(sdb);
+ len = ssm_pk_buff_len(spb);
#ifndef IPCP_FLOW_STATS
(void) fd;
@@ -451,13 +451,13 @@ static void packet_handler(int fd,
#endif
memset(&dt_pci, 0, sizeof(dt_pci));
- head = shm_du_buff_head(sdb);
+ head = ssm_pk_buff_head(spb);
dt_pci_des(head, &dt_pci);
if (dt_pci.dst_addr != dt.addr) {
if (dt_pci.ttl == 0) {
log_dbg("TTL was zero.");
- ipcp_sdb_release(sdb);
+ ipcp_spb_release(spb);
#ifdef IPCP_FLOW_STATS
pthread_mutex_lock(&dt.stat[fd].lock);
@@ -474,7 +474,7 @@ static void packet_handler(int fd,
if (ofd < 0) {
log_dbg("No next hop for %" PRIu64 ".",
dt_pci.dst_addr);
- ipcp_sdb_release(sdb);
+ ipcp_spb_release(spb);
#ifdef IPCP_FLOW_STATS
pthread_mutex_lock(&dt.stat[fd].lock);
@@ -488,12 +488,12 @@ static void packet_handler(int fd,
(void) ca_calc_ecn(ofd, head + dt_pci_info.ecn_o, qc, len);
- ret = ipcp_flow_write(ofd, sdb);
+ ret = ipcp_flow_write(ofd, spb);
if (ret < 0) {
log_dbg("Failed to write packet to fd %d.", ofd);
if (ret == -EFLOWDOWN)
notifier_event(NOTIFY_DT_FLOW_DOWN, &ofd);
- ipcp_sdb_release(sdb);
+ ipcp_spb_release(spb);
#ifdef IPCP_FLOW_STATS
pthread_mutex_lock(&dt.stat[ofd].lock);
@@ -513,17 +513,17 @@ static void packet_handler(int fd,
pthread_mutex_unlock(&dt.stat[ofd].lock);
#endif
} else {
- dt_pci_shrink(sdb);
+ dt_pci_shrink(spb);
if (dt_pci.eid >= PROG_RES_FDS) {
uint8_t ecn = *(head + dt_pci_info.ecn_o);
- fa_np1_rcv(dt_pci.eid, ecn, sdb);
+ fa_np1_rcv(dt_pci.eid, ecn, spb);
return;
}
if (dt.comps[dt_pci.eid].post_packet == NULL) {
log_err("No registered component on eid %" PRIu64 ".",
dt_pci.eid);
- ipcp_sdb_release(sdb);
+ ipcp_spb_release(spb);
return;
}
#ifdef IPCP_FLOW_STATS
@@ -541,7 +541,7 @@ static void packet_handler(int fd,
pthread_mutex_unlock(&dt.stat[dt_pci.eid].lock);
#endif
dt.comps[dt_pci.eid].post_packet(dt.comps[dt_pci.eid].comp,
- sdb);
+ spb);
}
}
@@ -758,7 +758,7 @@ 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;
@@ -809,7 +809,7 @@ 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)
{
struct dt_pci dt_pci;
int fd;
@@ -817,12 +817,12 @@ int dt_write_packet(uint64_t dst_addr,
uint8_t * head;
size_t len;
- assert(sdb);
+ assert(spb);
assert(dst_addr != dt.addr);
- len = shm_du_buff_len(sdb);
-
#ifdef IPCP_FLOW_STATS
+ len = ssm_pk_buff_len(spb);
+
if (eid < PROG_RES_FDS) {
pthread_mutex_lock(&dt.stat[eid].lock);
@@ -849,13 +849,13 @@ int dt_write_packet(uint64_t dst_addr,
return -EPERM;
}
- head = shm_du_buff_head_alloc(sdb, dt_pci_info.head_size);
+ head = ssm_pk_buff_head_alloc(spb, dt_pci_info.head_size);
if (head == NULL) {
log_dbg("Failed to allocate DT header.");
goto fail_write;
}
- len = shm_du_buff_len(sdb);
+ len = ssm_pk_buff_len(spb);
dt_pci.dst_addr = dst_addr;
dt_pci.qc = qc;
@@ -866,7 +866,7 @@ int dt_write_packet(uint64_t dst_addr,
dt_pci_ser(head, &dt_pci);
- ret = ipcp_flow_write(fd, sdb);
+ ret = ipcp_flow_write(fd, spb);
if (ret < 0) {
log_dbg("Failed to write packet to fd %d.", fd);
if (ret == -EFLOWDOWN)
diff --git a/src/ipcpd/unicast/dt.h b/src/ipcpd/unicast/dt.h
index 2c5b7978..a484377d 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"
@@ -40,7 +40,7 @@ int dt_start(void);
void dt_stop(void);
int dt_reg_comp(void * comp,
- void (* func)(void * comp, struct shm_du_buff * sdb),
+ void (* func)(void * comp, struct ssm_pk_buff * spb),
char * name);
void dt_unreg_comp(int eid);
@@ -48,6 +48,6 @@ void dt_unreg_comp(int eid);
int dt_write_packet(uint64_t dst_addr,
qoscube_t qc,
uint64_t eid,
- struct shm_du_buff * sdb);
+ struct ssm_pk_buff * spb);
#endif /* OUROBOROS_IPCPD_UNICAST_DT_H */
diff --git a/src/ipcpd/unicast/fa.c b/src/ipcpd/unicast/fa.c
index ac168bd9..ddf78e22 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
*
@@ -48,6 +48,7 @@
#include "ipcp.h"
#include "dt.h"
#include "ca.h"
+#include "np1.h"
#include <inttypes.h>
#include <stdlib.h>
@@ -85,7 +86,7 @@ struct fa_msg {
struct cmd {
struct list_head next;
- struct shm_du_buff * sdb;
+ struct ssm_pk_buff * spb;
};
struct fa_flow {
@@ -330,7 +331,7 @@ static uint64_t gen_eid(int fd)
static void packet_handler(int fd,
qoscube_t qc,
- struct shm_du_buff * sdb)
+ struct ssm_pk_buff * spb)
{
struct fa_flow * flow;
uint64_t r_addr;
@@ -342,7 +343,7 @@ static void packet_handler(int fd,
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;
@@ -357,8 +358,8 @@ static void packet_handler(int fd,
ca_wnd_wait(wnd);
- if (dt_write_packet(r_addr, qc, r_eid, sdb)) {
- ipcp_sdb_release(sdb);
+ if (dt_write_packet(r_addr, qc, r_eid, spb)) {
+ ipcp_spb_release(spb);
log_dbg("Failed to forward packet.");
#ifdef IPCP_FLOW_STATS
pthread_rwlock_wrlock(&fa.flows_lock);
@@ -411,7 +412,7 @@ static void fa_flow_fini(struct fa_flow * flow)
}
static void fa_post_packet(void * comp,
- struct shm_du_buff * sdb)
+ struct ssm_pk_buff * spb)
{
struct cmd * cmd;
@@ -422,11 +423,11 @@ static void fa_post_packet(void * comp,
cmd = malloc(sizeof(*cmd));
if (cmd == NULL) {
log_err("Command failed. Out of memory.");
- ipcp_sdb_release(sdb);
+ ipcp_spb_release(spb);
return;
}
- cmd->sdb = sdb;
+ cmd->spb = spb;
pthread_mutex_lock(&fa.mtx);
@@ -454,16 +455,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);
@@ -687,13 +688,21 @@ void fa_fini(void)
pthread_rwlock_destroy(&fa.flows_lock);
}
+static int np1_flow_read_fa(int fd,
+ struct ssm_pk_buff ** spb)
+{
+ return np1_flow_read(fd, spb, NP1_GET_POOL(fd));
+}
+
int fa_start(void)
{
+#ifndef BUILD_CONTAINER
struct sched_param par;
int pol;
int max;
+#endif
- fa.psched = psched_create(packet_handler, 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;
@@ -704,6 +713,7 @@ int fa_start(void)
goto fail_thread;
}
+#ifndef BUILD_CONTAINER
if (pthread_getschedparam(fa.worker, &pol, &par)) {
log_err("Failed to get worker thread scheduling parameters.");
goto fail_sched;
@@ -721,12 +731,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:
@@ -747,7 +760,7 @@ 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;
@@ -760,10 +773,10 @@ 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);
@@ -782,11 +795,11 @@ int fa_alloc(int fd,
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)) {
log_err("Failed to send flow allocation request packet.");
- ipcp_sdb_release(sdb);
+ ipcp_spb_release(spb);
return -1;
}
@@ -808,7 +821,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;
@@ -819,13 +832,13 @@ 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;
@@ -840,7 +853,7 @@ 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)) {
log_err("Failed to send flow allocation response packet.");
goto fail_packet;
}
@@ -856,7 +869,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);
@@ -887,17 +900,17 @@ static int fa_update_remote(int fd,
uint16_t ece)
{
struct fa_msg * msg;
- struct shm_du_buff * sdb;
+ struct ssm_pk_buff * spb;
qoscube_t qc = QOS_CUBE_BE;
struct fa_flow * flow;
uint64_t r_addr;
- if (ipcp_sdb_reserve(&sdb, sizeof(*msg))) {
- 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));
@@ -916,9 +929,9 @@ static int fa_update_remote(int fd,
pthread_rwlock_unlock(&fa.flows_lock);
- if (dt_write_packet(r_addr, qc, fa.eid, sdb)) {
+ if (dt_write_packet(r_addr, qc, fa.eid, spb)) {
log_err("Failed to send flow update packet.");
- ipcp_sdb_release(sdb);
+ ipcp_spb_release(spb);
return -1;
}
@@ -927,7 +940,7 @@ static int fa_update_remote(int fd,
void fa_np1_rcv(uint64_t eid,
uint8_t ecn,
- struct shm_du_buff * sdb)
+ struct ssm_pk_buff * spb)
{
struct fa_flow * flow;
bool update;
@@ -935,7 +948,7 @@ void fa_np1_rcv(uint64_t eid,
int fd;
size_t len;
- len = shm_du_buff_len(sdb);
+ len = ssm_pk_buff_len(spb);
pthread_rwlock_wrlock(&fa.flows_lock);
@@ -943,7 +956,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;
}
@@ -957,9 +970,9 @@ void fa_np1_rcv(uint64_t eid,
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;
diff --git a/src/ipcpd/unicast/fa.h b/src/ipcpd/unicast/fa.h
index 1e716966..0c19dc25 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
*
@@ -47,6 +47,6 @@ int fa_dealloc(int fd);
void fa_np1_rcv(uint64_t eid,
uint8_t ecn,
- struct shm_du_buff * sdb);
+ struct ssm_pk_buff * spb);
#endif /* OUROBOROS_IPCPD_UNICAST_FA_H */
diff --git a/src/ipcpd/unicast/main.c b/src/ipcpd/unicast/main.c
index 7989d3e1..583a04ff 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
*
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..be1c35c0 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
*
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..c636e789 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
*
diff --git a/src/ipcpd/unicast/pff/multipath.h b/src/ipcpd/unicast/pff/multipath.h
index 0eb03476..5329f7fc 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
*
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..a0d70799 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
*
diff --git a/src/ipcpd/unicast/pff/pft.h b/src/ipcpd/unicast/pff/pft.h
index 711dabcb..3bb9cff7 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
*
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..be542bdb 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
*
diff --git a/src/ipcpd/unicast/pff/simple.h b/src/ipcpd/unicast/pff/simple.h
index 0966a186..1046e4c4 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
*
diff --git a/src/ipcpd/unicast/pff/tests/CMakeLists.txt b/src/ipcpd/unicast/pff/tests/CMakeLists.txt
index 99c32e7a..8c0e3d51 100644
--- a/src/ipcpd/unicast/pff/tests/CMakeLists.txt
+++ b/src/ipcpd/unicast/pff/tests/CMakeLists.txt
@@ -3,36 +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 ${${PARENT_DIR}_tests})
-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
+)
+
+disable_test_logging_for_target(${PARENT_DIR}_test)
+target_link_libraries(${PARENT_DIR}_test PRIVATE ouroboros-common)
-set(tests_to_run ${${PARENT_DIR}_tests})
-if(CMAKE_VERSION VERSION_LESS "3.29.0")
- remove(tests_to_run test_suite.c)
-else ()
- list(POP_FRONT tests_to_run)
-endif()
+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..4962c241 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
*
diff --git a/src/ipcpd/unicast/psched.c b/src/ipcpd/unicast/psched.c
index 7e12148b..21e23617 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
*
@@ -41,11 +41,13 @@
#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];
@@ -67,7 +69,7 @@ static void cleanup_reader(void * o)
static void * packet_reader(void * o)
{
struct psched * sched;
- struct shm_du_buff * sdb;
+ struct ssm_pk_buff * spb;
int fd;
fqueue_t * fq;
qoscube_t qc;
@@ -102,10 +104,10 @@ static void * packet_reader(void * o)
notifier_event(NOTIFY_DT_FLOW_UP, &fd);
break;
case FLOW_PKT:
- if (sched->read(fd, &sdb) < 0)
+ if (sched->read(fd, &spb) < 0)
continue;
- sched->callback(fd, qc, sdb);
+ sched->callback(fd, qc, spb);
break;
default:
break;
@@ -168,6 +170,7 @@ struct psched * psched_create(next_packet_fn_t callback,
}
}
+#ifndef BUILD_CONTAINER
for (i = 0; i < QOS_CUBE_MAX * IPCP_SCHED_THR_MUL; ++i) {
struct sched_param par;
int pol = SCHED_RR;
@@ -185,14 +188,17 @@ struct psched * psched_create(next_packet_fn_t callback,
if (pthread_setschedparam(psched->readers[i], pol, &par))
goto fail_sched;
}
+#endif
return psched;
+#ifndef BUILD_CONTAINER
fail_sched:
for (j = 0; j < QOS_CUBE_MAX * IPCP_SCHED_THR_MUL; ++j)
pthread_cancel(psched->readers[j]);
for (j = 0; j < QOS_CUBE_MAX * IPCP_SCHED_THR_MUL; ++j)
pthread_join(psched->readers[j], NULL);
+#endif
fail_infos:
for (j = 0; j < QOS_CUBE_MAX; ++j)
fset_destroy(psched->set[j]);
diff --git a/src/ipcpd/unicast/psched.h b/src/ipcpd/unicast/psched.h
index 831f8084..d83bb793 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
*
@@ -28,10 +28,10 @@
typedef void (* next_packet_fn_t)(int fd,
qoscube_t qc,
- struct shm_du_buff * sdb);
+ 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 2ad7b234..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
*
diff --git a/src/ipcpd/unicast/routing.h b/src/ipcpd/unicast/routing.h
index e14960b5..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
*
diff --git a/src/ipcpd/unicast/routing/graph.c b/src/ipcpd/unicast/routing/graph.c
index 32442dad..0226c762 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,10 +57,7 @@ struct edge {
};
struct graph {
- struct {
- struct list_head list;
- size_t len;
- } vertices;
+ struct llist vertices;
pthread_mutex_t lock;
};
@@ -88,7 +85,7 @@ static struct vertex * find_vertex_by_addr(struct graph * graph,
assert(graph);
- list_for_each(p, &graph->vertices.list) {
+ llist_for_each(p, &graph->vertices) {
struct vertex * e = list_entry(p, struct vertex, next);
if (e->addr == addr)
return e;
@@ -142,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.list) {
+ llist_for_each(p, &graph->vertices) {
struct vertex * v = list_entry(p, struct vertex, next);
if (v->addr > addr)
break;
@@ -151,7 +148,7 @@ 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, &vertex->next) {
@@ -160,37 +157,41 @@ static struct vertex * add_vertex(struct graph * graph,
v->index++;
}
- ++graph->vertices.len;
-
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 != 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.list) {
+ 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->vertices.len;
}
struct graph * graph_create(void)
@@ -206,8 +207,7 @@ struct graph * graph_create(void)
return NULL;
}
- graph->vertices.len = 0;
- list_head_init(&graph->vertices.list);
+ llist_init(&graph->vertices);
return graph;
}
@@ -221,7 +221,7 @@ void graph_destroy(struct graph * graph)
pthread_mutex_lock(&graph->lock);
- list_for_each_safe(p, n, &graph->vertices.list) {
+ llist_for_each_safe(p, n, &graph->vertices) {
struct vertex * e = list_entry(p, struct vertex, next);
del_vertex(graph, e);
}
@@ -230,7 +230,7 @@ void graph_destroy(struct graph * graph)
pthread_mutex_destroy(&graph->lock);
- assert(graph->vertices.len == 0);
+ assert(llist_is_empty(&graph->vertices));
free(graph);
}
@@ -371,7 +371,7 @@ static int get_min_vertex(struct graph * graph,
*v = NULL;
- list_for_each(p, &graph->vertices.list) {
+ llist_for_each(p, &graph->vertices) {
if (!used[i] && dist[i] < min) {
min = dist[i];
index = i;
@@ -420,7 +420,7 @@ static int dijkstra(struct graph * graph,
memset(*nhops, 0, sizeof(**nhops) * graph->vertices.len);
memset(*dist, 0, sizeof(**dist) * graph->vertices.len);
- list_for_each(p, &graph->vertices.list) {
+ llist_for_each(p, &graph->vertices) {
v = list_entry(p, struct vertex, next);
(*dist)[i++] = (v->addr == src) ? 0 : INT_MAX;
}
@@ -526,7 +526,7 @@ static int graph_routing_table_simple(struct graph * graph,
list_head_init(table);
/* Now construct the routing table from the nhops. */
- list_for_each(p, &graph->vertices.list) {
+ llist_for_each(p, &graph->vertices) {
v = list_entry(p, struct vertex, next);
/* This is the src */
@@ -624,7 +624,7 @@ static int graph_routing_table_lfa(struct graph * graph,
addrs[j] = -1;
}
- list_for_each(p, &graph->vertices.list) {
+ llist_for_each(p, &graph->vertices) {
v = list_entry(p, struct vertex, next);
if (v->addr != s_addr)
@@ -650,7 +650,7 @@ static int graph_routing_table_lfa(struct graph * graph,
}
/* Loop though all nodes to see if we have a LFA for them. */
- list_for_each(p, &graph->vertices.list) {
+ llist_for_each(p, &graph->vertices) {
v = list_entry(p, struct vertex, next);
if (v->addr == s_addr)
@@ -695,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;
@@ -735,16 +734,15 @@ static int graph_routing_table_ecmp(struct graph * graph,
free(nhops);
- list_for_each(h, &graph->vertices.list) {
- 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]);
}
}
@@ -753,38 +751,34 @@ static int graph_routing_table_ecmp(struct graph * graph,
list_head_init(table);
i = 0;
- list_for_each(p, &graph->vertices.list) {
+ 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 e5edf539..051dd98d 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
*
@@ -56,7 +56,7 @@
#include <string.h>
#define LS_ENTRY_SIZE 104
-#define LSDB "lsdb"
+#define lsdb "lsdb"
#ifndef CLOCK_REALTIME_COARSE
#define CLOCK_REALTIME_COARSE CLOCK_REALTIME
@@ -121,16 +121,8 @@ struct {
struct graph * graph;
struct {
- struct {
- struct list_head list;
- size_t len;
- } nbs;
-
- struct {
- struct list_head list;
- size_t len;
- } db;
-
+ struct llist nbs;
+ struct llist db;
pthread_rwlock_t lock;
};
@@ -189,7 +181,7 @@ static struct adjacency * get_adj(const char * path)
assert(path);
- list_for_each(p, &ls.db.list) {
+ llist_for_each(p, &ls.db) {
struct adjacency * a = list_entry(p, struct adjacency, next);
sprintf(entry, LINK_FMT, LINK_VAL(a->src, a->dst));
if (strcmp(entry, path) == 0)
@@ -245,7 +237,7 @@ static int lsdb_rib_read(const char * path,
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);
@@ -274,7 +266,7 @@ static int lsdb_rib_readdir(char *** buf)
pthread_rwlock_rdlock(&ls.lock);
- if (ls.db.len + ls.nbs.len == 0) {
+ if (llist_is_empty(&ls.db) && llist_is_empty(&ls.nbs)) {
*buf = NULL;
goto no_entries;
}
@@ -284,7 +276,7 @@ static int lsdb_rib_readdir(char *** buf)
if (*buf == NULL)
goto fail_entries;
- list_for_each(p, &ls.nbs.list) {
+ 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" ADDR_FMT32 , str, ADDR_VAL32(&nb->addr));
@@ -295,7 +287,7 @@ static int lsdb_rib_readdir(char *** buf)
strcpy((*buf)[idx++], entry);
}
- list_for_each(p, &ls.db.list) {
+ llist_for_each(p, &ls.db) {
struct adjacency * a = list_entry(p, struct adjacency, next);
sprintf(entry, LINK_FMT, LINK_VAL(a->src, a->dst));
(*buf)[idx] = malloc(strlen(entry) + 1);
@@ -333,7 +325,7 @@ static int lsdb_add_nb(uint64_t addr,
pthread_rwlock_wrlock(&ls.lock);
- list_for_each(p, &ls.nbs.list) {
+ llist_for_each(p, &ls.nbs) {
struct nb * el = list_entry(p, struct nb, next);
if (addr > el->addr)
break;
@@ -360,9 +352,7 @@ 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 " ADDR_FMT32 " added.",
nb->type == NB_DT ? "dt" : "mgmt", ADDR_VAL32(&addr));
@@ -380,13 +370,12 @@ static int lsdb_del_nb(uint64_t addr,
pthread_rwlock_wrlock(&ls.lock);
- list_for_each_safe(p, h, &ls.nbs.list) {
+ llist_for_each_safe(p, h, &ls.nbs) {
struct nb * nb = list_entry(p, struct nb, next);
if (nb->addr != addr || nb->fd != fd)
continue;
- list_del(&nb->next);
- --ls.nbs.len;
+ 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));
@@ -406,7 +395,7 @@ static int nbr_to_fd(uint64_t addr)
pthread_rwlock_rdlock(&ls.lock);
- list_for_each(p, &ls.nbs.list) {
+ 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;
@@ -494,7 +483,7 @@ static int lsdb_add_link(uint64_t src,
pthread_rwlock_wrlock(&ls.lock);
- list_for_each(p, &ls.db.list) {
+ 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) {
@@ -521,9 +510,7 @@ 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.");
@@ -543,15 +530,13 @@ static int lsdb_del_link(uint64_t src,
pthread_rwlock_wrlock(&ls.lock);
- list_for_each_safe(p, h, &ls.db.list) {
+ 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.lock);
set_pff_modified(false);
free(a);
@@ -599,7 +584,7 @@ static void send_lsm(uint64_t src,
lsm.s_addr = hton64(src);
lsm.seqno = hton64(seqno);
- list_for_each(p, &ls.nbs.list) {
+ llist_for_each(p, &ls.nbs) {
struct nb * nb = list_entry(p, struct nb, next);
if (nb->type != NB_MGMT)
continue;
@@ -628,7 +613,7 @@ static void lsdb_replicate(int fd)
/* Lock the lsdb, copy the lsms and send outside of lock. */
pthread_rwlock_rdlock(&ls.lock);
- list_for_each(p, &ls.db.list) {
+ llist_for_each(p, &ls.db) {
struct adjacency * adj;
struct adjacency * cpy;
adj = list_entry(p, struct adjacency, next);
@@ -675,11 +660,11 @@ static void * lsupdate(void * o)
pthread_cleanup_push(__cleanup_rwlock_unlock, &ls.lock);
- list_for_each_safe(p, h, &ls.db.list) {
+ 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.conf.t_timeo) {
- list_del(&adj->next);
+ 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,
@@ -746,7 +731,7 @@ static void forward_lsm(uint8_t * buf,
pthread_cleanup_push(__cleanup_rwlock_unlock, &ls.lock);
- list_for_each(p, &ls.nbs.list) {
+ llist_for_each(p, &ls.nbs) {
struct nb * nb = list_entry(p, struct nb, next);
if (nb->type != NB_MGMT || nb->fd == in_fd)
continue;
@@ -874,19 +859,19 @@ static void handle_event(void * self,
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(ls.addr, c->conn_info.addr, 0, &qs))
- log_dbg("Failed to add new adjacency to LSDB.");
+ 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(ls.addr, c->conn_info.addr))
- log_dbg("Local link was not in LSDB.");
+ log_dbg("Local link was not in lsdb.");
break;
case NOTIFY_DT_CONN_QOS:
log_dbg("QoS changes currently unsupported.");
@@ -902,14 +887,14 @@ static void handle_event(void * self,
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;
@@ -1090,16 +1075,13 @@ int link_state_init(struct ls_config * conf,
goto fail_fset_create;
}
- list_head_init(&ls.db.list);
- list_head_init(&ls.nbs.list);
+ 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:
@@ -1121,7 +1103,7 @@ void link_state_fini(void)
struct list_head * p;
struct list_head * h;
- rib_unreg(LSDB);
+ rib_unreg(lsdb);
fset_destroy(ls.mgmt_set);
@@ -1131,9 +1113,9 @@ void link_state_fini(void)
pthread_rwlock_wrlock(&ls.lock);
- list_for_each_safe(p, h, &ls.db.list) {
+ 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);
}
diff --git a/src/ipcpd/unicast/routing/link-state.h b/src/ipcpd/unicast/routing/link-state.h
index 69eb6781..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
*
diff --git a/src/ipcpd/unicast/routing/ops.h b/src/ipcpd/unicast/routing/ops.h
index 4bf75c80..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
*
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 b5011474..be2de72c 100644
--- a/src/ipcpd/unicast/routing/tests/CMakeLists.txt
+++ b/src/ipcpd/unicast/routing/tests/CMakeLists.txt
@@ -3,36 +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 ${${PARENT_DIR}_tests})
-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
+)
+
+disable_test_logging_for_target(${PARENT_DIR}_test)
+target_link_libraries(${PARENT_DIR}_test PRIVATE ouroboros-common)
-set(tests_to_run ${${PARENT_DIR}_tests})
-if(CMAKE_VERSION VERSION_LESS "3.29.0")
- remove(tests_to_run test_suite.c)
-else ()
- list(POP_FRONT tests_to_run)
-endif()
+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/irmd/CMakeLists.txt b/src/irmd/CMakeLists.txt
index 32904d59..9aa747ca 100644
--- a/src/irmd/CMakeLists.txt
+++ b/src/irmd/CMakeLists.txt
@@ -1,114 +1,67 @@
-include_directories(${CMAKE_CURRENT_SOURCE_DIR})
-include_directories(${CMAKE_CURRENT_BINARY_DIR})
+# IRMd (IPC Resource Manager daemon) build configuration
+# Configuration options are in cmake/config/global.cmake and cmake/config/irmd.cmake
-include_directories(${CMAKE_SOURCE_DIR}/include)
-include_directories(${CMAKE_BINARY_DIR}/include)
-
-set(OUROBOROS_CONFIG_DIR /etc/ouroboros CACHE STRING
- "Configuration directory")
-
-find_library(LIBTOML_LIBRARIES toml QUIET)
-if (LIBTOML_LIBRARIES)
- set(DISABLE_CONFIGFILE FALSE CACHE BOOL
- "Disable configuration file support")
- if (NOT DISABLE_CONFIGFILE)
- set(OUROBOROS_CONFIG_FILE irmd.conf CACHE STRING
- "Name of the IRMd configuration file")
- set(HAVE_TOML TRUE)
- message(STATUS "Found TOML C99 library: " ${LIBTOML_LIBRARIES})
- message(STATUS "Configuration file support enabled")
- message(STATUS "Configuration directory: ${OUROBOROS_CONFIG_DIR}")
- set(INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}")
- configure_file("${CMAKE_SOURCE_DIR}/irmd.conf.in"
- "${CMAKE_BINARY_DIR}/irmd.conf.example" @ONLY)
- install(FILES "${CMAKE_BINARY_DIR}/irmd.conf.example"
- DESTINATION "${OUROBOROS_CONFIG_DIR}")
- unset(INSTALL_DIR)
- find_path(LIBTOML_INCLUDE toml.h)
- mark_as_advanced(LIBTOML_LIBRARIES LIBTOML_INCLUDE)
- else ()
- message(STATUS "Configuration file support disabled by user")
- unset(OUROBOROS_CONFIG_FILE CACHE)
- set(HAVE_TOML FALSE)
- endif ()
-else ()
- message(STATUS "Install tomlc99 for config file support")
- message(STATUS " https://github.com/cktan/tomlc99")
- set(LIBTOML_LIBRARIES "")
- unset(DISABLE_CONFIGFILE CACHE)
- unset(HAVE_TOML)
-endif ()
-
-set(OUROBOROS_SECURITY_DIR "${OUROBOROS_CONFIG_DIR}/security" CACHE STRING
- "Security directory holding authentication information")
-set(OUROBOROS_CA_CRT_DIR "${OUROBOROS_SECURITY_DIR}/cacert" CACHE STRING
- "Directory holding trusted CA certificates")
-set(OUROBOROS_SRV_CRT_DIR "${OUROBOROS_SECURITY_DIR}/server" CACHE STRING
- "Directory holding trusted CA certificates")
-set(OUROBOROS_CLI_CRT_DIR "${OUROBOROS_SECURITY_DIR}/client" CACHE STRING
- "Directory holding trusted CA certificates")
-set(OUROBOROS_UNTRUSTED_DIR "${OUROBOROS_SECURITY_DIR}/untrusted" CACHE STRING
- "Directory holding untrusted intermediate certificates")
-
-set(IRMD_REQ_ARR_TIMEOUT 1000 CACHE STRING
- "Timeout for an application to respond to a new flow (ms)")
-set(BOOTSTRAP_TIMEOUT 5000 CACHE STRING
- "Timeout for an IPCP to bootstrap (ms)")
-set(ENROLL_TIMEOUT 20000 CACHE STRING
- "Timeout for an IPCP to enroll (ms)")
-set(REG_TIMEOUT 20000 CACHE STRING
- "Timeout for registering a name (ms)")
-set(QUERY_TIMEOUT 200 CACHE STRING
- "Timeout to query a name with an IPCP (ms)")
-set(CONNECT_TIMEOUT 20000 CACHE STRING
- "Timeout to connect an IPCP to another IPCP (ms)")
-set(FLOW_ALLOC_TIMEOUT 20000 CACHE STRING
- "Timeout for a flow allocation response (ms)")
-set(IRMD_MIN_THREADS 8 CACHE STRING
- "Minimum number of worker threads in the IRMd")
-set(IRMD_ADD_THREADS 8 CACHE STRING
- "Number of extra threads to start when the IRMD faces thread starvation")
-set(IRMD_PKILL_TIMEOUT 30 CACHE STRING
- "Number of seconds to wait before sending SIGKILL to subprocesses on exit")
-set(IRMD_KILL_ALL_PROCESSES TRUE CACHE BOOL
- "Kill all processes on exit")
-set(DEBUG_PROTO_OAP FALSE CACHE BOOL
- "Add Flow allocation protocol message output to IRMd debug logging")
+# Generate and install configuration files if TOML support available
+# HAVE_TOML is set in cmake/dependencies/irmd/libtoml.cmake
+if(HAVE_TOML)
+ set(INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}")
+ configure_file("${CMAKE_SOURCE_DIR}/irmd.conf.in"
+ "${CMAKE_BINARY_DIR}/${OUROBOROS_CONFIG_FILE}.example" @ONLY)
+ configure_file("${CMAKE_SOURCE_DIR}/enc.conf.in"
+ "${CMAKE_BINARY_DIR}/enc.conf.example" @ONLY)
+ install(FILES "${CMAKE_BINARY_DIR}/${OUROBOROS_CONFIG_FILE}.example"
+ DESTINATION "${OUROBOROS_CONFIG_DIR}")
+ install(FILES "${CMAKE_BINARY_DIR}/enc.conf.example"
+ DESTINATION "${OUROBOROS_CONFIG_DIR}")
+ install(CODE "
+ if(NOT EXISTS \"${OUROBOROS_CONFIG_DIR}/${OUROBOROS_CONFIG_FILE}\")
+ file(WRITE \"${OUROBOROS_CONFIG_DIR}/${OUROBOROS_CONFIG_FILE}\" \"\")
+ endif()
+ ")
+ unset(INSTALL_DIR)
+endif()
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/config.h.in"
"${CMAKE_CURRENT_BINARY_DIR}/config.h" @ONLY)
-set(SOURCE_FILES
- # Add source files here
+set(IRMD_SOURCES
ipcp.c
configfile.c
main.c
- oap.c
+ oap/io.c
+ oap/hdr.c
+ oap/auth.c
+ oap/srv.c
+ oap/cli.c
reg/flow.c
reg/ipcp.c
+ reg/pool.c
reg/proc.c
reg/prog.c
reg/name.c
reg/reg.c
- )
+)
+
+add_executable(irmd ${IRMD_SOURCES})
-add_executable (irmd ${SOURCE_FILES})
+add_dependencies(irmd version_header)
-target_link_libraries (irmd LINK_PUBLIC ouroboros-common
- ${LIBTOML_LIBRARIES})
+target_include_directories(irmd PRIVATE
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}/include
+ ${CMAKE_BINARY_DIR}/include)
-if (HAVE_TOML)
- target_include_directories(irmd PUBLIC ${LIBTOML_INCLUDE})
-endif ()
+target_link_libraries(irmd PRIVATE ouroboros-common)
+if(HAVE_TOML)
+ target_link_libraries(irmd PRIVATE toml::toml)
+endif()
-include(AddCompileFlags)
-if (CMAKE_BUILD_TYPE MATCHES "Debug*")
- add_compile_flags(irmd -DCONFIG_OUROBOROS_DEBUG)
-endif ()
+ouroboros_target_debug_definitions(irmd)
install(TARGETS irmd RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR})
-add_subdirectory(reg)
if(BUILD_TESTS)
- add_subdirectory(tests)
-endif ()
+ add_subdirectory(oap/tests)
+ add_subdirectory(reg/tests)
+endif()
diff --git a/src/irmd/config.h.in b/src/irmd/config.h.in
index 527694c0..df0cd718 100644
--- a/src/irmd/config.h.in
+++ b/src/irmd/config.h.in
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Configuration for the IPC Resource Manager
*
@@ -41,6 +41,8 @@
#define FLOW_ALLOC_TIMEOUT @FLOW_ALLOC_TIMEOUT@
#define FLOW_DEALLOC_TIMEOUT @FLOW_DEALLOC_TIMEOUT@
+#define OAP_REPLAY_TIMER @OAP_REPLAY_TIMER@
+
#define BOOTSTRAP_TIMEOUT @BOOTSTRAP_TIMEOUT@
#define ENROLL_TIMEOUT @ENROLL_TIMEOUT@
#define REG_TIMEOUT @REG_TIMEOUT@
@@ -51,6 +53,8 @@
#define IRMD_MIN_THREADS @IRMD_MIN_THREADS@
#define IRMD_ADD_THREADS @IRMD_ADD_THREADS@
+#define SSM_PID_GSPP 0
+
#cmakedefine HAVE_FUSE
#ifdef HAVE_FUSE
#define FUSE_PREFIX "@FUSE_PREFIX@"
@@ -70,9 +74,15 @@
#define IRMD_PKILL_TIMEOUT @IRMD_PKILL_TIMEOUT@
+#cmakedefine DISABLE_DIRECT_IPC
#cmakedefine IRMD_KILL_ALL_PROCESSES
#cmakedefine HAVE_LIBGCRYPT
#cmakedefine HAVE_OPENSSL
+#ifdef HAVE_OPENSSL
+#cmakedefine HAVE_OPENSSL_ML_KEM
+#cmakedefine HAVE_OPENSSL_ML_DSA
+#endif
+#define IRMD_SECMEM_MAX @IRMD_SECMEM_MAX@
#ifdef CONFIG_OUROBOROS_DEBUG
#cmakedefine DEBUG_PROTO_OAP
#endif
diff --git a/src/irmd/configfile.c b/src/irmd/configfile.c
index ce9fc8fc..53608eee 100644
--- a/src/irmd/configfile.c
+++ b/src/irmd/configfile.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* The IPC Resource Manager / Configuration from file
*
diff --git a/src/irmd/configfile.h b/src/irmd/configfile.h
index 3ccf53fd..62f522a4 100644
--- a/src/irmd/configfile.h
+++ b/src/irmd/configfile.h
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* The IPC Resource Manager / Configuration from file
*
diff --git a/src/irmd/ipcp.c b/src/irmd/ipcp.c
index 6226aeda..a7da186c 100644
--- a/src/irmd/ipcp.c
+++ b/src/irmd/ipcp.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* The API to instruct IPCPs
*
@@ -421,6 +421,8 @@ int ipcp_flow_join(const struct flow_info * flow,
msg.flow_id = flow->id;
msg.has_pid = true;
msg.pid = flow->n_pid;
+ msg.has_uid = true;
+ msg.uid = flow->uid;
msg.has_hash = true;
msg.hash.data = (uint8_t *) dst.data;
msg.hash.len = dst.len;
@@ -455,6 +457,8 @@ int ipcp_flow_alloc(const struct flow_info * flow,
msg.flow_id = flow->id;
msg.has_pid = true;
msg.pid = flow->n_pid;
+ msg.has_uid = true;
+ msg.uid = flow->uid;
msg.qosspec = qos_spec_s_to_msg(&flow->qs);
msg.has_hash = true;
msg.hash.data = (uint8_t *) dst.data;
@@ -495,6 +499,8 @@ int ipcp_flow_alloc_resp(const struct flow_info * flow,
msg.flow_id = flow->id;
msg.has_pid = true;
msg.pid = flow->n_pid;
+ msg.has_uid = true;
+ msg.uid = flow->uid;
msg.has_response = true;
msg.response = response;
msg.has_pk = response == 0;
diff --git a/src/irmd/ipcp.h b/src/irmd/ipcp.h
index b7413cd2..f1025096 100644
--- a/src/irmd/ipcp.h
+++ b/src/irmd/ipcp.h
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* The API for the IRM to instruct IPCPs
*
diff --git a/src/irmd/irmd.h b/src/irmd/irmd.h
index 3e54904a..f88378ad 100644
--- a/src/irmd/irmd.h
+++ b/src/irmd/irmd.h
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* The IPC Resource Manager
*
diff --git a/src/irmd/main.c b/src/irmd/main.c
index daaf4129..e610a015 100644
--- a/src/irmd/main.c
+++ b/src/irmd/main.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* The IPC Resource Manager
*
@@ -22,8 +22,10 @@
#if defined(__linux__) || defined(__CYGWIN__)
#define _DEFAULT_SOURCE
+#define _GNU_SOURCE
#else
-#define _POSIX_C_SOURCE 200809L
+#define _DEFAULT_SOURCE
+#define _BSD_SOURCE
#endif
#include "config.h"
@@ -39,10 +41,11 @@
#include <ouroboros/list.h>
#include <ouroboros/lockfile.h>
#include <ouroboros/logs.h>
+#include <ouroboros/protobuf.h>
#include <ouroboros/pthread.h>
#include <ouroboros/random.h>
#include <ouroboros/rib.h>
-#include <ouroboros/shm_rdrbuff.h>
+#include <ouroboros/ssm_pool.h>
#include <ouroboros/sockets.h>
#include <ouroboros/time.h>
#include <ouroboros/tpm.h>
@@ -56,15 +59,21 @@
#include "configfile.h"
#include <dirent.h>
-#include <sys/socket.h>
-#include <sys/un.h>
+#include <grp.h>
+#include <pwd.h>
#include <signal.h>
+#include <spawn.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
+#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/wait.h>
-#include <spawn.h>
+#include <sys/un.h>
+#ifdef __APPLE__
+#include <sys/types.h>
+#include <unistd.h>
+#endif
#ifdef HAVE_LIBGCRYPT
#include <gcrypt.h>
@@ -77,6 +86,7 @@
#define TIMESYNC_SLACK 100 /* ms */
#define OAP_SEEN_TIMER 20 /* s */
#define DEALLOC_TIME 300 /* s */
+#define DIRECT_MPL 1 /* s */
enum irm_state {
IRMD_NULL = 0,
@@ -85,13 +95,6 @@ enum irm_state {
IRMD_SHUTDOWN
};
-struct oaph {
- struct list_head next;
-
- uint64_t stamp;
- uint8_t id[OAP_ID_SIZE];
-};
-
struct cmd {
struct list_head next;
@@ -105,14 +108,8 @@ struct {
#ifdef HAVE_TOML
char * cfg_file; /* configuration file path */
#endif
- struct {
- struct auth_ctx * ctx; /* default authentication ctx */
- struct list_head list; /* OAP headers seen before */
- pthread_mutex_t mtx; /* mutex for OAP headers */
- } auth;
-
struct lockfile * lf; /* single irmd per system */
- struct shm_rdrbuff * rdrb; /* rdrbuff for packets */
+ struct ssm_pool * gspp; /* pool for packets */
int sockfd; /* UNIX socket */
@@ -456,7 +453,7 @@ static void name_update_sec_paths(struct name_info * info)
assert(info != NULL);
if (strlen(info->s.enc) == 0)
- sprintf(info->s.enc, "%s/%s/enc.cfg", srv_dir, info->name);
+ sprintf(info->s.enc, "%s/%s/enc.conf", srv_dir, info->name);
if (strlen(info->s.crt) == 0)
sprintf(info->s.crt, "%s/%s/crt.pem", srv_dir, info->name);
@@ -465,7 +462,7 @@ static void name_update_sec_paths(struct name_info * info)
sprintf(info->s.key, "%s/%s/key.pem", srv_dir, info->name);
if (strlen(info->c.enc) == 0)
- sprintf(info->c.enc, "%s/%s/enc.cfg", cli_dir, info->name);
+ sprintf(info->c.enc, "%s/%s/enc.conf", cli_dir, info->name);
if (strlen(info->c.crt) == 0)
sprintf(info->c.crt, "%s/%s/crt.pem", cli_dir, info->name);
@@ -613,18 +610,21 @@ static int unbind_program(const char * prog,
if (name == NULL) {
if (reg_destroy_prog(prog) < 0) {
log_err("Failed to unbind %s.", prog);
- return -1;
+ goto fail;
}
log_info("Program %s unbound.", prog);
} else {
if (reg_unbind_prog(name, prog) < 0) {
log_err("Failed to unbind %s from %s", prog, name);
- return -1;
+ goto fail;
}
log_info("Name %s unbound for %s.", name, prog);
}
return 0;
+
+ fail:
+ return -1;
}
static int unbind_process(pid_t pid,
@@ -633,18 +633,21 @@ static int unbind_process(pid_t pid,
if (name == NULL) {
if (reg_destroy_proc(pid) < 0) {
log_err("Failed to unbind %d.", pid);
- return -1;
+ goto fail;
}
log_info("Process %d unbound.", pid);
} else {
if (reg_unbind_proc(name, pid) < 0) {
log_err("Failed to unbind %d from %s", pid, name);
- return -1;
+ goto fail;
}
log_info("Name %s unbound for process %d.", name, pid);
}
return 0;
+
+ fail:
+ return -1;
}
static int list_ipcps(ipcp_list_msg_t *** ipcps,
@@ -716,8 +719,7 @@ int name_reg(const char * name,
if (ipcp_reg(pid, hash)) {
log_err("Could not register " HASH_FMT32 " with IPCP %d.",
HASH_VAL32(hash.data), pid);
- freebuf(hash);
- return -1;
+ goto fail_hash;
}
log_info("Registered %s with IPCP %d as " HASH_FMT32 ".",
@@ -726,6 +728,10 @@ int name_reg(const char * name,
freebuf(hash);
return 0;
+
+ fail_hash:
+ freebuf(hash);
+ return -1;
}
static int name_unreg(const char * name,
@@ -760,8 +766,7 @@ static int name_unreg(const char * name,
if (ipcp_unreg(pid, hash)) {
log_err("Could not unregister %s with IPCP %d.", name, pid);
- freebuf(hash);
- return -1;
+ goto fail_hash;
}
log_info("Unregistered %s from %d.", name, pid);
@@ -769,20 +774,53 @@ static int name_unreg(const char * name,
freebuf(hash);
return 0;
+
+ fail_hash:
+ freebuf(hash);
+ return -1;
+}
+
+static int get_peer_ids(int fd,
+ uid_t * uid,
+ gid_t * gid)
+{
+#if defined(__linux__)
+ struct ucred ucred;
+ socklen_t len;
+
+ len = sizeof(ucred);
+
+ if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) < 0)
+ goto fail;
+
+ *uid = ucred.uid;
+ *gid = ucred.gid;
+#else
+ if (getpeereid(fd, uid, gid) < 0)
+ goto fail;
+#endif
+ return 0;
+ fail:
+ return -1;
}
static int proc_announce(const struct proc_info * info)
{
+ if (reg_prepare_pool(info->uid, info->gid) < 0) {
+ log_err("Failed to prepare pool for uid %d.", info->uid);
+ goto fail;
+ }
+
if (reg_create_proc(info) < 0) {
log_err("Failed to add process %d.", info->pid);
- goto fail_proc;
+ goto fail;
}
log_info("Process added: %d (%s).", info->pid, info->prog);
return 0;
- fail_proc:
+ fail:
return -1;
}
@@ -796,266 +834,26 @@ static int proc_exit(pid_t pid)
return 0;
}
-static void __cleanup_pkp(void * pkp)
-{
- if (pkp != NULL)
- crypt_dh_pkp_destroy(pkp);
-}
-
static void __cleanup_flow(void * flow)
{
reg_destroy_flow(((struct flow_info *) flow)->id);
}
-static bool file_exists(const char * path)
-{
- struct stat s;
-
- if (stat(path, &s) < 0 && errno == ENOENT) {
- log_dbg("File %s does not exist.", path);
- return false;
- }
-
- return true;
-}
-
-static int load_credentials(const char * name,
- const struct name_sec_paths * paths,
- void ** pkp,
- void ** crt,
- bool * crypt)
-{
- assert(paths != NULL);
- assert(pkp != NULL);
- assert(crt != NULL);
-
- *pkp = NULL;
- *crt = NULL;
-
- /* TODO: Allow configuration. For now, encrypt if path exists */
- *crypt = file_exists(paths->enc);
- if (*crypt)
- log_info("Encryption enabled for %s.", name);
-
- if (!file_exists(paths->crt) || !file_exists(paths->key)) {
- log_info("No security info for %s.", name);
- return 0;
- }
-
- if (crypt_load_crt_file(paths->crt, crt) < 0) {
- log_err("Failed to load %s for %s.", paths->crt, name);
- goto fail_crt;
- }
-
- if (crypt_load_privkey_file(paths->key, pkp) < 0) {
- log_err("Failed to load %s for %s.", paths->key, name);
- goto fail_key;
- }
-
- log_info("Loaded security keys for %s.", name);
-
- return 0;
-
- fail_key:
- crypt_free_crt(*crt);
- *crt = NULL;
- fail_crt:
- return -EAUTH;
-}
-
-static int load_srv_credentials(const char * name,
- void ** pkp,
- void ** crt,
- bool * crypt)
-{
- struct name_info info;
-
- assert(name != NULL);
- assert(pkp != NULL);
- assert(crt != NULL);
-
- if (reg_get_name_info(name, &info) < 0) {
- log_err("Failed to get name info for %s.", name);
- return -ENAME;
- }
-
- return load_credentials(name, &info.s, pkp, crt, crypt);
-}
-
-static int load_cli_credentials(const char * name,
- void ** pkp,
- void ** crt,
- bool * crypt)
-{
- struct name_info info;
-
- assert(name != NULL);
- assert(pkp != NULL);
- assert(crt != NULL);
-
- if (reg_get_name_info(name, &info) < 0) {
- log_err("Failed to get name info for %s.", name);
- return -ENAME;
- }
-
- return load_credentials(name, &info.c, pkp, crt, crypt);
-}
-
-#define ID_IS_EQUAL(id1, id2) (memcmp(id1, id2, OAP_ID_SIZE) == 0)
-static int irm_check_oap_hdr(const struct oap_hdr * oap_hdr,
- time_t mpl)
-{
- struct list_head * p;
- struct list_head * h;
- struct timespec now;
- struct oaph * new;
- uint64_t stamp;
- uint64_t cur;
- uint8_t * id;
- ssize_t delta;
-
- assert(oap_hdr != NULL);
-
- stamp = oap_hdr->timestamp;
- id = oap_hdr->id.data;
-
- clock_gettime(CLOCK_REALTIME, &now);
-
- cur = TS_TO_UINT64(now);
-
- delta = (ssize_t)(cur - stamp) / MILLION;
- if (delta > mpl)
- log_warn("Transit time exceeds MPL by %zd ms.", delta);
- if (delta < -TIMESYNC_SLACK)
- log_warn("OAP header sent %zd ms from the future.", -delta);
-
- new = malloc(sizeof(*new));
- if (new == NULL) {
- log_err("Failed to allocate memory for OAP element.");
- return -ENOMEM;
- }
-
- pthread_mutex_lock(&irmd.auth.mtx);
-
- list_for_each_safe(p, h, &irmd.auth.list) {
- struct oaph * oaph = list_entry(p, struct oaph, next);
- if (cur > oaph->stamp + OAP_SEEN_TIMER * BILLION) {
- list_del(&oaph->next);
- free(oaph);
- continue;
- }
-
- if (oaph->stamp == stamp && ID_IS_EQUAL(oaph->id, id)) {
- log_warn("OAP header already known: " HASH_FMT64 ".",
- HASH_VAL64(id));
- goto fail_replay;
- }
- }
-
- memcpy(new->id, id, OAP_ID_SIZE);
- new->stamp = stamp;
-
- list_add_tail(&new->next, &irmd.auth.list);
-
- pthread_mutex_unlock(&irmd.auth.mtx);
-
- return 0;
-
- fail_replay:
- pthread_mutex_unlock(&irmd.auth.mtx);
- free(new);
- return -EAUTH;
-}
-
-static int irm_auth_peer(const char * name,
- const struct oap_hdr * oap_hdr,
- const struct oap_hdr * r_oap_hdr)
-{
- void * crt;
- void * pk;
- buffer_t sign;
- const char * n = name == NULL ? "<client>" : name;
-
- if (memcmp(r_oap_hdr->id.data, oap_hdr->id.data, OAP_ID_SIZE) != 0) {
- log_err("OAP ID mismatch in flow allocation.");
- goto fail_check;
- }
-
- if (r_oap_hdr->crt.len == 0) {
- log_info("No certificate provided by %s.", n);
- return 0;
- }
-
- if (crypt_load_crt_der(r_oap_hdr->crt, &crt) < 0) {
- log_err("Failed to load certificate from %s.", n);
- goto fail_check;
- }
-
- log_dbg("Loaded peer certificate for %s.", n);
-
- if (name != NULL) {
- if (crypt_check_crt_name(crt, n) < 0) {
- log_err("Certificate does not match %s.", n);
- goto fail_crt;
- }
- log_dbg("Certificate matches name %s.", n);
- }
-
- if (crypt_get_pubkey_crt(crt, &pk) < 0) {
- log_err("Failed to get pubkey from certificate for %s.", n);
- goto fail_crt;
- }
-
- log_dbg("Got public key from certificate for %s.", n);
-
- if (auth_verify_crt(irmd.auth.ctx, crt) < 0) {
- log_err("Failed to verify peer %s with CA store.", n);
- goto fail_crt;
- }
-
- log_info("Successfully verified peer certificate for %s.", n);
-
- sign = r_oap_hdr->hdr;
- sign.len -= (r_oap_hdr->sig.len + sizeof(uint16_t));
-
- if (auth_verify_sig(pk, sign, r_oap_hdr->sig) < 0) {
- log_err("Failed to verify signature for peer %s.", n);
- goto fail_check_sig;
- }
-
- crypt_free_key(pk);
- crypt_free_crt(crt);
-
- log_info("Successfully authenticated %s.", n);
-
- return 0;
-
- fail_check_sig:
- crypt_free_key(pk);
- fail_crt:
- crypt_free_crt(crt);
- fail_check:
- return -1;
-}
-
static int flow_accept(struct flow_info * flow,
- buffer_t * symmkey,
buffer_t * data,
- struct timespec * abstime)
+ struct timespec * abstime,
+ struct crypt_sk * sk)
{
- struct oap_hdr oap_hdr; /* incoming request */
- struct oap_hdr r_oap_hdr; /* outgoing response */
- uint8_t buf[MSGBUFSZ]; /* buffer for local ephkey */
- buffer_t lpk = BUF_INIT; /* local ephemeral pubkey */
- char name[NAME_SIZE + 1]; /* name for flow */
- void * pkp = NULL; /* signing private key */
- void * crt = NULL; /* signing certificate */
- int err;
- bool crypt;
+ buffer_t req_hdr;
+ buffer_t resp_hdr;
+ char name[NAME_SIZE + 1];
+ struct name_info info;
+ int err;
- /* piggyback of user data not yet implemented */
assert(data != NULL && BUF_IS_EMPTY(data));
- assert(symmkey != NULL && BUF_IS_EMPTY(symmkey));
+
+ clrbuf(req_hdr);
+ clrbuf(resp_hdr);
if (!reg_has_proc(flow->n_pid)) {
log_err("Unknown process %d calling accept.", flow->n_pid);
@@ -1077,7 +875,7 @@ static int flow_accept(struct flow_info * flow,
pthread_cleanup_push(__cleanup_flow, flow);
- err = reg_wait_flow_accepted(flow, &oap_hdr.hdr, abstime);
+ err = reg_wait_flow_accepted(flow, &req_hdr, abstime);
pthread_cleanup_pop(false);
@@ -1097,119 +895,49 @@ static int flow_accept(struct flow_info * flow,
if (reg_get_name_for_flow_id(name, flow->id) < 0) {
log_err("Failed to get name for flow %d.", flow->id);
err = -EIPCP;
- goto fail_cred;
+ goto fail_oap;
+ }
+
+ if (reg_get_name_info(name, &info) < 0) {
+ log_err("Failed to get name info for %s.", name);
+ err = -ENAME;
+ goto fail_oap;
}
log_dbg("IPCP %d accepting flow %d for %s.",
flow->n_pid, flow->id, name);
- if (load_srv_credentials(name, &pkp, &crt, &crypt) < 0) {
- log_err("Failed to load security keys for %s.", name);
- err = -EAUTH;
- goto fail_cred;
- }
-
- if (oap_hdr_decode(oap_hdr.hdr, &oap_hdr) < 0) {
- log_err("Failed to decode OAP header from %s.", name);
- err = -EIPCP;
- goto fail_oap_hdr;
- }
-#ifdef DEBUG_PROTO_OAP
- debug_oap_hdr_rcv(&oap_hdr);
-#endif
- if (irm_check_oap_hdr(&oap_hdr, flow->mpl) < 0) {
- log_err("OAP header failed replay check.");
- goto fail_oap_hdr;
- }
+ flow->uid = reg_get_proc_uid(flow->n_pid);
- if (crypt && oap_hdr.eph.len == 0) {
- log_warn("Encryption required but no key provided.");
- err = -ECRYPT;
- goto fail_oap_hdr;
+ err = oap_srv_process(&info, req_hdr, &resp_hdr, data, sk);
+ if (err < 0) {
+ log_err("OAP processing failed for %s.", name);
+ goto fail_oap;
}
- if (oap_hdr.eph.len > 0) { /* crypto requested */
- uint8_t * s; /* symmetric encryption key */
- ssize_t key_len; /* length of local pubkey */
- void * pkp = NULL; /* ephemeral private key pair */
-
- s = malloc(SYMMKEYSZ);
- if (s == NULL) {
- log_err("Failed to malloc symmkey.");
- err = -ENOMEM;
- goto fail_keys;
+ if (reg_flow_is_direct(flow->id)) {
+ if (reg_respond_flow_direct(flow->id, &resp_hdr) < 0) {
+ log_err("Failed to respond to direct flow.");
+ goto fail_resp;
}
-
- key_len = crypt_dh_pkp_create(&pkp, buf);
- if (key_len < 0) {
- free(s);
- log_err("Failed to generate key pair.");
- err = -ECRYPT;
- goto fail_keys;
- }
-
- lpk.data = buf;
- lpk.len = (size_t) key_len;
-
- log_dbg("Generated ephemeral keys for %d.", flow->n_pid);
-
- if (crypt_dh_derive(pkp, oap_hdr.eph, s) < 0) {
- log_err("Failed to derive secret for %d.", flow->id);
- crypt_dh_pkp_destroy(pkp);
- free(s);
- err = -ECRYPT;
- goto fail_derive;
- }
-
- symmkey->data = s;
- symmkey->len = SYMMKEYSZ;
-
- crypt_dh_pkp_destroy(pkp);
- }
-
- if (oap_hdr_init(oap_hdr.id, pkp, crt, lpk, *data, &r_oap_hdr) < 0) {
- log_err("Failed to create OAP header.");
- err = -ENOMEM;
- goto fail_r_oap_hdr;
- }
-
- if (irm_auth_peer(NULL, &r_oap_hdr, &oap_hdr) < 0) {
- log_err("Failed to auth %s client, flow %d.", name, flow->id);
- err = -EAUTH;
- goto fail_r_oap_hdr;
- }
-
- crypt_free_crt(crt);
- crypt_free_key(pkp);
-
-#ifdef DEBUG_PROTO_OAP
- debug_oap_hdr_snd(&r_oap_hdr);
-#endif
- if (ipcp_flow_alloc_resp(flow, 0, r_oap_hdr.hdr) < 0) {
+ log_info("Flow %d accepted (direct) by %d for %s.",
+ flow->id, flow->n_pid, name);
+ } else if (ipcp_flow_alloc_resp(flow, 0, resp_hdr) < 0) {
log_err("Failed to respond to flow allocation.");
goto fail_resp;
+ } else {
+ log_info("Flow %d accepted by %d for %s (uid %d).",
+ flow->id, flow->n_pid, name, flow->uid);
}
- log_info("Flow %d accepted by %d for %s.",
- flow->id, flow->n_pid, name);
-
- oap_hdr_fini(&oap_hdr);
- oap_hdr_fini(&r_oap_hdr);
+ freebuf(req_hdr);
+ freebuf(resp_hdr);
return 0;
- fail_r_oap_hdr:
- freebuf(*symmkey);
- fail_derive:
- clrbuf(lpk);
- fail_keys:
- oap_hdr_fini(&oap_hdr);
- fail_oap_hdr:
- crypt_free_crt(crt);
- crypt_free_key(pkp);
- fail_cred:
- assert(lpk.data == NULL && lpk.len == 0);
- ipcp_flow_alloc_resp(flow, err, lpk);
+ fail_oap:
+ if (!reg_flow_is_direct(flow->id))
+ ipcp_flow_alloc_resp(flow, err, resp_hdr);
fail_wait:
reg_destroy_flow(flow->id);
fail_flow:
@@ -1217,10 +945,8 @@ static int flow_accept(struct flow_info * flow,
fail_resp:
flow->state = FLOW_NULL;
- oap_hdr_fini(&r_oap_hdr);
- freebuf(*symmkey);
- clrbuf(lpk);
- oap_hdr_fini(&oap_hdr);
+ freebuf(req_hdr);
+ freebuf(resp_hdr);
reg_destroy_flow(flow->id);
return -EIPCP;
}
@@ -1235,14 +961,17 @@ static int flow_join(struct flow_info * flow,
buffer_t pbuf = BUF_INIT; /* nothing to piggyback */
int err;
- log_info("Allocating flow for %d to %s.", flow->n_pid, dst);
-
if (reg_create_flow(flow) < 0) {
log_err("Failed to create flow.");
err = -EBADF;
goto fail_flow;
}
+ flow->uid = reg_get_proc_uid(flow->n_pid);
+
+ log_info("Allocating flow for %d to %s (uid %d).",
+ flow->n_pid, dst, flow->uid);
+
strcpy(layer.name, dst);
if (reg_get_ipcp_by_layer(&ipcp, &layer) < 0) {
log_err("Failed to get IPCP for layer %s.", dst);
@@ -1308,7 +1037,7 @@ static int get_ipcp_by_dst(const char * dst,
pid_t * pid,
buffer_t * hash)
{
- ipcp_list_msg_t ** ipcps;
+ ipcp_list_msg_t ** ipcps = NULL;
int n;
int i;
int err = -EIPCP;
@@ -1361,196 +1090,6 @@ static int get_ipcp_by_dst(const char * dst,
return err;
}
-static int flow_alloc(struct flow_info * flow,
- const char * dst,
- buffer_t * symmkey,
- buffer_t * data,
- struct timespec * abstime)
-{
- struct oap_hdr oap_hdr; /* outgoing request */
- struct oap_hdr r_oap_hdr; /* incoming response */
- uint8_t buf[MSGBUFSZ]; /* buffer for local ephkey */
- buffer_t lpk = BUF_INIT; /* local ephemeral pubkey */
- void * pkp = NULL; /* ephemeral private key pair */
- uint8_t * s = NULL; /* symmetric key */
- void * cpkp = NULL; /* signing private key */
- void * ccrt = NULL; /* signing certificate */
- buffer_t hash;
- uint8_t idbuf[OAP_ID_SIZE];
- buffer_t id;
- int err;
- bool crypt;
-
- /* piggyback of user data not yet implemented */
- assert(data != NULL && BUF_IS_EMPTY(data));
- assert(symmkey != NULL && BUF_IS_EMPTY(symmkey));
-
- log_info("Allocating flow for %d to %s.", flow->n_pid, dst);
-
- if (random_buffer(idbuf, OAP_ID_SIZE) < 0) {
- log_err("Failed to generate ID.");
- err = -EIRMD;
- goto fail_id;
- }
-
- id.data = idbuf;
- id.len = OAP_ID_SIZE;
-
- if (load_cli_credentials(dst, &cpkp, &ccrt, &crypt) < 0) {
- log_err("Failed to load security keys for %s.", dst);
- err = -EAUTH;
- goto fail_cred;
- }
-
- if (crypt > 0) {
- ssize_t key_len;
-
- s = malloc(SYMMKEYSZ);
- if (s == NULL) {
- log_err("Failed to malloc symmetric key");
- err = -ENOMEM;
- goto fail_malloc;
- }
-
- key_len = crypt_dh_pkp_create(&pkp, buf);
- if (key_len < 0) {
- log_err("Failed to generate key pair.");
- err = -ECRYPT;
- goto fail_pkp;
- }
-
- lpk.data = buf;
- lpk.len = (size_t) key_len;
-
- log_dbg("Generated ephemeral keys for %d.", flow->n_pid);
- }
-
- if (oap_hdr_init(id, cpkp, ccrt, lpk, *data, &oap_hdr) < 0) {
- log_err("Failed to create OAP header.");
- err = -ENOMEM;
- goto fail_oap_hdr;
- }
-#ifdef DEBUG_PROTO_OAP
- debug_oap_hdr_snd(&oap_hdr);
-#endif
- if (reg_create_flow(flow) < 0) {
- log_err("Failed to create flow.");
- err = -EBADF;
- goto fail_flow;
- }
-
- if (get_ipcp_by_dst(dst, &flow->n_1_pid, &hash) < 0) {
- log_err("Failed to find IPCP for %s.", dst);
- err = -EIPCP;
- goto fail_ipcp;
- }
-
- reg_prepare_flow_alloc(flow);
-
- if (ipcp_flow_alloc(flow, hash, oap_hdr.hdr)) {
- log_err("Flow allocation %d failed.", flow->id);
- err = -ENOTALLOC;
- goto fail_alloc;
- }
-
- pthread_cleanup_push(__cleanup_flow, flow);
- pthread_cleanup_push(__cleanup_pkp, pkp);
- pthread_cleanup_push(free, hash.data);
- pthread_cleanup_push(free, s);
-
- err = reg_wait_flow_allocated(flow, &r_oap_hdr.hdr, abstime);
-
- pthread_cleanup_pop(false);
- pthread_cleanup_pop(false);
- pthread_cleanup_pop(false);
- pthread_cleanup_pop(false);
-
- if (err == -ETIMEDOUT) {
- log_err("Flow allocation timed out.");
- goto fail_alloc;
- }
-
- if (err == -1) {
- log_dbg("Flow allocation terminated.");
- err = -EIPCP;
- goto fail_alloc;
- }
-
- log_dbg("Response received for flow %d to %s.", flow->id, dst);
-
- if (err < 0) {
- log_warn("Flow allocation rejected for %s: %d.", dst, err);
- goto fail_alloc;
- }
-
- if (oap_hdr_decode(r_oap_hdr.hdr, &r_oap_hdr) < 0) {
- log_err("Failed to decode OAP header.");
- err = -EIPCP;
- goto fail_r_oap_hdr;
- }
-#ifdef DEBUG_PROTO_OAP
- debug_oap_hdr_rcv(&r_oap_hdr);
-#endif
- if (irm_check_oap_hdr(&r_oap_hdr, flow->mpl) < 0) {
- log_err("OAP header failed replay check.");
- err = -EAUTH;
- goto fail_r_oap_hdr;
- }
-
- if (irm_auth_peer(dst, &oap_hdr, &r_oap_hdr) < 0) {
- log_err("Failed to authenticate %s (flow %d).", dst, flow->id);
- err = -EAUTH;
- goto fail_r_oap_hdr;
- }
-
- if (lpk.len > 0) { /* crypto requested */
- if (crypt_dh_derive(pkp, r_oap_hdr.eph, s) < 0) {
- log_err("Failed to derive secret for %d.", flow->id);
- err = -ECRYPT;
- goto fail_r_oap_hdr;
- }
- crypt_dh_pkp_destroy(pkp);
-
- symmkey->data = s;
- symmkey->len = SYMMKEYSZ;
- s = NULL;
- }
-
- oap_hdr_fini(&r_oap_hdr);
- oap_hdr_fini(&oap_hdr);
-
- crypt_free_crt(ccrt);
- crypt_free_key(cpkp);
-
- /* TODO: piggyback user data if needed */
-
- freebuf(hash);
- free(s);
-
- return 0;
-
- fail_r_oap_hdr:
- flow->state = FLOW_DEALLOCATED;
- oap_hdr_fini(&r_oap_hdr);
- fail_alloc:
- freebuf(hash);
- fail_ipcp:
- reg_destroy_flow(flow->id);
- fail_flow:
- oap_hdr_fini(&oap_hdr);
- fail_oap_hdr:
- crypt_dh_pkp_destroy(pkp);
- fail_pkp:
- free(s);
- fail_malloc:
- crypt_free_crt(ccrt);
- crypt_free_key(cpkp);
- fail_cred:
- clrbuf(id);
- fail_id:
- return err;
-}
-
static int wait_for_accept(const char * name)
{
struct timespec timeo = TIMESPEC_INIT_MS(IRMD_REQ_ARR_TIMEOUT);
@@ -1643,6 +1182,194 @@ static int flow_req_arr(struct flow_info * flow,
return ret;
}
+#ifndef DISABLE_DIRECT_IPC
+static int flow_alloc_direct(const char * dst,
+ struct flow_info * flow,
+ buffer_t * data,
+ struct timespec * abstime,
+ struct crypt_sk * sk,
+ struct name_info * info)
+{
+ struct flow_info acc; /* server side flow */
+ buffer_t req_hdr = BUF_INIT;
+ buffer_t resp_hdr = BUF_INIT;
+ void * ctx;
+ int err;
+
+ acc.id = wait_for_accept(dst);
+ if (acc.id < 0) {
+ log_dbg("No accepting process for %s.", dst);
+ return -EAGAIN;
+ }
+
+ if (oap_cli_prepare(&ctx, info, &req_hdr, *data) < 0) {
+ log_err("Failed to prepare OAP for %s.", dst);
+ return -EBADF;
+ }
+
+ acc.n_1_pid = flow->n_pid;
+ acc.mpl = DIRECT_MPL;
+ acc.qs = flow->qs;
+ acc.state = FLOW_ALLOCATED;
+
+ err = reg_prepare_flow_direct(&acc, &req_hdr, flow->uid);
+ if (err == -EPERM) {
+ log_dbg("UID mismatch, falling back.");
+ oap_ctx_free(ctx);
+ freebuf(req_hdr);
+ return -EPERM;
+ }
+
+ if (err < 0) {
+ log_err("Failed to prepare direct flow.");
+ oap_ctx_free(ctx);
+ freebuf(req_hdr);
+ return -EBADF;
+ }
+
+ err = reg_wait_flow_direct(acc.id, &resp_hdr, abstime);
+ if (err < 0) {
+ log_err("Timeout waiting for OAP response.");
+ oap_ctx_free(ctx);
+ return -ETIMEDOUT;
+ }
+
+ err = oap_cli_complete(ctx, info, resp_hdr, data, sk);
+ if (err < 0) {
+ log_err("OAP completion failed for %s.", dst);
+ freebuf(resp_hdr);
+ return err;
+ }
+
+ flow->id = acc.id;
+ flow->n_1_pid = acc.n_pid;
+ flow->mpl = DIRECT_MPL;
+ flow->state = FLOW_ALLOCATED;
+
+ log_info("Flow %d allocated (direct) for %d to %s.",
+ flow->id, flow->n_pid, dst);
+
+ freebuf(resp_hdr);
+
+ return 0;
+}
+#endif /* DISABLE_DIRECT_IPC */
+
+static int flow_alloc(const char * dst,
+ struct flow_info * flow,
+ buffer_t * data,
+ struct timespec * abstime,
+ struct crypt_sk * sk)
+{
+ buffer_t req_hdr = BUF_INIT;
+ buffer_t resp_hdr = BUF_INIT;
+ buffer_t hash = BUF_INIT;
+ struct name_info info;
+ void * ctx;
+ int err;
+
+ /* piggyback of user data not yet implemented */
+ assert(data != NULL && BUF_IS_EMPTY(data));
+
+ /* Look up name_info for dst */
+ if (reg_get_name_info(dst, &info) < 0) {
+ log_err("Failed to get name info for %s.", dst);
+ err = -ENAME;
+ goto fail_flow;
+ }
+
+ flow->uid = reg_get_proc_uid(flow->n_pid);
+
+ log_info("Allocating flow for %d to %s (uid %d).",
+ flow->n_pid, dst, flow->uid);
+
+#ifndef DISABLE_DIRECT_IPC
+ err = flow_alloc_direct(dst, flow, data, abstime, sk, &info);
+ if (err == 0)
+ return 0;
+
+ if (err != -EPERM && err != -EAGAIN)
+ goto fail_flow;
+#endif
+ if (reg_create_flow(flow) < 0) {
+ log_err("Failed to create flow.");
+ err = -EBADF;
+ goto fail_flow;
+ }
+
+ if (get_ipcp_by_dst(dst, &flow->n_1_pid, &hash) < 0) {
+ log_err("Failed to find IPCP for %s.", dst);
+ err = -EIPCP;
+ goto fail_ipcp;
+ }
+
+ if (reg_prepare_flow_alloc(flow) < 0) {
+ log_err("Failed to prepare flow allocation.");
+ err = -EBADF;
+ goto fail_prepare;
+ }
+
+ if (oap_cli_prepare(&ctx, &info, &req_hdr, *data) < 0) {
+ log_err("Failed to prepare OAP request for %s.", dst);
+ err = -EBADF;
+ goto fail_prepare;
+ }
+
+ if (ipcp_flow_alloc(flow, hash, req_hdr)) {
+ log_err("Flow allocation %d failed.", flow->id);
+ err = -EIPCP;
+ goto fail_alloc;
+ }
+
+ pthread_cleanup_push(__cleanup_flow, flow);
+ pthread_cleanup_push(free, hash.data);
+
+ err = reg_wait_flow_allocated(flow, &resp_hdr, abstime);
+
+ pthread_cleanup_pop(false);
+ pthread_cleanup_pop(false);
+
+ if (err == -ETIMEDOUT) {
+ log_err("Flow allocation timed out.");
+ goto fail_wait;
+ }
+
+ log_dbg("Response for flow %d to %s.", flow->id, dst);
+
+ if (err < 0) {
+ log_warn("Allocation rejected: %s (%d).", dst, err);
+ goto fail_peer;
+ }
+
+ err = oap_cli_complete(ctx, &info, resp_hdr, data, sk);
+ if (err < 0) {
+ log_err("OAP completion failed for %s.", dst);
+ goto fail_complete;
+ }
+
+ freebuf(req_hdr);
+ freebuf(resp_hdr);
+ freebuf(hash);
+
+ return 0;
+
+ fail_complete:
+ ctx = NULL; /* freee'd on complete */
+ fail_peer:
+ flow->state = FLOW_DEALLOCATED;
+ fail_wait:
+ freebuf(resp_hdr);
+ fail_alloc:
+ freebuf(req_hdr);
+ oap_ctx_free(ctx);
+ fail_prepare:
+ freebuf(hash);
+ fail_ipcp:
+ reg_destroy_flow(flow->id);
+ fail_flow:
+ return err;
+}
+
static int flow_alloc_reply(struct flow_info * flow,
int response,
buffer_t * data)
@@ -1666,6 +1393,12 @@ static int flow_dealloc(struct flow_info * flow,
reg_dealloc_flow(flow);
+ if (reg_flow_is_direct(flow->id)) {
+ if (flow->state == FLOW_DEALLOCATED)
+ reg_destroy_flow(flow->id);
+ return 0;
+ }
+
if (ipcp_flow_dealloc(flow->n_1_pid, flow->id, ts->tv_sec) < 0) {
log_err("Failed to request dealloc from %d.", flow->n_1_pid);
return -EIPCP;
@@ -1742,21 +1475,24 @@ static void __cleanup_irm_msg(void * o)
irm_msg__free_unpacked((irm_msg_t *) o, NULL);
}
-static irm_msg_t * do_command_msg(irm_msg_t * msg)
+static irm_msg_t * do_command_msg(irm_msg_t * msg,
+ int fd)
{
- struct ipcp_config conf;
- struct ipcp_info ipcp;
- struct flow_info flow;
- struct proc_info proc;
- struct name_info name;
- struct timespec * abstime;
- struct timespec max = TIMESPEC_INIT_MS(FLOW_ALLOC_TIMEOUT);
- struct timespec now;
- struct timespec ts = TIMESPEC_INIT_S(0); /* static analysis */
- int res;
- irm_msg_t * ret_msg;
- buffer_t data;
- buffer_t symmkey = BUF_INIT;;
+ struct ipcp_config conf;
+ struct ipcp_info ipcp;
+ struct flow_info flow;
+ struct proc_info proc;
+ struct name_info name;
+ struct crypt_sk sk;
+ uint8_t kbuf[SYMMKEYSZ]; /* stack buffer for OAP */
+ uint8_t * hbuf = NULL; /* heap copy for response */
+ struct timespec * abstime;
+ struct timespec max = TIMESPEC_INIT_MS(FLOW_ALLOC_TIMEOUT);
+ struct timespec now;
+ struct timespec ts = TIMESPEC_INIT_S(0); /* static analysis */
+ int res;
+ irm_msg_t * ret_msg;
+ buffer_t data;
memset(&flow, 0, sizeof(flow));
@@ -1821,7 +1557,11 @@ static irm_msg_t * do_command_msg(irm_msg_t * msg)
case IRM_MSG_CODE__IRM_PROC_ANNOUNCE:
proc.pid = msg->pid;
strcpy(proc.prog, msg->prog);
- res = proc_announce(&proc);
+ res = get_peer_ids(fd, &proc.uid, &proc.gid);
+ if (res < 0)
+ log_err("Failed to get UID/GID for pid %d.", msg->pid);
+ else
+ res = proc_announce(&proc);
break;
case IRM_MSG_CODE__IRM_PROC_EXIT:
res = proc_exit(msg->pid);
@@ -1858,15 +1598,27 @@ static irm_msg_t * do_command_msg(irm_msg_t * msg)
msg->has_pk = false;
assert(data.len > 0 ? data.data != NULL : data.data == NULL);
flow = flow_info_msg_to_s(msg->flow_info);
- res = flow_accept(&flow, &symmkey, &data, abstime);
+ sk.key = kbuf;
+ res = flow_accept(&flow, &data, abstime, &sk);
if (res == 0) {
- ret_msg->flow_info = flow_info_s_to_msg(&flow);
- ret_msg->has_symmkey = symmkey.len != 0;
- ret_msg->symmkey.data = symmkey.data;
- ret_msg->symmkey.len = symmkey.len;
- ret_msg->has_pk = data.len != 0;
- ret_msg->pk.data = data.data;
- ret_msg->pk.len = data.len;
+ ret_msg->flow_info = flow_info_s_to_msg(&flow);
+ ret_msg->has_pk = data.len != 0;
+ ret_msg->pk.data = data.data;
+ ret_msg->pk.len = data.len;
+ ret_msg->has_cipher_nid = true;
+ ret_msg->cipher_nid = sk.nid;
+ if (sk.nid != NID_undef) {
+ hbuf = malloc(SYMMKEYSZ);
+ if (hbuf == NULL) {
+ log_err("Failed to malloc key buf");
+ return NULL;
+ }
+
+ memcpy(hbuf, kbuf, SYMMKEYSZ);
+ ret_msg->sym_key.data = hbuf;
+ ret_msg->sym_key.len = SYMMKEYSZ;
+ ret_msg->has_sym_key = true;
+ }
}
break;
case IRM_MSG_CODE__IRM_FLOW_ALLOC:
@@ -1876,15 +1628,26 @@ static irm_msg_t * do_command_msg(irm_msg_t * msg)
assert(data.len > 0 ? data.data != NULL : data.data == NULL);
flow = flow_info_msg_to_s(msg->flow_info);
abstime = abstime == NULL ? &max : abstime;
- res = flow_alloc(&flow, msg->dst, &symmkey, &data, abstime);
+ sk.key = kbuf;
+ res = flow_alloc(msg->dst, &flow, &data, abstime, &sk);
if (res == 0) {
- ret_msg->flow_info = flow_info_s_to_msg(&flow);
- ret_msg->has_symmkey = symmkey.len != 0;
- ret_msg->symmkey.data = symmkey.data;
- ret_msg->symmkey.len = symmkey.len;
- ret_msg->has_pk = data.len != 0;
- ret_msg->pk.data = data.data;
- ret_msg->pk.len = data.len;
+ ret_msg->flow_info = flow_info_s_to_msg(&flow);
+ ret_msg->has_pk = data.len != 0;
+ ret_msg->pk.data = data.data;
+ ret_msg->pk.len = data.len;
+ ret_msg->has_cipher_nid = true;
+ ret_msg->cipher_nid = sk.nid;
+ if (sk.nid != NID_undef) {
+ hbuf = malloc(SYMMKEYSZ);
+ if (hbuf == NULL) {
+ log_err("Failed to malloc key buf");
+ return NULL;
+ }
+ memcpy(hbuf, kbuf, SYMMKEYSZ);
+ ret_msg->sym_key.data = hbuf;
+ ret_msg->sym_key.len = SYMMKEYSZ;
+ ret_msg->has_sym_key = true;
+ }
}
break;
case IRM_MSG_CODE__IRM_FLOW_JOIN:
@@ -1938,14 +1701,16 @@ static irm_msg_t * do_command_msg(irm_msg_t * msg)
else
ret_msg->result = res;
+ crypt_secure_clear(kbuf, SYMMKEYSZ);
+
return ret_msg;
}
static void * mainloop(void * o)
{
- int sfd;
- irm_msg_t * msg;
- buffer_t buffer;
+ int sfd;
+ irm_msg_t * msg;
+ buffer_t buffer;
(void) o;
@@ -1981,7 +1746,7 @@ static void * mainloop(void * o)
pthread_cleanup_push(__cleanup_close_ptr, &sfd);
pthread_cleanup_push(__cleanup_irm_msg, msg);
- ret_msg = do_command_msg(msg);
+ ret_msg = do_command_msg(msg, sfd);
pthread_cleanup_pop(true);
pthread_cleanup_pop(false);
@@ -2074,7 +1839,7 @@ static void destroy_mount(char * mnt)
static int ouroboros_reset(void)
{
- shm_rdrbuff_purge();
+ ssm_pool_gspp_purge();
lockfile_destroy(irmd.lf);
return 0;
@@ -2095,10 +1860,8 @@ static void cleanup_pid(pid_t pid)
}
destroy_mount(mnt);
-
-#else
- (void) pid;
#endif
+ ssm_pool_reclaim_orphans(irmd.gspp, pid);
}
void * irm_sanitize(void * o)
@@ -2170,7 +1933,7 @@ static int irm_load_store(char * dpath)
goto fail_file;
}
- if (auth_add_crt_to_store(irmd.auth.ctx, crt) < 0) {
+ if (oap_auth_add_ca_crt(crt) < 0) {
log_err("Failed to add certificate from %s to store.",
path);
goto fail_crt_add;
@@ -2198,6 +1961,8 @@ static int irm_load_store(char * dpath)
static int irm_init(void)
{
struct stat st;
+ struct group * grp;
+ gid_t gid;
pthread_condattr_t cattr;
#ifdef HAVE_FUSE
mode_t mask;
@@ -2283,11 +2048,23 @@ static int irm_init(void)
goto fail_sock_path;
}
- if ((irmd.rdrb = shm_rdrbuff_create()) == NULL) {
- log_err("Failed to create rdrbuff.");
- goto fail_rdrbuff;
+ grp = getgrnam("ouroboros");
+ if (grp == NULL) {
+ log_warn("ouroboros group not found, using gid %d.", getgid());
+ gid = getgid();
+ } else {
+ gid = grp->gr_gid;
+ }
+
+ irmd.gspp = ssm_pool_create(getuid(), gid);
+ if (irmd.gspp == NULL) {
+ log_err("Failed to create GSPP.");
+ goto fail_pool;
}
+ if (ssm_pool_mlock(irmd.gspp) < 0)
+ log_warn("Failed to mlock pool.");
+
irmd.tpm = tpm_create(IRMD_MIN_THREADS, IRMD_ADD_THREADS,
mainloop, NULL);
if (irmd.tpm == NULL) {
@@ -2295,27 +2072,19 @@ static int irm_init(void)
goto fail_tpm_create;
}
- if (pthread_mutex_init(&irmd.auth.mtx, NULL) < 0) {
- log_err("Failed to initialize auth mutex.");
- goto fail_auth_mtx;
- }
-
- irmd.auth.ctx = auth_create_ctx();
- if (irmd.auth.ctx == NULL) {
- log_err("Failed to create auth store context.");
- goto fail_auth_ctx;
+ if (oap_auth_init() < 0) {
+ log_err("Failed to initialize OAP module.");
+ goto fail_oap;
}
- list_head_init(&irmd.auth.list);
-
if (irm_load_store(OUROBOROS_CA_CRT_DIR) < 0) {
log_err("Failed to load CA certificates.");
- goto fail_auth_ctx;
+ goto fail_load_store;
}
if (irm_load_store(OUROBOROS_CHAIN_DIR) < 0) {
log_err("Failed to load intermediate certificates.");
- goto fail_auth_ctx;
+ goto fail_load_store;
}
#ifdef HAVE_FUSE
@@ -2352,15 +2121,14 @@ static int irm_init(void)
#ifdef HAVE_FUSE
rmdir(FUSE_PREFIX);
#endif
- auth_destroy_ctx(irmd.auth.ctx);
#endif
- fail_auth_ctx:
- pthread_mutex_destroy(&irmd.auth.mtx);
- fail_auth_mtx:
+ fail_load_store:
+ oap_auth_fini();
+ fail_oap:
tpm_destroy(irmd.tpm);
fail_tpm_create:
- shm_rdrbuff_destroy(irmd.rdrb);
- fail_rdrbuff:
+ ssm_pool_destroy(irmd.gspp);
+ fail_pool:
close(irmd.sockfd);
fail_sock_path:
unlink(IRM_SOCK_PATH);
@@ -2388,18 +2156,7 @@ static void irm_fini(void)
if (irmd_get_state() != IRMD_INIT)
log_warn("Unsafe destroy.");
- pthread_mutex_lock(&irmd.auth.mtx);
-
- list_for_each_safe(p, h, &irmd.auth.list) {
- struct oaph * oaph = list_entry(p, struct oaph, next);
- list_del(&oaph->next);
- free(oaph);
- }
-
- pthread_mutex_unlock(&irmd.auth.mtx);
- pthread_mutex_destroy(&irmd.auth.mtx);
-
- auth_destroy_ctx(irmd.auth.ctx);
+ oap_auth_fini();
tpm_destroy(irmd.tpm);
@@ -2408,8 +2165,7 @@ static void irm_fini(void)
if (unlink(IRM_SOCK_PATH))
log_dbg("Failed to unlink %s.", IRM_SOCK_PATH);
- if (irmd.rdrb != NULL)
- shm_rdrbuff_destroy(irmd.rdrb);
+ ssm_pool_destroy(irmd.gspp);
if (irmd.lf != NULL)
lockfile_destroy(irmd.lf);
@@ -2532,10 +2288,8 @@ static void irm_argparse(int argc,
argc--;
argv++;
} else if (strcmp(*argv, "--version") == 0) {
- printf("Ouroboros version %d.%d.%d\n",
- OUROBOROS_VERSION_MAJOR,
- OUROBOROS_VERSION_MINOR,
- OUROBOROS_VERSION_PATCH);
+ printf("Ouroboros version %s\n",
+ OUROBOROS_VERSION_STRING);
exit(EXIT_SUCCESS);
#ifdef HAVE_TOML
} else if (strcmp (*argv, "--config") == 0) {
@@ -2637,6 +2391,11 @@ int main(int argc,
goto fail_reg;
}
+ if (crypt_secure_malloc_init(IRMD_SECMEM_MAX) < 0) {
+ log_err("Failed to initialize secure memory allocation.");
+ goto fail_reg;
+ }
+
pthread_sigmask(SIG_BLOCK, &sigset, NULL);
if (irm_start() < 0)
@@ -2656,6 +2415,8 @@ int main(int argc,
pthread_sigmask(SIG_UNBLOCK, &sigset, NULL);
+ crypt_secure_malloc_fini();
+
reg_clear();
reg_fini();
diff --git a/src/irmd/oap.c b/src/irmd/oap.c
index 500da6f1..1831f533 100644
--- a/src/irmd/oap.c
+++ b/src/irmd/oap.c
@@ -1,7 +1,7 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
- * Ouroboros flow allocation protocol header
+ * OAP - Shared credential and configuration loading
*
* Dimitri Staessens <dimitri@ouroboros.rocks>
* Sander Vrijders <sander@ouroboros.rocks>
@@ -29,260 +29,102 @@
#define OUROBOROS_PREFIX "irmd/oap"
#include <ouroboros/crypt.h>
-#include <ouroboros/endian.h>
+#include <ouroboros/errno.h>
#include <ouroboros/logs.h>
-#include <ouroboros/rib.h>
-#include <ouroboros/time.h>
#include "config.h"
-#include "oap.h"
-
#include <assert.h>
+#include <string.h>
+#include <sys/stat.h>
-int oap_hdr_init(buffer_t id,
- void * pkp,
- void * pubcrt,
- buffer_t ephkey,
- buffer_t data,
- struct oap_hdr * oap_hdr)
-{
- struct timespec now;
- uint64_t stamp;
- buffer_t hdr;
- buffer_t der = BUF_INIT;
- buffer_t sig = BUF_INIT;
- buffer_t sign;
- uint16_t len;
- off_t offset;
-
- assert(id.data != NULL && id.len == OAP_ID_SIZE);
- assert(oap_hdr != NULL);
- memset(oap_hdr, 0, sizeof(*oap_hdr));
-
- clock_gettime(CLOCK_REALTIME, &now);
- stamp = hton64(TS_TO_UINT64(now));
-
- if (pubcrt != NULL && crypt_crt_der(pubcrt, &der) < 0)
- goto fail_der;
-
- hdr.len = id.len +
- sizeof(stamp) +
- sizeof(len) + der.len +
- sizeof(len) + ephkey.len +
- sizeof(len) + data.len +
- sizeof(len); /* sig len */
-
- hdr.data = malloc(hdr.len);
- if (hdr.data == NULL)
- goto fail_hdr;
-
- offset = 0;
-
- memcpy(hdr.data, id.data, id.len);
- offset += id.len;
-
- memcpy(hdr.data + offset, &stamp, sizeof(stamp));
- offset += sizeof(stamp);
-
- /* pubcrt */
- len = hton16((uint16_t) der.len);
- memcpy(hdr.data + offset, &len, sizeof(len));
- offset += sizeof(len);
- if (der.len != 0)
- memcpy(hdr.data + offset, der.data, der.len);
- offset += der.len;
-
- /* ephkey */
- len = hton16((uint16_t) ephkey.len);
- memcpy(hdr.data + offset, &len, sizeof(len));
- offset += sizeof(len);
- if (ephkey.len != 0)
- memcpy(hdr.data + offset, ephkey.data, ephkey.len);
- offset += ephkey.len;
-
- /* data */
- len = hton16((uint16_t) data.len);
- memcpy(hdr.data + offset, &len, sizeof(len));
- offset += sizeof(len);
- if (data.len != 0)
- memcpy(hdr.data + offset, data.data, data.len);
- offset += data.len;
-
- sign.data = hdr.data;
- sign.len = hdr.len - sizeof(len);
-
- if (pkp != NULL && auth_sign(pkp, sign, &sig) < 0)
- goto fail_sig;
-
- len = hton16((uint16_t) sig.len);
- memcpy(hdr.data + offset, &len, sizeof(len));
- offset += sizeof(len);
-
- oap_hdr->hdr = hdr;
+/*
+ * Shared credential and configuration loading helpers
+ */
- assert((size_t) offset == hdr.len);
+#ifndef OAP_TEST_MODE
- if (sig.len > 0) {
- oap_hdr->hdr.len += sig.len;
- oap_hdr->hdr.data = realloc(hdr.data, oap_hdr->hdr.len);
- if (oap_hdr->hdr.data == NULL)
- goto fail_oap_hdr;
+static bool file_exists(const char * path)
+{
+ struct stat s;
- memcpy(oap_hdr->hdr.data + offset, sig.data, sig.len);
- clrbuf(hdr);
+ if (stat(path, &s) < 0 && errno == ENOENT) {
+ log_dbg("File %s does not exist.", path);
+ return false;
}
- if (oap_hdr_decode(oap_hdr->hdr, oap_hdr) < 0)
- goto fail_decode;
-
- freebuf(der);
- freebuf(sig);
-
- return 0;
-
- fail_decode:
- oap_hdr_fini(oap_hdr);
- fail_oap_hdr:
- freebuf(sig);
- fail_sig:
- freebuf(hdr);
- fail_hdr:
- freebuf(der);
- fail_der:
- memset(oap_hdr, 0, sizeof(*oap_hdr));
- return -1;
+ return true;
}
-void oap_hdr_fini(struct oap_hdr * oap_hdr)
+int load_credentials(const char * name,
+ const struct name_sec_paths * paths,
+ void ** pkp,
+ void ** crt)
{
- assert(oap_hdr != NULL);
-
- freebuf(oap_hdr->hdr);
- memset(oap_hdr, 0, sizeof(*oap_hdr));
-}
-
-int oap_hdr_decode(buffer_t hdr,
- struct oap_hdr * oap_hdr)
-{
- off_t offset;
-
- assert(oap_hdr != NULL);
- memset(oap_hdr, 0, sizeof(*oap_hdr));
-
- if (hdr.len < OAP_HDR_MIN_SIZE)
- goto fail_decode;
-
- oap_hdr->id.data = hdr.data;
- oap_hdr->id.len = OAP_ID_SIZE;
+ assert(paths != NULL);
+ assert(pkp != NULL);
+ assert(crt != NULL);
- offset = OAP_ID_SIZE;
+ *pkp = NULL;
+ *crt = NULL;
- oap_hdr->timestamp = ntoh64(*(uint64_t *)(hdr.data + offset));
-
- offset += sizeof(uint64_t);
-
- oap_hdr->crt.len = (size_t) ntoh16(*(uint16_t *)(hdr.data + offset));
- oap_hdr->crt.data = hdr.data + offset + sizeof(uint16_t);
-
- offset += sizeof(uint16_t) + oap_hdr->crt.len;
-
- if ((size_t) offset + sizeof(uint16_t) >= hdr.len)
- goto fail_decode;
-
- oap_hdr->eph.len = (size_t) ntoh16(*(uint16_t *)(hdr.data + offset));
- oap_hdr->eph.data = hdr.data + offset + sizeof(uint16_t);
-
- offset += sizeof(uint16_t) + oap_hdr->eph.len;
-
- if ((size_t) offset + sizeof(uint16_t) >= hdr.len)
- goto fail_decode;
-
- oap_hdr->data.len = (size_t) ntoh16(*(uint16_t *)(hdr.data + offset));
- oap_hdr->data.data = hdr.data + offset + sizeof(uint16_t);
-
- offset += sizeof(uint16_t) + oap_hdr->data.len;
-
- if ((size_t) offset + sizeof(uint16_t) > hdr.len)
- goto fail_decode;
-
- oap_hdr->sig.len = (size_t) ntoh16(*(uint16_t *)(hdr.data + offset));
- oap_hdr->sig.data = hdr.data + offset + sizeof(uint16_t);
+ if (!file_exists(paths->crt) || !file_exists(paths->key)) {
+ log_info("No authentication certificates for %s.", name);
+ return 0;
+ }
- offset += sizeof(uint16_t) + oap_hdr->sig.len;
+ if (crypt_load_crt_file(paths->crt, crt) < 0) {
+ log_err("Failed to load %s for %s.", paths->crt, name);
+ goto fail_crt;
+ }
- if ((size_t) offset != hdr.len)
- goto fail_decode;
+ if (crypt_load_privkey_file(paths->key, pkp) < 0) {
+ log_err("Failed to load %s for %s.", paths->key, name);
+ goto fail_key;
+ }
- oap_hdr->hdr = hdr;
+ log_info("Loaded authentication certificates for %s.", name);
return 0;
- fail_decode:
- memset(oap_hdr, 0, sizeof(*oap_hdr));
- return -1;
-}
-
-#ifdef DEBUG_PROTO_OAP
-static void debug_oap_hdr(const struct oap_hdr * hdr)
-{
- assert(hdr);
-
- if (hdr->crt.len > 0)
- log_proto(" Certificate: [%zu bytes]", hdr->crt.len);
- else
- log_proto(" Certificate: <none>");
-
- if (hdr->eph.len > 0)
- log_proto(" Ephemeral Public Key: [%zu bytes]", hdr->eph.len);
- else
- log_proto(" Ephemeral Public Key: <none>");
- if (hdr->data.len > 0)
- log_proto(" Data: [%zu bytes]", hdr->data.len);
- else
- log_proto(" Data: <none>");
- if (hdr->sig.len > 0)
- log_proto(" Signature: [%zu bytes]", hdr->sig.len);
- else
- log_proto(" Signature: <none>");
+ fail_key:
+ crypt_free_crt(*crt);
+ *crt = NULL;
+ fail_crt:
+ return -EAUTH;
}
-void debug_oap_hdr_rcv(const struct oap_hdr * hdr)
+int load_kex_config(const char * name,
+ const char * path,
+ struct sec_config * cfg)
{
- struct tm * tm;
- char tmstr[RIB_TM_STRLEN];
- time_t stamp;
+ assert(name != NULL);
+ assert(cfg != NULL);
- assert(hdr);
+ memset(cfg, 0, sizeof(*cfg));
- stamp = (time_t) hdr->timestamp / BILLION;
-
- tm = gmtime(&stamp);
- strftime(tmstr, sizeof(tmstr), RIB_TM_FORMAT, tm);
-
- log_proto("OAP_HDR [" HASH_FMT64 " @ %s ] <--",
- HASH_VAL64(hdr->id.data), tmstr);
-
- debug_oap_hdr(hdr);
-}
+ /* Load encryption config */
+ if (!file_exists(path))
+ log_dbg("No encryption %s for %s.", path, name);
-void debug_oap_hdr_snd(const struct oap_hdr * hdr)
-{
- struct tm * tm;
- char tmstr[RIB_TM_STRLEN];
- time_t stamp;
-
- assert(hdr);
+ if (load_sec_config_file(cfg, path) < 0) {
+ log_warn("Failed to load %s for %s.", path, name);
+ return -1;
+ }
- stamp = (time_t) hdr->timestamp / BILLION;
+ if (!IS_KEX_ALGO_SET(cfg)) {
+ log_info("Key exchange not configured for %s.", name);
+ return 0;
+ }
- tm = gmtime(&stamp);
- strftime(tmstr, sizeof(tmstr), RIB_TM_FORMAT, tm);
+ if (cfg->c.nid == NID_undef || crypt_nid_to_str(cfg->c.nid) == NULL) {
+ log_err("Invalid cipher NID %d for %s.", cfg->c.nid, name);
+ return -ECRYPT;
+ }
- log_proto("OAP_HDR [" HASH_FMT64 " @ %s ] -->",
- HASH_VAL64(hdr->id.data), tmstr);
+ log_info("Encryption enabled for %s.", name);
- debug_oap_hdr(hdr);
+ return 0;
}
-#endif
+#endif /* OAP_TEST_MODE */
diff --git a/src/irmd/oap.h b/src/irmd/oap.h
index ccdfa804..d6d8dfe2 100644
--- a/src/irmd/oap.h
+++ b/src/irmd/oap.h
@@ -1,7 +1,7 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
- * Ouroboros flow allocation protocol header
+ * Ouroboros Allocation Protocol (OAP) Component
*
* Dimitri Staessens <dimitri@ouroboros.rocks>
* Sander Vrijders <sander@ouroboros.rocks>
@@ -23,72 +23,45 @@
#ifndef OUROBOROS_IRMD_OAP_H
#define OUROBOROS_IRMD_OAP_H
+#include <ouroboros/crypt.h>
+#include <ouroboros/flow.h>
+#include <ouroboros/name.h>
#include <ouroboros/utils.h>
-#define OAP_ID_SIZE (16)
-#define OAP_HDR_MIN_SIZE (OAP_ID_SIZE + sizeof(uint64_t) + 4 * sizeof(uint16_t))
+/* OAP authentication state (in oap/auth.c) */
+int oap_auth_init(void);
+void oap_auth_fini(void);
-/*
- * 0 1 2 3
- * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
- * +---------------------------------------------------------------+
- * | |
- * | id (128 bits) |
- * | |
- * | |
- * +---------------------------------------------------------------+
- * | timestamp (64 bits) |
- * | |
- * +---------------------------------------------------------------+
- * | crt_len (16 bits) | |
- * +-----------+-----------------+ |
- * | certificate |
- * | |
- * +---------------------------------------------------------------+
- * | eph_len (16 bits) | |
- * +-----------+-----------------+ |
- * | public key for ECDHE |
- * | |
- * +---------------------------------------------------------------+
- * | data_len (16 bits) | |
- * +-----------+-----------------+ |
- * | piggy backed application data |
- * | |
- * +---------------------------------------------------------------+
- * | sig_len (16 bits) | |
- * +-----------+-----------------+ |
- * | signature |
- * | |
- * +---------------------------------------------------------------+
- */
-
-struct oap_hdr {
- uint64_t timestamp;
- buffer_t id;
- buffer_t crt;
- buffer_t eph;
- buffer_t data;
- buffer_t sig;
- buffer_t hdr;
-};
-
-int oap_hdr_init(buffer_t id,
- void * pkp,
- void * pubcrt,
- buffer_t ephkey,
- buffer_t data,
- struct oap_hdr * oap_hdr);
+int oap_auth_add_ca_crt(void * crt);
-void oap_hdr_fini(struct oap_hdr * oap_hdr);
+/*
+* Prepare OAP request header for server, returns context
+* Passes client data for srv, returns srv data for client
+*/
+int oap_cli_prepare(void ** ctx,
+ const struct name_info * info,
+ buffer_t * req_buf,
+ buffer_t data);
-int oap_hdr_decode(buffer_t hdr,
- struct oap_hdr * oap_hdr);
+/*
+ * Server processes header, creates response header, returns secret key.
+ * data is in/out: input=srv data to send, output=cli data received.
+ */
+int oap_srv_process(const struct name_info * info,
+ buffer_t req_buf,
+ buffer_t * rsp_buf,
+ buffer_t * data,
+ struct crypt_sk * sk);
-#ifdef DEBUG_PROTO_OAP
-void debug_oap_hdr_snd(const struct oap_hdr * hdr);
+/* Complete OAP, returns secret key and server data, frees ctx */
+int oap_cli_complete(void * ctx,
+ const struct name_info * info,
+ buffer_t rsp_buf,
+ buffer_t * data,
+ struct crypt_sk * sk);
-void debug_oap_hdr_rcv(const struct oap_hdr * hdr);
-#endif /* DEBUG_PROTO_OAP */
+/* Free OAP state (on failure before complete) */
+void oap_ctx_free(void * ctx);
#endif /* OUROBOROS_IRMD_OAP_H */
diff --git a/src/irmd/oap/auth.c b/src/irmd/oap/auth.c
new file mode 100644
index 00000000..a11ab158
--- /dev/null
+++ b/src/irmd/oap/auth.c
@@ -0,0 +1,252 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * OAP - Authentication, replay detection, and validation
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#if defined(__linux__) || defined(__CYGWIN__)
+ #define _DEFAULT_SOURCE
+#else
+ #define _POSIX_C_SOURCE 200809L
+#endif
+
+#define OUROBOROS_PREFIX "irmd/oap"
+
+#include <ouroboros/crypt.h>
+#include <ouroboros/errno.h>
+#include <ouroboros/list.h>
+#include <ouroboros/logs.h>
+#include <ouroboros/pthread.h>
+#include <ouroboros/time.h>
+
+#include "config.h"
+
+#include "auth.h"
+#include "hdr.h"
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct oap_replay_entry {
+ struct list_head next;
+ uint64_t timestamp;
+ uint8_t id[OAP_ID_SIZE];
+};
+
+static struct {
+ struct auth_ctx * ca_ctx;
+ struct {
+ struct list_head list;
+ pthread_mutex_t mtx;
+ } replay;
+} oap_auth;
+
+int oap_auth_init(void)
+{
+ oap_auth.ca_ctx = auth_create_ctx();
+ if (oap_auth.ca_ctx == NULL) {
+ log_err("Failed to create OAP auth context.");
+ goto fail_ctx;
+ }
+
+ list_head_init(&oap_auth.replay.list);
+
+ if (pthread_mutex_init(&oap_auth.replay.mtx, NULL)) {
+ log_err("Failed to init OAP replay mutex.");
+ goto fail_mtx;
+ }
+
+ return 0;
+
+ fail_mtx:
+ auth_destroy_ctx(oap_auth.ca_ctx);
+ fail_ctx:
+ return -1;
+}
+
+void oap_auth_fini(void)
+{
+ struct list_head * p;
+ struct list_head * h;
+
+ pthread_mutex_lock(&oap_auth.replay.mtx);
+
+ list_for_each_safe(p, h, &oap_auth.replay.list) {
+ struct oap_replay_entry * e;
+ e = list_entry(p, struct oap_replay_entry, next);
+ list_del(&e->next);
+ free(e);
+ }
+
+ pthread_mutex_unlock(&oap_auth.replay.mtx);
+ pthread_mutex_destroy(&oap_auth.replay.mtx);
+
+ auth_destroy_ctx(oap_auth.ca_ctx);
+}
+
+int oap_auth_add_ca_crt(void * crt)
+{
+ return auth_add_crt_to_store(oap_auth.ca_ctx, crt);
+}
+
+#define TIMESYNC_SLACK 100 /* ms */
+#define ID_IS_EQUAL(id1, id2) (memcmp(id1, id2, OAP_ID_SIZE) == 0)
+int oap_check_hdr(const struct oap_hdr * hdr)
+{
+ struct list_head * p;
+ struct list_head * h;
+ struct timespec now;
+ struct oap_replay_entry * new;
+ uint64_t stamp;
+ uint64_t cur;
+ uint8_t * id;
+ ssize_t delta;
+
+ assert(hdr != NULL);
+
+ stamp = hdr->timestamp;
+ id = hdr->id.data;
+
+ clock_gettime(CLOCK_REALTIME, &now);
+
+ cur = TS_TO_UINT64(now);
+
+ delta = (ssize_t)(cur - stamp) / MILLION;
+ if (delta < -TIMESYNC_SLACK) {
+ log_err_id(id, "OAP header from %zd ms into future.", -delta);
+ goto fail_stamp;
+ }
+
+ if (delta > OAP_REPLAY_TIMER * 1000) {
+ log_err_id(id, "OAP header too old (%zd ms).", delta);
+ goto fail_stamp;
+ }
+
+ new = malloc(sizeof(*new));
+ if (new == NULL) {
+ log_err_id(id, "Failed to allocate memory for OAP element.");
+ goto fail_stamp;
+ }
+
+ pthread_mutex_lock(&oap_auth.replay.mtx);
+
+ list_for_each_safe(p, h, &oap_auth.replay.list) {
+ struct oap_replay_entry * e;
+ e = list_entry(p, struct oap_replay_entry, next);
+ if (cur > e->timestamp + OAP_REPLAY_TIMER * BILLION) {
+ list_del(&e->next);
+ free(e);
+ continue;
+ }
+
+ if (e->timestamp == stamp && ID_IS_EQUAL(e->id, id)) {
+ log_warn_id(id, "OAP header already known.");
+ goto fail_replay;
+ }
+ }
+
+ memcpy(new->id, id, OAP_ID_SIZE);
+ new->timestamp = stamp;
+
+ list_add_tail(&new->next, &oap_auth.replay.list);
+
+ pthread_mutex_unlock(&oap_auth.replay.mtx);
+
+ return 0;
+
+ fail_replay:
+ pthread_mutex_unlock(&oap_auth.replay.mtx);
+ free(new);
+ fail_stamp:
+ return -EAUTH;
+}
+
+int oap_auth_peer(char * name,
+ const struct oap_hdr * local_hdr,
+ const struct oap_hdr * peer_hdr)
+{
+ void * crt;
+ void * pk;
+ buffer_t sign; /* Signed region */
+ uint8_t * id = peer_hdr->id.data;
+
+ assert(name != NULL);
+ assert(local_hdr != NULL);
+ assert(peer_hdr != NULL);
+
+ if (memcmp(peer_hdr->id.data, local_hdr->id.data, OAP_ID_SIZE) != 0) {
+ log_err_id(id, "OAP ID mismatch in flow allocation.");
+ goto fail_check;
+ }
+
+ if (peer_hdr->crt.len == 0) {
+ log_dbg_id(id, "No crt provided.");
+ name[0] = '\0';
+ return 0;
+ }
+
+ if (crypt_load_crt_der(peer_hdr->crt, &crt) < 0) {
+ log_err_id(id, "Failed to load crt.");
+ goto fail_check;
+ }
+
+ log_dbg_id(id, "Loaded peer crt.");
+
+ if (crypt_get_pubkey_crt(crt, &pk) < 0) {
+ log_err_id(id, "Failed to get pubkey from crt.");
+ goto fail_crt;
+ }
+
+ log_dbg_id(id, "Got public key from crt.");
+
+ if (auth_verify_crt(oap_auth.ca_ctx, crt) < 0) {
+ log_err_id(id, "Failed to verify peer with CA store.");
+ goto fail_crt;
+ }
+
+ log_dbg_id(id, "Successfully verified peer crt.");
+
+ sign = peer_hdr->hdr;
+ sign.len -= peer_hdr->sig.len;
+
+ if (auth_verify_sig(pk, peer_hdr->md_nid, sign, peer_hdr->sig) < 0) {
+ log_err_id(id, "Failed to verify signature.");
+ goto fail_check_sig;
+ }
+
+ if (crypt_get_crt_name(crt, name) < 0) {
+ log_warn_id(id, "Failed to extract name from certificate.");
+ name[0] = '\0';
+ }
+
+ crypt_free_key(pk);
+ crypt_free_crt(crt);
+
+ log_dbg_id(id, "Successfully authenticated peer.");
+
+ return 0;
+
+ fail_check_sig:
+ crypt_free_key(pk);
+ fail_crt:
+ crypt_free_crt(crt);
+ fail_check:
+ return -EAUTH;
+}
diff --git a/src/irmd/tests/irm_test.c b/src/irmd/oap/auth.h
index d440289c..4f748750 100644
--- a/src/irmd/tests/irm_test.c
+++ b/src/irmd/oap/auth.h
@@ -1,7 +1,8 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * OAP - Authentication functions
*
- * Unit tests of IRMd functions
* Dimitri Staessens <dimitri@ouroboros.rocks>
* Sander Vrijders <sander@ouroboros.rocks>
*
@@ -19,15 +20,16 @@
* Foundation, Inc., http://www.fsf.org/about/contact/.
*/
+#ifndef OUROBOROS_IRMD_OAP_AUTH_H
+#define OUROBOROS_IRMD_OAP_AUTH_H
+#include "hdr.h"
-int irm_test(int argc,
- char **argv)
-{
- int ret = 0;
+int oap_check_hdr(const struct oap_hdr * hdr);
- (void) argc;
- (void) argv;
+/* name is updated with the peer's certificate name if available */
+int oap_auth_peer(char * name,
+ const struct oap_hdr * local_hdr,
+ const struct oap_hdr * peer_hdr);
- return ret;
-}
+#endif /* OUROBOROS_IRMD_OAP_AUTH_H */
diff --git a/src/irmd/oap/cli.c b/src/irmd/oap/cli.c
new file mode 100644
index 00000000..8ecd317d
--- /dev/null
+++ b/src/irmd/oap/cli.c
@@ -0,0 +1,576 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * OAP - Client-side processing
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#if defined(__linux__) || defined(__CYGWIN__)
+ #define _DEFAULT_SOURCE
+#else
+ #define _POSIX_C_SOURCE 200809L
+#endif
+
+#define OUROBOROS_PREFIX "irmd/oap"
+
+#include <ouroboros/crypt.h>
+#include <ouroboros/errno.h>
+#include <ouroboros/logs.h>
+#include <ouroboros/random.h>
+
+#include "config.h"
+
+#include "auth.h"
+#include "hdr.h"
+#include "io.h"
+#include "../oap.h"
+
+#include <assert.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Client context between oap_cli_prepare and oap_cli_complete */
+struct oap_cli_ctx {
+ uint8_t __id[OAP_ID_SIZE];
+ buffer_t id;
+ uint8_t kex_buf[MSGBUFSZ];
+ uint8_t req_hash[MAX_HASH_SIZE];
+ size_t req_hash_len;
+ int req_md_nid;
+ struct sec_config kcfg;
+ struct oap_hdr local_hdr;
+ void * pkp; /* Ephemeral keypair */
+ uint8_t * key; /* For client-encap KEM */
+};
+
+#define OAP_CLI_CTX_INIT(s) \
+ do { s->id.len = OAP_ID_SIZE; s->id.data = s->__id; } while (0)
+
+/* Client-side credential loading, mocked in tests */
+
+#ifdef OAP_TEST_MODE
+extern int load_cli_credentials(const struct name_info * info,
+ void ** pkp,
+ void ** crt);
+extern int load_cli_kex_config(const struct name_info * info,
+ struct sec_config * cfg);
+extern int load_server_kem_pk(const char * name,
+ struct sec_config * cfg,
+ buffer_t * buf);
+#else
+
+int load_cli_credentials(const struct name_info * info,
+ void ** pkp,
+ void ** crt)
+{
+ assert(info != NULL);
+ assert(pkp != NULL);
+ assert(crt != NULL);
+
+ return load_credentials(info->name, &info->c, pkp, crt);
+}
+
+int load_cli_kex_config(const struct name_info * info,
+ struct sec_config * cfg)
+{
+ assert(info != NULL);
+ assert(cfg != NULL);
+
+ return load_kex_config(info->name, info->c.enc, cfg);
+}
+
+int load_server_kem_pk(const char * name,
+ struct sec_config * cfg,
+ buffer_t * pk)
+{
+ char path[PATH_MAX];
+ const char * ext;
+
+ assert(name != NULL);
+ assert(cfg != NULL);
+ assert(pk != NULL);
+
+ ext = IS_HYBRID_KEM(cfg->x.str) ? "raw" : "pem";
+
+ snprintf(path, sizeof(path),
+ OUROBOROS_CLI_CRT_DIR "/%s/kex.srv.pub.%s", name, ext);
+
+ if (IS_HYBRID_KEM(cfg->x.str)) {
+ if (crypt_load_pubkey_raw_file(path, pk) < 0) {
+ log_err("Failed to load %s pubkey from %s.", ext, path);
+ return -1;
+ }
+ } else {
+ if (crypt_load_pubkey_file_to_der(path, pk) < 0) {
+ log_err("Failed to load %s pubkey from %s.", ext, path);
+ return -1;
+ }
+ }
+
+ log_dbg("Loaded %s pubkey from %s (%zu bytes).", ext, path, pk->len);
+
+ return 0;
+}
+
+#endif /* OAP_TEST_MODE */
+
+static int do_client_kex_prepare_dhe(struct oap_cli_ctx * s)
+{
+ struct sec_config * kcfg = &s->kcfg;
+ buffer_t * kex = &s->local_hdr.kex;
+ uint8_t * id = s->id.data;
+ ssize_t len;
+
+ /* Generate ephemeral keypair, send PK */
+ len = kex_pkp_create(kcfg, &s->pkp, kex->data);
+ if (len < 0) {
+ log_err_id(id, "Failed to generate DHE keypair.");
+ return -ECRYPT;
+ }
+
+ kex->len = (size_t) len;
+ log_dbg_id(id, "Generated ephemeral %s keys (%zd bytes).",
+ kcfg->x.str, len);
+
+ return 0;
+}
+
+static int do_client_kex_prepare_kem_encap(const char * server_name,
+ struct oap_cli_ctx * s)
+{
+ struct sec_config * kcfg = &s->kcfg;
+ buffer_t * kex = &s->local_hdr.kex;
+ uint8_t * id = s->id.data;
+ buffer_t server_pk = BUF_INIT;
+ uint8_t key_buf[SYMMKEYSZ];
+ ssize_t len;
+
+ if (load_server_kem_pk(server_name, kcfg, &server_pk) < 0) {
+ log_err_id(id, "Failed to load server KEM pk.");
+ return -ECRYPT;
+ }
+
+ if (IS_HYBRID_KEM(kcfg->x.str))
+ len = kex_kem_encap_raw(server_pk, kex->data,
+ kcfg->k.nid, key_buf);
+ else
+ len = kex_kem_encap(server_pk, kex->data,
+ kcfg->k.nid, key_buf);
+
+ freebuf(server_pk);
+
+ if (len < 0) {
+ log_err_id(id, "Failed to encapsulate KEM.");
+ return -ECRYPT;
+ }
+
+ kex->len = (size_t) len;
+ log_dbg_id(id, "Client encaps: CT len=%zd.", len);
+
+ /* Store derived key */
+ s->key = crypt_secure_malloc(SYMMKEYSZ);
+ if (s->key == NULL) {
+ log_err_id(id, "Failed to allocate secure key.");
+ return -ENOMEM;
+ }
+ memcpy(s->key, key_buf, SYMMKEYSZ);
+ crypt_secure_clear(key_buf, SYMMKEYSZ);
+
+ return 0;
+}
+
+static int do_client_kex_prepare_kem_decap(struct oap_cli_ctx * s)
+{
+ struct sec_config * kcfg = &s->kcfg;
+ buffer_t * kex = &s->local_hdr.kex;
+ uint8_t * id = s->id.data;
+ ssize_t len;
+
+ /* Server encaps: generate keypair, send PK */
+ len = kex_pkp_create(kcfg, &s->pkp, kex->data);
+ if (len < 0) {
+ log_err_id(id, "Failed to generate KEM keypair.");
+ return -ECRYPT;
+ }
+
+ kex->len = (size_t) len;
+ log_dbg_id(id, "Client PK for server encaps (%zd bytes).", len);
+
+ return 0;
+}
+
+static int do_client_kex_prepare(const char * server_name,
+ struct oap_cli_ctx * s)
+{
+ struct sec_config * kcfg = &s->kcfg;
+
+ if (!IS_KEX_ALGO_SET(kcfg))
+ return 0;
+
+ if (IS_KEM_ALGORITHM(kcfg->x.str)) {
+ if (kcfg->x.mode == KEM_MODE_CLIENT_ENCAP)
+ return do_client_kex_prepare_kem_encap(server_name, s);
+ else
+ return do_client_kex_prepare_kem_decap(s);
+ }
+
+ return do_client_kex_prepare_dhe(s);
+}
+
+int oap_cli_prepare(void ** ctx,
+ const struct name_info * info,
+ buffer_t * req_buf,
+ buffer_t data)
+{
+ struct oap_cli_ctx * s;
+ void * pkp = NULL;
+ void * crt = NULL;
+ ssize_t ret;
+
+ assert(ctx != NULL);
+ assert(info != NULL);
+ assert(req_buf != NULL);
+
+ clrbuf(*req_buf);
+ *ctx = NULL;
+
+ /* Allocate ctx to carry between prepare and complete */
+ s = malloc(sizeof(*s));
+ if (s == NULL) {
+ log_err("Failed to allocate OAP client ctx.");
+ return -ENOMEM;
+ }
+
+ memset(s, 0, sizeof(*s));
+ OAP_CLI_CTX_INIT(s);
+
+ /* Generate session ID */
+ if (random_buffer(s->__id, OAP_ID_SIZE) < 0) {
+ log_err("Failed to generate OAP session ID.");
+ goto fail_id;
+ }
+
+ log_dbg_id(s->id.data, "Preparing OAP request for %s.", info->name);
+
+ /* Load client credentials */
+ if (load_cli_credentials(info, &pkp, &crt) < 0) {
+ log_err_id(s->id.data, "Failed to load credentials for %s.",
+ info->name);
+ goto fail_id;
+ }
+
+ /* Load KEX config */
+ if (load_cli_kex_config(info, &s->kcfg) < 0) {
+ log_err_id(s->id.data, "Failed to load KEX config for %s.",
+ info->name);
+ goto fail_kex;
+ }
+
+ oap_hdr_init(&s->local_hdr, s->id, s->kex_buf, data, s->kcfg.c.nid);
+
+ if (do_client_kex_prepare(info->name, s) < 0) {
+ log_err_id(s->id.data, "Failed to prepare client KEX.");
+ goto fail_kex;
+ }
+
+ if (oap_hdr_encode(&s->local_hdr, pkp, crt, &s->kcfg,
+ (buffer_t) BUF_INIT, NID_undef)) {
+ log_err_id(s->id.data, "Failed to create OAP request header.");
+ goto fail_hdr;
+ }
+
+ debug_oap_hdr_snd(&s->local_hdr);
+
+ /* Compute and store hash of request for verification in complete */
+ s->req_md_nid = s->kcfg.d.nid != NID_undef ? s->kcfg.d.nid : NID_sha384;
+ ret = md_digest(s->req_md_nid, s->local_hdr.hdr, s->req_hash);
+ if (ret < 0) {
+ log_err_id(s->id.data, "Failed to hash request.");
+ goto fail_hash;
+ }
+ s->req_hash_len = (size_t) ret;
+
+ /* Transfer ownership of request buffer */
+ *req_buf = s->local_hdr.hdr;
+ clrbuf(s->local_hdr.hdr);
+
+ /* oap_hdr_encode repoints id into hdr; restore to __id */
+ s->local_hdr.id = s->id;
+
+ crypt_free_crt(crt);
+ crypt_free_key(pkp);
+
+ *ctx = s;
+
+ log_dbg_id(s->id.data, "OAP request prepared for %s.", info->name);
+
+ return 0;
+
+ fail_hash:
+ fail_hdr:
+ crypt_secure_free(s->key, SYMMKEYSZ);
+ crypt_free_key(s->pkp);
+ fail_kex:
+ crypt_free_crt(crt);
+ crypt_free_key(pkp);
+ fail_id:
+ free(s);
+ return -ECRYPT;
+}
+
+void oap_ctx_free(void * ctx)
+{
+ struct oap_cli_ctx * s = ctx;
+
+ if (s == NULL)
+ return;
+
+ oap_hdr_fini(&s->local_hdr);
+
+ if (s->pkp != NULL)
+ crypt_free_key(s->pkp);
+
+ if (s->key != NULL)
+ crypt_secure_free(s->key, SYMMKEYSZ);
+
+ memset(s, 0, sizeof(*s));
+ free(s);
+}
+
+static int do_client_kex_complete_kem(struct oap_cli_ctx * s,
+ const struct oap_hdr * peer_hdr,
+ struct crypt_sk * sk)
+{
+ struct sec_config * kcfg = &s->kcfg;
+ uint8_t * id = s->id.data;
+ uint8_t key_buf[SYMMKEYSZ];
+
+ if (kcfg->x.mode == KEM_MODE_SERVER_ENCAP) {
+ buffer_t ct;
+
+ if (peer_hdr->kex.len == 0) {
+ log_err_id(id, "Server did not send KEM CT.");
+ return -ECRYPT;
+ }
+
+ ct.data = peer_hdr->kex.data;
+ ct.len = peer_hdr->kex.len;
+
+ if (kex_kem_decap(s->pkp, ct, kcfg->k.nid, key_buf) < 0) {
+ log_err_id(id, "Failed to decapsulate KEM.");
+ return -ECRYPT;
+ }
+
+ log_dbg_id(id, "Client decapsulated server CT.");
+
+ } else if (kcfg->x.mode == KEM_MODE_CLIENT_ENCAP) {
+ /* Key already derived during prepare */
+ memcpy(sk->key, s->key, SYMMKEYSZ);
+ sk->nid = kcfg->c.nid;
+ log_info_id(id, "Negotiated %s + %s.", kcfg->x.str,
+ kcfg->c.str);
+ return 0;
+ }
+
+ memcpy(sk->key, key_buf, SYMMKEYSZ);
+ sk->nid = kcfg->c.nid;
+ crypt_secure_clear(key_buf, SYMMKEYSZ);
+
+ log_info_id(id, "Negotiated %s + %s.", kcfg->x.str, kcfg->c.str);
+
+ return 0;
+}
+
+static int do_client_kex_complete_dhe(struct oap_cli_ctx * s,
+ const struct oap_hdr * peer_hdr,
+ struct crypt_sk * sk)
+{
+ struct sec_config * kcfg = &s->kcfg;
+ uint8_t * id = s->id.data;
+ uint8_t key_buf[SYMMKEYSZ];
+
+ /* DHE: derive from server's public key */
+ if (peer_hdr->kex.len == 0) {
+ log_err_id(id, "Server did not send DHE public key.");
+ return -ECRYPT;
+ }
+
+ if (kex_dhe_derive(kcfg, s->pkp, peer_hdr->kex, key_buf) < 0) {
+ log_err_id(id, "Failed to derive DHE secret.");
+ return -ECRYPT;
+ }
+
+ log_dbg_id(id, "DHE: derived shared secret.");
+
+ memcpy(sk->key, key_buf, SYMMKEYSZ);
+ sk->nid = kcfg->c.nid;
+ crypt_secure_clear(key_buf, SYMMKEYSZ);
+
+ log_info_id(id, "Negotiated %s + %s.", kcfg->x.str, kcfg->c.str);
+
+ return 0;
+}
+
+
+static int do_client_kex_complete(struct oap_cli_ctx * s,
+ const struct oap_hdr * peer_hdr,
+ struct crypt_sk * sk)
+{
+ struct sec_config * kcfg = &s->kcfg;
+ uint8_t * id = s->id.data;
+ int cipher_nid;
+ int kdf_nid;
+
+ if (!IS_KEX_ALGO_SET(kcfg))
+ return 0;
+
+ /* Save client's configured minimums */
+ cipher_nid = kcfg->c.nid;
+ kdf_nid = kcfg->k.nid;
+
+ /* Accept server's cipher choice */
+ if (peer_hdr->cipher_str == NULL) {
+ log_err_id(id, "Server did not provide cipher.");
+ return -ECRYPT;
+ }
+
+ SET_KEX_CIPHER(kcfg, peer_hdr->cipher_str);
+ if (crypt_validate_nid(kcfg->c.nid) < 0) {
+ log_err_id(id, "Server cipher '%s' not supported.",
+ peer_hdr->cipher_str);
+ return -ENOTSUP;
+ }
+
+ /* Verify server cipher >= client's minimum */
+ if (crypt_cipher_rank(kcfg->c.nid) < crypt_cipher_rank(cipher_nid)) {
+ log_err_id(id, "Server cipher %s too weak.",
+ peer_hdr->cipher_str);
+ return -ECRYPT;
+ }
+
+ log_dbg_id(id, "Accepted server cipher %s.",
+ peer_hdr->cipher_str);
+
+ /* Accept server's KDF for non-client-encap modes */
+ if (kcfg->x.mode != KEM_MODE_CLIENT_ENCAP
+ && peer_hdr->kdf_nid != NID_undef) {
+ if (crypt_kdf_rank(peer_hdr->kdf_nid)
+ < crypt_kdf_rank(kdf_nid)) {
+ log_err_id(id, "Server KDF too weak.");
+ return -ECRYPT;
+ }
+ SET_KEX_KDF_NID(kcfg, peer_hdr->kdf_nid);
+ log_dbg_id(id, "Accepted server KDF %s.",
+ md_nid_to_str(kcfg->k.nid));
+ }
+
+ /* Derive shared secret */
+ if (IS_KEM_ALGORITHM(kcfg->x.str))
+ return do_client_kex_complete_kem(s, peer_hdr, sk);
+
+ return do_client_kex_complete_dhe(s, peer_hdr, sk);
+}
+
+int oap_cli_complete(void * ctx,
+ const struct name_info * info,
+ buffer_t rsp_buf,
+ buffer_t * data,
+ struct crypt_sk * sk)
+{
+ struct oap_cli_ctx * s = ctx;
+ struct oap_hdr peer_hdr;
+ char peer[NAME_SIZE + 1];
+ uint8_t * id;
+
+ assert(ctx != NULL);
+ assert(info != NULL);
+ assert(data != NULL);
+ assert(sk != NULL);
+
+ sk->nid = NID_undef;
+
+ clrbuf(*data);
+
+ memset(&peer_hdr, 0, sizeof(peer_hdr));
+
+ id = s->id.data;
+
+ log_dbg_id(id, "Completing OAP for %s.", info->name);
+
+ /* Decode response header using client's md_nid for hash length */
+ if (oap_hdr_decode(&peer_hdr, rsp_buf, s->req_md_nid) < 0) {
+ log_err_id(id, "Failed to decode OAP response header.");
+ goto fail_oap;
+ }
+
+ debug_oap_hdr_rcv(&peer_hdr);
+
+ /* Verify response ID matches request */
+ if (memcmp(peer_hdr.id.data, id, OAP_ID_SIZE) != 0) {
+ log_err_id(id, "OAP response ID mismatch.");
+ goto fail_oap;
+ }
+
+ /* Authenticate server */
+ if (oap_auth_peer(peer, &s->local_hdr, &peer_hdr) < 0) {
+ log_err_id(id, "Failed to authenticate server.");
+ goto fail_oap;
+ }
+
+ /* Verify request hash in authenticated response */
+ if (peer_hdr.req_hash.len == 0) {
+ log_err_id(id, "Response missing req_hash.");
+ goto fail_oap;
+ }
+
+ if (memcmp(peer_hdr.req_hash.data, s->req_hash, s->req_hash_len) != 0) {
+ log_err_id(id, "Response req_hash mismatch.");
+ goto fail_oap;
+ }
+
+ /* Verify peer certificate name matches expected destination */
+ if (peer_hdr.crt.len > 0 && strcmp(peer, info->name) != 0) {
+ log_err_id(id, "Peer crt for '%s' does not match '%s'.",
+ peer, info->name);
+ goto fail_oap;
+ }
+
+ /* Complete key exchange */
+ if (do_client_kex_complete(s, &peer_hdr, sk) < 0) {
+ log_err_id(id, "Failed to complete key exchange.");
+ goto fail_oap;
+ }
+
+ /* Copy piggybacked data from server response */
+ if (oap_hdr_copy_data(&peer_hdr, data) < 0) {
+ log_err_id(id, "Failed to copy server data.");
+ goto fail_oap;
+ }
+
+ log_info_id(id, "OAP completed for %s.", info->name);
+
+ oap_ctx_free(s);
+
+ return 0;
+
+ fail_oap:
+ oap_ctx_free(s);
+ return -ECRYPT;
+}
diff --git a/src/irmd/oap/hdr.c b/src/irmd/oap/hdr.c
new file mode 100644
index 00000000..5465dd2a
--- /dev/null
+++ b/src/irmd/oap/hdr.c
@@ -0,0 +1,465 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * OAP - Header encoding, decoding, and debugging
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#if defined(__linux__) || defined(__CYGWIN__)
+ #define _DEFAULT_SOURCE
+#else
+ #define _POSIX_C_SOURCE 200809L
+#endif
+
+#define OUROBOROS_PREFIX "irmd/oap"
+
+#include <ouroboros/crypt.h>
+#include <ouroboros/endian.h>
+#include <ouroboros/hash.h>
+#include <ouroboros/logs.h>
+#include <ouroboros/rib.h>
+#include <ouroboros/time.h>
+
+#include "config.h"
+
+#include "hdr.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+int oap_hdr_decode(struct oap_hdr * oap_hdr,
+ buffer_t hdr,
+ int req_md_nid)
+{
+ off_t offset;
+ uint16_t kex_len;
+ uint16_t ciph_nid;
+ size_t crt_len;
+ size_t data_len;
+ size_t hash_len;
+ size_t sig_len;
+
+ assert(oap_hdr != NULL);
+ memset(oap_hdr, 0, sizeof(*oap_hdr));
+
+ if (hdr.len < OAP_HDR_MIN_SIZE)
+ goto fail_decode;
+
+ /* Parse fixed header (36 bytes) */
+ oap_hdr->id.data = hdr.data;
+ oap_hdr->id.len = OAP_ID_SIZE;
+
+ offset = OAP_ID_SIZE;
+
+ oap_hdr->timestamp = ntoh64(*(uint64_t *)(hdr.data + offset));
+ offset += sizeof(uint64_t);
+
+ /* cipher NID */
+ ciph_nid = ntoh16(*(uint16_t *)(hdr.data + offset));
+ oap_hdr->nid = ciph_nid;
+ oap_hdr->cipher_str = crypt_nid_to_str(ciph_nid);
+ offset += sizeof(uint16_t);
+
+ /* kdf NID */
+ oap_hdr->kdf_nid = ntoh16(*(uint16_t *)(hdr.data + offset));
+ oap_hdr->kdf_str = md_nid_to_str(oap_hdr->kdf_nid);
+ offset += sizeof(uint16_t);
+
+ /* md NID (signature hash) */
+ oap_hdr->md_nid = ntoh16(*(uint16_t *)(hdr.data + offset));
+ oap_hdr->md_str = md_nid_to_str(oap_hdr->md_nid);
+ offset += sizeof(uint16_t);
+
+ /* Validate NIDs: NID_undef is valid at parse time, else must be known.
+ * Note: md_nid=NID_undef only valid for PQC; enforced at sign/verify.
+ */
+ if (ciph_nid != NID_undef && crypt_validate_nid(ciph_nid) < 0)
+ goto fail_decode;
+ if (oap_hdr->kdf_nid != NID_undef &&
+ md_validate_nid(oap_hdr->kdf_nid) < 0)
+ goto fail_decode;
+ if (oap_hdr->md_nid != NID_undef &&
+ md_validate_nid(oap_hdr->md_nid) < 0)
+ goto fail_decode;
+
+ /* crt_len */
+ crt_len = (size_t) ntoh16(*(uint16_t *)(hdr.data + offset));
+ offset += sizeof(uint16_t);
+
+ /* kex_len + flags */
+ kex_len = ntoh16(*(uint16_t *)(hdr.data + offset));
+ oap_hdr->kex.len = (size_t) (kex_len & OAP_KEX_LEN_MASK);
+ oap_hdr->kex_flags.fmt = (kex_len & OAP_KEX_FMT_BIT) ? 1 : 0;
+ oap_hdr->kex_flags.role = (kex_len & OAP_KEX_ROLE_BIT) ? 1 : 0;
+ offset += sizeof(uint16_t);
+
+ /* data_len */
+ data_len = (size_t) ntoh16(*(uint16_t *)(hdr.data + offset));
+ offset += sizeof(uint16_t);
+
+ /* Response includes req_hash when md_nid is set */
+ hash_len = (req_md_nid != NID_undef) ?
+ (size_t) md_len(req_md_nid) : 0;
+
+ /* Validate total length */
+ if (hdr.len < (size_t) offset + crt_len + oap_hdr->kex.len +
+ data_len + hash_len)
+ goto fail_decode;
+
+ /* Derive sig_len from remaining bytes */
+ sig_len = hdr.len - offset - crt_len - oap_hdr->kex.len -
+ data_len - hash_len;
+
+ /* Unsigned packets must not have trailing bytes */
+ if (crt_len == 0 && sig_len != 0)
+ goto fail_decode;
+
+ /* Parse variable fields */
+ oap_hdr->crt.data = hdr.data + offset;
+ oap_hdr->crt.len = crt_len;
+ offset += crt_len;
+
+ oap_hdr->kex.data = hdr.data + offset;
+ offset += oap_hdr->kex.len;
+
+ oap_hdr->data.data = hdr.data + offset;
+ oap_hdr->data.len = data_len;
+ offset += data_len;
+
+ oap_hdr->req_hash.data = hdr.data + offset;
+ oap_hdr->req_hash.len = hash_len;
+ offset += hash_len;
+
+ oap_hdr->sig.data = hdr.data + offset;
+ oap_hdr->sig.len = sig_len;
+
+ oap_hdr->hdr = hdr;
+
+ return 0;
+
+ fail_decode:
+ memset(oap_hdr, 0, sizeof(*oap_hdr));
+ return -1;
+}
+
+void oap_hdr_fini(struct oap_hdr * oap_hdr)
+{
+ assert(oap_hdr != NULL);
+
+ freebuf(oap_hdr->hdr);
+ memset(oap_hdr, 0, sizeof(*oap_hdr));
+}
+
+int oap_hdr_copy_data(const struct oap_hdr * hdr,
+ buffer_t * out)
+{
+ assert(hdr != NULL);
+ assert(out != NULL);
+
+ if (hdr->data.len == 0) {
+ clrbuf(*out);
+ return 0;
+ }
+
+ out->data = malloc(hdr->data.len);
+ if (out->data == NULL)
+ return -ENOMEM;
+
+ memcpy(out->data, hdr->data.data, hdr->data.len);
+ out->len = hdr->data.len;
+
+ return 0;
+}
+
+void oap_hdr_init(struct oap_hdr * hdr,
+ buffer_t id,
+ uint8_t * kex_buf,
+ buffer_t data,
+ uint16_t nid)
+{
+ assert(hdr != NULL);
+ assert(id.data != NULL && id.len == OAP_ID_SIZE);
+
+ memset(hdr, 0, sizeof(*hdr));
+
+ hdr->id = id;
+ hdr->kex.data = kex_buf;
+ hdr->kex.len = 0;
+ hdr->data = data;
+ hdr->nid = nid;
+}
+
+int oap_hdr_encode(struct oap_hdr * hdr,
+ void * pkp,
+ void * crt,
+ struct sec_config * kcfg,
+ buffer_t req_hash,
+ int req_md_nid)
+{
+ struct timespec now;
+ uint64_t stamp;
+ buffer_t out;
+ buffer_t der = BUF_INIT;
+ buffer_t sig = BUF_INIT;
+ buffer_t sign;
+ uint16_t len;
+ uint16_t ciph_nid;
+ uint16_t kdf_nid;
+ uint16_t md_nid;
+ uint16_t kex_len;
+ off_t offset;
+
+ assert(hdr != NULL);
+ assert(hdr->id.data != NULL && hdr->id.len == OAP_ID_SIZE);
+ assert(kcfg != NULL);
+
+ clock_gettime(CLOCK_REALTIME, &now);
+ stamp = hton64(TS_TO_UINT64(now));
+
+ if (crt != NULL && crypt_crt_der(crt, &der) < 0)
+ goto fail_der;
+
+ ciph_nid = hton16(hdr->nid);
+ kdf_nid = hton16(kcfg->k.nid);
+ md_nid = hton16(kcfg->d.nid);
+
+ /* Build kex_len with flags */
+ kex_len = (uint16_t) hdr->kex.len;
+ if (hdr->kex.len > 0 && IS_KEM_ALGORITHM(kcfg->x.str)) {
+ if (IS_HYBRID_KEM(kcfg->x.str))
+ kex_len |= OAP_KEX_FMT_BIT;
+ if (kcfg->x.mode == KEM_MODE_CLIENT_ENCAP)
+ kex_len |= OAP_KEX_ROLE_BIT;
+ }
+ kex_len = hton16(kex_len);
+
+ /* Fixed header (36 bytes) + variable fields + req_hash (if auth) */
+ out.len = OAP_HDR_MIN_SIZE + der.len + hdr->kex.len + hdr->data.len +
+ req_hash.len;
+
+ out.data = malloc(out.len);
+ if (out.data == NULL)
+ goto fail_out;
+
+ offset = 0;
+
+ /* id (16 bytes) */
+ memcpy(out.data + offset, hdr->id.data, hdr->id.len);
+ offset += hdr->id.len;
+
+ /* timestamp (8 bytes) */
+ memcpy(out.data + offset, &stamp, sizeof(stamp));
+ offset += sizeof(stamp);
+
+ /* cipher_nid (2 bytes) */
+ memcpy(out.data + offset, &ciph_nid, sizeof(ciph_nid));
+ offset += sizeof(ciph_nid);
+
+ /* kdf_nid (2 bytes) */
+ memcpy(out.data + offset, &kdf_nid, sizeof(kdf_nid));
+ offset += sizeof(kdf_nid);
+
+ /* md_nid (2 bytes) */
+ memcpy(out.data + offset, &md_nid, sizeof(md_nid));
+ offset += sizeof(md_nid);
+
+ /* crt_len (2 bytes) */
+ len = hton16((uint16_t) der.len);
+ memcpy(out.data + offset, &len, sizeof(len));
+ offset += sizeof(len);
+
+ /* kex_len + flags (2 bytes) */
+ memcpy(out.data + offset, &kex_len, sizeof(kex_len));
+ offset += sizeof(kex_len);
+
+ /* data_len (2 bytes) */
+ len = hton16((uint16_t) hdr->data.len);
+ memcpy(out.data + offset, &len, sizeof(len));
+ offset += sizeof(len);
+
+ /* Fixed header complete (36 bytes) */
+ assert((size_t) offset == OAP_HDR_MIN_SIZE);
+
+ /* certificate (variable) */
+ if (der.len != 0)
+ memcpy(out.data + offset, der.data, der.len);
+ offset += der.len;
+
+ /* kex data (variable) */
+ if (hdr->kex.len != 0)
+ memcpy(out.data + offset, hdr->kex.data, hdr->kex.len);
+ offset += hdr->kex.len;
+
+ /* data (variable) */
+ if (hdr->data.len != 0)
+ memcpy(out.data + offset, hdr->data.data, hdr->data.len);
+ offset += hdr->data.len;
+
+ /* req_hash (variable, only for authenticated responses) */
+ if (req_hash.len != 0)
+ memcpy(out.data + offset, req_hash.data, req_hash.len);
+ offset += req_hash.len;
+
+ assert((size_t) offset == out.len);
+
+ /* Sign the entire header (fixed + variable, excluding signature) */
+ sign.data = out.data;
+ sign.len = out.len;
+
+ if (pkp != NULL && auth_sign(pkp, kcfg->d.nid, sign, &sig) < 0)
+ goto fail_sig;
+
+ hdr->hdr = out;
+
+ /* Append signature */
+ if (sig.len > 0) {
+ hdr->hdr.len += sig.len;
+ hdr->hdr.data = realloc(out.data, hdr->hdr.len);
+ if (hdr->hdr.data == NULL)
+ goto fail_realloc;
+
+ memcpy(hdr->hdr.data + offset, sig.data, sig.len);
+ clrbuf(out);
+ }
+
+ if (oap_hdr_decode(hdr, hdr->hdr, req_md_nid) < 0)
+ goto fail_decode;
+
+ freebuf(der);
+ freebuf(sig);
+
+ return 0;
+
+ fail_decode:
+ oap_hdr_fini(hdr);
+ fail_realloc:
+ freebuf(sig);
+ fail_sig:
+ freebuf(out);
+ fail_out:
+ freebuf(der);
+ fail_der:
+ return -1;
+}
+
+#ifdef DEBUG_PROTO_OAP
+#define OAP_KEX_IS_KEM(hdr) ((hdr)->kex_flags.role | (hdr)->kex_flags.fmt)
+static void debug_oap_hdr(const struct oap_hdr * hdr)
+{
+ assert(hdr);
+
+ if (hdr->crt.len > 0)
+ log_proto(" crt: [%zu bytes]", hdr->crt.len);
+ else
+ log_proto(" crt: <none>");
+
+ if (hdr->kex.len > 0) {
+ if (OAP_KEX_IS_KEM(hdr))
+ log_proto(" Key Exchange Data:"
+ " [%zu bytes] [%s]",
+ hdr->kex.len,
+ hdr->kex_flags.role ?
+ "Client encaps" :
+ "Server encaps");
+ else
+ log_proto(" Key Exchange Data:"
+ " [%zu bytes]",
+ hdr->kex.len);
+ } else
+ log_proto(" Key Exchange Data: <none>");
+
+ if (hdr->cipher_str != NULL)
+ log_proto(" Cipher: %s", hdr->cipher_str);
+ else
+ log_proto(" Cipher: <none>");
+
+ if (hdr->kdf_str != NULL)
+ log_proto(" KDF: HKDF-%s", hdr->kdf_str);
+ else
+ log_proto(" KDF: <none>");
+
+ if (hdr->md_str != NULL)
+ log_proto(" Digest: %s", hdr->md_str);
+ else
+ log_proto(" Digest: <none>");
+
+ if (hdr->data.len > 0)
+ log_proto(" Data: [%zu bytes]", hdr->data.len);
+ else
+ log_proto(" Data: <none>");
+
+ if (hdr->req_hash.len > 0)
+ log_proto(" Req Hash: [%zu bytes]", hdr->req_hash.len);
+ else
+ log_proto(" Req Hash: <none>");
+
+ if (hdr->sig.len > 0)
+ log_proto(" Signature: [%zu bytes]", hdr->sig.len);
+ else
+ log_proto(" Signature: <none>");
+}
+#endif
+
+void debug_oap_hdr_rcv(const struct oap_hdr * hdr)
+{
+#ifdef DEBUG_PROTO_OAP
+ struct tm * tm;
+ char tmstr[RIB_TM_STRLEN];
+ time_t stamp;
+
+ assert(hdr);
+
+ stamp = (time_t) hdr->timestamp / BILLION;
+
+ tm = gmtime(&stamp);
+ strftime(tmstr, sizeof(tmstr), RIB_TM_FORMAT, tm);
+
+ log_proto("OAP_HDR [" HASH_FMT64 " @ %s ] <--",
+ HASH_VAL64(hdr->id.data), tmstr);
+
+ debug_oap_hdr(hdr);
+#else
+ (void) hdr;
+#endif
+}
+
+void debug_oap_hdr_snd(const struct oap_hdr * hdr)
+{
+#ifdef DEBUG_PROTO_OAP
+ struct tm * tm;
+ char tmstr[RIB_TM_STRLEN];
+ time_t stamp;
+
+ assert(hdr);
+
+ stamp = (time_t) hdr->timestamp / BILLION;
+
+ tm = gmtime(&stamp);
+ strftime(tmstr, sizeof(tmstr), RIB_TM_FORMAT, tm);
+
+ log_proto("OAP_HDR [" HASH_FMT64 " @ %s ] -->",
+ HASH_VAL64(hdr->id.data), tmstr);
+
+ debug_oap_hdr(hdr);
+#else
+ (void) hdr;
+#endif
+}
diff --git a/src/irmd/oap/hdr.h b/src/irmd/oap/hdr.h
new file mode 100644
index 00000000..6016452c
--- /dev/null
+++ b/src/irmd/oap/hdr.h
@@ -0,0 +1,159 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * OAP - Header definitions and functions
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#ifndef OUROBOROS_IRMD_OAP_HDR_H
+#define OUROBOROS_IRMD_OAP_HDR_H
+
+#include <ouroboros/crypt.h>
+#include <ouroboros/utils.h>
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#define OAP_ID_SIZE (16)
+#define OAP_HDR_MIN_SIZE (OAP_ID_SIZE + sizeof(uint64_t) + 6 * sizeof(uint16_t))
+
+#define OAP_KEX_FMT_BIT 0x8000 /* bit 15: 0=X.509 DER, 1=Raw */
+#define OAP_KEX_ROLE_BIT 0x4000 /* bit 14: 0=Server encaps, 1=Client encaps */
+#define OAP_KEX_LEN_MASK 0x3FFF /* bits 0-13: Length (0-16383 bytes) */
+
+#define OAP_KEX_ROLE(hdr) (hdr->kex_flags.role)
+#define OAP_KEX_FMT(hdr) (hdr->kex_flags.fmt)
+
+#define OAP_KEX_IS_X509_FMT(hdr) (((hdr)->kex_flags.fmt) == 0)
+#define OAP_KEX_IS_RAW_FMT(hdr) (((hdr)->kex_flags.fmt) == 1)
+
+/*
+ * 0 1 2 3
+ * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ---+
+ * | | |
+ * + + |
+ * | | |
+ * + id (128 bits) + |
+ * | Unique flow allocation ID | |
+ * + + |
+ * | | |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
+ * | | |
+ * + timestamp (64 bits) + |
+ * | UTC nanoseconds since epoch | |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
+ * | cipher_nid (16 bits) | kdf_nid (16 bits) | |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
+ * | md_nid (16 bits) | crt_len (16 bits) | |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
+ * |F|R| kex_len (14 bits) | data_len (16 bits) | | Signed
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Region
+ * | | |
+ * + certificate (variable) + |
+ * | X.509 certificate, DER encoded | |
+ * + + |
+ * | | |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
+ * | | |
+ * + kex_data (variable) + |
+ * | public key (DER/raw) or ciphertext (KEM) | |
+ * + + |
+ * | | |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
+ * | | |
+ * + data (variable) + |
+ * | Piggybacked application data | |
+ * + + |
+ * | | |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
+ * | | |
+ * + req_hash (variable, response only) + |
+ * | H(request) using req md_nid / sha384 | |
+ * | | |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ---+
+ * | |
+ * + signature (variable) +
+ * | DSA signature over signed region |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * cipher_nid: NID value for symmetric cipher (0 = none)
+ * kdf_nid: NID value for KDF function (0 = none)
+ * md_nid: NID value for signature hash (0 = PQC/no signature)
+ *
+ * kex_len field bit layout:
+ * F (bit 15): Format - 0 = X.509 DER, 1 = Raw/Hybrid
+ * R (bit 14): Role - 0 = Server encaps, 1 = Client encaps
+ * (R is ignored for non-KEM algorithms)
+ * Bits 0-13: Length (0-16383 bytes)
+ *
+ * Request: sig_len = total - 36 - crt_len - kex_len - data_len
+ * Response: sig_len = total - 36 - crt_len - kex_len - data_len - hash_len
+ * where hash_len = md_len(req_md_nid / sha384)
+ */
+
+/* Parsed OAP header - buffers pointing to a single memory region */
+struct oap_hdr {
+ const char * cipher_str;
+ const char * kdf_str;
+ const char * md_str;
+ uint64_t timestamp;
+ uint16_t nid;
+ uint16_t kdf_nid;
+ uint16_t md_nid;
+ struct {
+ bool fmt; /* Format */
+ bool role; /* Role */
+ } kex_flags;
+ buffer_t id;
+ buffer_t crt;
+ buffer_t kex;
+ buffer_t data;
+ buffer_t req_hash; /* H(request) - response only */
+ buffer_t sig;
+ buffer_t hdr;
+};
+
+
+void oap_hdr_init(struct oap_hdr * hdr,
+ buffer_t id,
+ uint8_t * kex_buf,
+ buffer_t data,
+ uint16_t nid);
+
+void oap_hdr_fini(struct oap_hdr * oap_hdr);
+
+int oap_hdr_encode(struct oap_hdr * hdr,
+ void * pkp,
+ void * crt,
+ struct sec_config * kcfg,
+ buffer_t req_hash,
+ int req_md_nid);
+
+int oap_hdr_decode(struct oap_hdr * hdr,
+ buffer_t buf,
+ int req_md_nid);
+
+void debug_oap_hdr_rcv(const struct oap_hdr * hdr);
+
+void debug_oap_hdr_snd(const struct oap_hdr * hdr);
+
+int oap_hdr_copy_data(const struct oap_hdr * hdr,
+ buffer_t * out);
+
+#endif /* OUROBOROS_IRMD_OAP_HDR_H */
diff --git a/src/irmd/oap/internal.h b/src/irmd/oap/internal.h
new file mode 100644
index 00000000..6dd44d56
--- /dev/null
+++ b/src/irmd/oap/internal.h
@@ -0,0 +1,118 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * OAP internal definitions
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#ifndef OUROBOROS_IRMD_OAP_INTERNAL_H
+#define OUROBOROS_IRMD_OAP_INTERNAL_H
+
+#include <ouroboros/crypt.h>
+#include <ouroboros/list.h>
+#include <ouroboros/name.h>
+#include <ouroboros/pthread.h>
+#include <ouroboros/utils.h>
+
+#include "hdr.h"
+
+#include <stdbool.h>
+#include <stdint.h>
+
+int oap_check_hdr(const struct oap_hdr * hdr);
+
+int oap_auth_peer(char * name,
+ const struct oap_hdr * local_hdr,
+ const struct oap_hdr * peer_hdr);
+
+int oap_negotiate_cipher(const struct oap_hdr * peer_hdr,
+ struct sec_config * kcfg);
+
+#ifndef OAP_TEST_MODE
+int load_credentials(const char * name,
+ const struct name_sec_paths * paths,
+ void ** pkp,
+ void ** crt);
+
+int load_kex_config(const char * name,
+ const char * path,
+ struct sec_config * cfg);
+#endif
+
+#ifndef OAP_TEST_MODE
+int load_srv_credentials(const struct name_info * info,
+ void ** pkp,
+ void ** crt);
+
+int load_srv_kex_config(const struct name_info * info,
+ struct sec_config * cfg);
+
+int load_server_kem_keypair(const char * name,
+ struct sec_config * cfg,
+ void ** pkp);
+#else
+extern int load_srv_credentials(const struct name_info * info,
+ void ** pkp,
+ void ** crt);
+extern int load_srv_kex_config(const struct name_info * info,
+ struct sec_config * cfg);
+extern int load_server_kem_keypair(const char * name,
+ struct sec_config * cfg,
+ void ** pkp);
+#endif
+
+int do_server_kex(const struct name_info * info,
+ struct oap_hdr * peer_hdr,
+ struct sec_config * kcfg,
+ buffer_t * kex,
+ struct crypt_sk * sk);
+
+#ifndef OAP_TEST_MODE
+int load_cli_credentials(const struct name_info * info,
+ void ** pkp,
+ void ** crt);
+
+int load_cli_kex_config(const struct name_info * info,
+ struct sec_config * cfg);
+
+int load_server_kem_pk(const char * name,
+ struct sec_config * cfg,
+ buffer_t * pk);
+#else
+extern int load_cli_credentials(const struct name_info * info,
+ void ** pkp,
+ void ** crt);
+extern int load_cli_kex_config(const struct name_info * info,
+ struct sec_config * cfg);
+extern int load_server_kem_pk(const char * name,
+ struct sec_config * cfg,
+ buffer_t * pk);
+#endif
+
+int oap_client_kex_prepare(struct sec_config * kcfg,
+ buffer_t server_pk,
+ buffer_t * kex,
+ uint8_t * key,
+ void ** ephemeral_pkp);
+
+int oap_client_kex_complete(const struct oap_hdr * peer_hdr,
+ struct sec_config * kcfg,
+ void * pkp,
+ uint8_t * key);
+
+#endif /* OUROBOROS_IRMD_OAP_INTERNAL_H */
diff --git a/src/irmd/oap/io.c b/src/irmd/oap/io.c
new file mode 100644
index 00000000..c2c91b91
--- /dev/null
+++ b/src/irmd/oap/io.c
@@ -0,0 +1,138 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * OAP - File I/O for credentials and configuration
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#if defined(__linux__) || defined(__CYGWIN__)
+ #define _DEFAULT_SOURCE
+#else
+ #define _POSIX_C_SOURCE 200809L
+#endif
+
+#define OUROBOROS_PREFIX "irmd/oap"
+
+#include <ouroboros/crypt.h>
+#include <ouroboros/errno.h>
+#include <ouroboros/logs.h>
+
+#include "config.h"
+
+#include "io.h"
+
+#include <assert.h>
+#include <string.h>
+#include <sys/stat.h>
+
+/*
+ * Shared credential and configuration loading helpers
+ */
+
+#ifndef OAP_TEST_MODE
+
+static bool file_exists(const char * path)
+{
+ struct stat s;
+
+ if (stat(path, &s) < 0 && errno == ENOENT) {
+ log_dbg("File %s does not exist.", path);
+ return false;
+ }
+
+ return true;
+}
+
+int load_credentials(const char * name,
+ const struct name_sec_paths * paths,
+ void ** pkp,
+ void ** crt)
+{
+ assert(paths != NULL);
+ assert(pkp != NULL);
+ assert(crt != NULL);
+
+ *pkp = NULL;
+ *crt = NULL;
+
+ if (!file_exists(paths->crt) || !file_exists(paths->key)) {
+ log_info("No authentication certificates for %s.", name);
+ return 0;
+ }
+
+ if (crypt_load_crt_file(paths->crt, crt) < 0) {
+ log_err("Failed to load %s for %s.", paths->crt, name);
+ goto fail_crt;
+ }
+
+ if (crypt_load_privkey_file(paths->key, pkp) < 0) {
+ log_err("Failed to load %s for %s.", paths->key, name);
+ goto fail_key;
+ }
+
+ log_info("Loaded authentication certificates for %s.", name);
+
+ return 0;
+
+ fail_key:
+ crypt_free_crt(*crt);
+ *crt = NULL;
+ fail_crt:
+ return -EAUTH;
+}
+
+int load_kex_config(const char * name,
+ const char * path,
+ struct sec_config * cfg)
+{
+ assert(name != NULL);
+ assert(cfg != NULL);
+
+ memset(cfg, 0, sizeof(*cfg));
+
+ /* Load encryption config */
+ if (!file_exists(path))
+ log_dbg("No encryption %s for %s.", path, name);
+
+ if (load_sec_config_file(cfg, path) < 0) {
+ log_warn("Failed to load %s for %s.", path, name);
+ return -1;
+ }
+
+ if (!IS_KEX_ALGO_SET(cfg)) {
+ log_info("Key exchange not configured for %s.", name);
+ return 0;
+ }
+#ifndef HAVE_OPENSSL_ML_KEM
+ if (IS_KEM_ALGORITHM(cfg->x.str)) {
+ log_err("PQC not available, can't use %s for %s.",
+ cfg->x.str, name);
+ return -ENOTSUP;
+ }
+#endif
+ if (cfg->c.nid == NID_undef) {
+ log_err("Invalid cipher for %s.", name);
+ return -ECRYPT;
+ }
+
+ log_info("Encryption enabled for %s.", name);
+
+ return 0;
+}
+
+#endif /* OAP_TEST_MODE */
diff --git a/src/irmd/oap/io.h b/src/irmd/oap/io.h
new file mode 100644
index 00000000..2d47c62f
--- /dev/null
+++ b/src/irmd/oap/io.h
@@ -0,0 +1,40 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * OAP - Credential and configuration file I/O
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#ifndef OUROBOROS_IRMD_OAP_IO_H
+#define OUROBOROS_IRMD_OAP_IO_H
+
+#include <ouroboros/crypt.h>
+#include <ouroboros/name.h>
+
+#ifndef OAP_TEST_MODE
+int load_credentials(const char * name,
+ const struct name_sec_paths * paths,
+ void ** pkp,
+ void ** crt);
+
+int load_kex_config(const char * name,
+ const char * path,
+ struct sec_config * cfg);
+#endif
+
+#endif /* OUROBOROS_IRMD_OAP_IO_H */
diff --git a/src/irmd/oap/srv.c b/src/irmd/oap/srv.c
new file mode 100644
index 00000000..36391e50
--- /dev/null
+++ b/src/irmd/oap/srv.c
@@ -0,0 +1,498 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * OAP - Server-side processing
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#if defined(__linux__) || defined(__CYGWIN__)
+ #define _DEFAULT_SOURCE
+#else
+ #define _POSIX_C_SOURCE 200809L
+#endif
+
+#define OUROBOROS_PREFIX "irmd/oap"
+
+#include <ouroboros/crypt.h>
+#include <ouroboros/errno.h>
+#include <ouroboros/logs.h>
+
+#include "config.h"
+
+#include "auth.h"
+#include "hdr.h"
+#include "io.h"
+#include "oap.h"
+
+#include <assert.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef OAP_TEST_MODE
+extern int load_srv_credentials(const struct name_info * info,
+ void ** pkp,
+ void ** crt);
+extern int load_srv_kex_config(const struct name_info * info,
+ struct sec_config * cfg);
+extern int load_server_kem_keypair(const char * name,
+ bool raw_fmt,
+ void ** pkp);
+#else
+
+int load_srv_credentials(const struct name_info * info,
+ void ** pkp,
+ void ** crt)
+{
+ assert(info != NULL);
+ assert(pkp != NULL);
+ assert(crt != NULL);
+
+ return load_credentials(info->name, &info->s, pkp, crt);
+}
+
+int load_srv_kex_config(const struct name_info * info,
+ struct sec_config * cfg)
+{
+ assert(info != NULL);
+ assert(cfg != NULL);
+
+ return load_kex_config(info->name, info->s.enc, cfg);
+}
+
+int load_server_kem_keypair(const char * name,
+ bool raw_fmt,
+ void ** pkp)
+{
+ char path[PATH_MAX];
+ const char * ext;
+
+ assert(name != NULL);
+ assert(pkp != NULL);
+
+ ext = raw_fmt ? "raw" : "pem";
+
+ snprintf(path, sizeof(path),
+ OUROBOROS_SRV_CRT_DIR "/%s/kex.key.%s", name, ext);
+
+ if (raw_fmt) {
+ if (crypt_load_privkey_raw_file(path, pkp) < 0) {
+ log_err("Failed to load %s keypair from %s.",
+ ext, path);
+ return -ECRYPT;
+ }
+ } else {
+ if (crypt_load_privkey_file(path, pkp) < 0) {
+ log_err("Failed to load %s keypair from %s.",
+ ext, path);
+ return -ECRYPT;
+ }
+ }
+
+ log_dbg("Loaded server KEM keypair from %s.", path);
+ return 0;
+}
+
+#endif /* OAP_TEST_MODE */
+
+static int get_algo_from_peer_key(const struct oap_hdr * peer_hdr,
+ char * algo_buf)
+{
+ uint8_t * id = peer_hdr->id.data;
+ int ret;
+
+ if (OAP_KEX_IS_RAW_FMT(peer_hdr)) {
+ ret = kex_get_algo_from_pk_raw(peer_hdr->kex, algo_buf);
+ if (ret < 0) {
+ log_err_id(id, "Failed to get algo from raw key.");
+ return -ECRYPT;
+ }
+ } else {
+ ret = kex_get_algo_from_pk_der(peer_hdr->kex, algo_buf);
+ if (ret < 0) {
+ log_err_id(id, "Failed to get algo from DER key.");
+ return -ECRYPT;
+ }
+ }
+
+ return 0;
+}
+
+static int negotiate_cipher(const struct oap_hdr * peer_hdr,
+ struct sec_config * kcfg)
+{
+ uint8_t * id = peer_hdr->id.data;
+ int cli_nid;
+ int cli_rank;
+ int srv_rank;
+
+ /* Cipher: select the strongest of client and server */
+ cli_nid = peer_hdr->cipher_str != NULL
+ ? (int) crypt_str_to_nid(peer_hdr->cipher_str)
+ : NID_undef;
+
+ if (cli_nid != NID_undef
+ && crypt_cipher_rank(cli_nid) < 0) {
+ log_err_id(id, "Unsupported cipher '%s'.",
+ peer_hdr->cipher_str);
+ return -ENOTSUP;
+ }
+
+ cli_rank = crypt_cipher_rank(cli_nid);
+ srv_rank = crypt_cipher_rank(kcfg->c.nid);
+
+ if (cli_rank > srv_rank) {
+ SET_KEX_CIPHER_NID(kcfg, cli_nid);
+ log_dbg_id(id, "Selected client cipher %s.",
+ kcfg->c.str);
+ } else if (srv_rank > 0) {
+ log_dbg_id(id, "Selected server cipher %s.",
+ kcfg->c.str);
+ } else {
+ log_err_id(id, "Encryption requested, no cipher.");
+ return -ECRYPT;
+ }
+
+ /* KDF: select the strongest of client and server */
+ if (peer_hdr->kdf_nid != NID_undef
+ && crypt_kdf_rank(peer_hdr->kdf_nid) < 0) {
+ log_err_id(id, "Unsupported KDF NID %d.",
+ peer_hdr->kdf_nid);
+ return -ENOTSUP;
+ }
+
+ cli_rank = crypt_kdf_rank(peer_hdr->kdf_nid);
+ srv_rank = crypt_kdf_rank(kcfg->k.nid);
+
+ /*
+ * For client-encap KEM, the KDF is baked into
+ * the ciphertext. The server must use the client's
+ * KDF and can only verify the minimum.
+ */
+ if (OAP_KEX_ROLE(peer_hdr) == KEM_MODE_CLIENT_ENCAP) {
+ if (srv_rank > cli_rank) {
+ log_err_id(id, "Client KDF too weak.");
+ return -ECRYPT;
+ }
+ SET_KEX_KDF_NID(kcfg, peer_hdr->kdf_nid);
+ } else if (cli_rank > srv_rank) {
+ SET_KEX_KDF_NID(kcfg, peer_hdr->kdf_nid);
+ log_dbg_id(id, "Selected client KDF %s.",
+ md_nid_to_str(kcfg->k.nid));
+ } else if (srv_rank > 0) {
+ log_dbg_id(id, "Selected server KDF %s.",
+ md_nid_to_str(kcfg->k.nid));
+ }
+
+ if (IS_KEX_ALGO_SET(kcfg))
+ log_info_id(id, "Negotiated %s + %s.",
+ kcfg->x.str, kcfg->c.str);
+ else
+ log_info_id(id, "No key exchange.");
+
+ return 0;
+}
+
+static int do_server_kem_decap(const struct name_info * info,
+ const struct oap_hdr * peer_hdr,
+ struct sec_config * kcfg,
+ struct crypt_sk * sk)
+{
+ buffer_t ct;
+ void * server_pkp = NULL;
+ int ret;
+ uint8_t * id = peer_hdr->id.data;
+
+ ret = load_server_kem_keypair(info->name,
+ peer_hdr->kex_flags.fmt,
+ &server_pkp);
+ if (ret < 0)
+ return ret;
+
+ ct.data = peer_hdr->kex.data;
+ ct.len = peer_hdr->kex.len;
+
+ ret = kex_kem_decap(server_pkp, ct, kcfg->k.nid, sk->key);
+
+ crypt_free_key(server_pkp);
+
+ if (ret < 0) {
+ log_err_id(id, "Failed to decapsulate KEM.");
+ return -ECRYPT;
+ }
+
+ log_dbg_id(id, "Client encaps: decapsulated CT.");
+
+ return 0;
+}
+
+static int do_server_kem_encap(const struct oap_hdr * peer_hdr,
+ struct sec_config * kcfg,
+ buffer_t * kex,
+ struct crypt_sk * sk)
+{
+ buffer_t client_pk;
+ ssize_t ct_len;
+ uint8_t * id = peer_hdr->id.data;
+
+ client_pk.data = peer_hdr->kex.data;
+ client_pk.len = peer_hdr->kex.len;
+
+ if (IS_HYBRID_KEM(kcfg->x.str))
+ ct_len = kex_kem_encap_raw(client_pk, kex->data,
+ kcfg->k.nid, sk->key);
+ else
+ ct_len = kex_kem_encap(client_pk, kex->data,
+ kcfg->k.nid, sk->key);
+
+ if (ct_len < 0) {
+ log_err_id(id, "Failed to encapsulate KEM.");
+ return -ECRYPT;
+ }
+
+ kex->len = (size_t) ct_len;
+
+ log_dbg_id(id, "Server encaps: generated CT, len=%zd.", ct_len);
+
+ return 0;
+}
+
+static int do_server_kex_kem(const struct name_info * info,
+ struct oap_hdr * peer_hdr,
+ struct sec_config * kcfg,
+ buffer_t * kex,
+ struct crypt_sk * sk)
+{
+ int ret;
+
+ kcfg->x.mode = peer_hdr->kex_flags.role;
+
+ if (kcfg->x.mode == KEM_MODE_CLIENT_ENCAP) {
+ ret = do_server_kem_decap(info, peer_hdr, kcfg, sk);
+ kex->len = 0;
+ } else {
+ ret = do_server_kem_encap(peer_hdr, kcfg, kex, sk);
+ }
+
+ return ret;
+}
+
+static int do_server_kex_dhe(const struct oap_hdr * peer_hdr,
+ struct sec_config * kcfg,
+ buffer_t * kex,
+ struct crypt_sk * sk)
+{
+ ssize_t key_len;
+ void * epkp;
+ int ret;
+ uint8_t * id = peer_hdr->id.data;
+
+ key_len = kex_pkp_create(kcfg, &epkp, kex->data);
+ if (key_len < 0) {
+ log_err_id(id, "Failed to generate key pair.");
+ return -ECRYPT;
+ }
+
+ kex->len = (size_t) key_len;
+
+ log_dbg_id(id, "Generated %s ephemeral keys.", kcfg->x.str);
+
+ ret = kex_dhe_derive(kcfg, epkp, peer_hdr->kex, sk->key);
+ if (ret < 0) {
+ log_err_id(id, "Failed to derive secret.");
+ kex_pkp_destroy(epkp);
+ return -ECRYPT;
+ }
+
+ kex_pkp_destroy(epkp);
+
+ return 0;
+}
+
+int do_server_kex(const struct name_info * info,
+ struct oap_hdr * peer_hdr,
+ struct sec_config * kcfg,
+ buffer_t * kex,
+ struct crypt_sk * sk)
+{
+ char algo_buf[KEX_ALGO_BUFSZ];
+ int srv_kex_nid;
+ uint8_t * id;
+
+ id = peer_hdr->id.data;
+
+ /* No KEX data from client */
+ if (peer_hdr->kex.len == 0) {
+ if (IS_KEX_ALGO_SET(kcfg)) {
+ log_warn_id(id, "KEX requested without info.");
+ return -ECRYPT;
+ }
+ return 0;
+ }
+
+ if (negotiate_cipher(peer_hdr, kcfg) < 0)
+ return -ECRYPT;
+
+ /* Save server's configured KEX before overwriting */
+ srv_kex_nid = kcfg->x.nid;
+
+ if (OAP_KEX_ROLE(peer_hdr) != KEM_MODE_CLIENT_ENCAP) {
+ /* Server encapsulation or DHE: extract algo from DER PK */
+ if (get_algo_from_peer_key(peer_hdr, algo_buf) < 0)
+ return -ECRYPT;
+
+ SET_KEX_ALGO(kcfg, algo_buf);
+
+ /* Reject if client KEX is weaker than server's */
+ if (crypt_kex_rank(kcfg->x.nid)
+ < crypt_kex_rank(srv_kex_nid)) {
+ log_err_id(id, "Client KEX %s too weak.",
+ kcfg->x.str);
+ return -ECRYPT;
+ }
+ }
+
+ /* Dispatch based on algorithm type */
+ if (IS_KEM_ALGORITHM(kcfg->x.str))
+ return do_server_kex_kem(info, peer_hdr, kcfg, kex, sk);
+ else
+ return do_server_kex_dhe(peer_hdr, kcfg, kex, sk);
+}
+
+int oap_srv_process(const struct name_info * info,
+ buffer_t req_buf,
+ buffer_t * rsp_buf,
+ buffer_t * data,
+ struct crypt_sk * sk)
+{
+ struct oap_hdr peer_hdr;
+ struct oap_hdr local_hdr;
+ struct sec_config kcfg;
+ uint8_t kex_buf[MSGBUFSZ];
+ uint8_t hash_buf[MAX_HASH_SIZE];
+ buffer_t req_hash = BUF_INIT;
+ ssize_t hash_ret;
+ char cli_name[NAME_SIZE + 1]; /* TODO */
+ uint8_t * id;
+ void * pkp = NULL;
+ void * crt = NULL;
+ int req_md_nid;
+
+ assert(info != NULL);
+ assert(rsp_buf != NULL);
+ assert(data != NULL);
+ assert(sk != NULL);
+
+ sk->nid = NID_undef;
+
+ memset(&peer_hdr, 0, sizeof(peer_hdr));
+ memset(&local_hdr, 0, sizeof(local_hdr));
+ clrbuf(*rsp_buf);
+
+ log_dbg("Processing OAP request for %s.", info->name);
+
+ if (load_srv_credentials(info, &pkp, &crt) < 0) {
+ log_err("Failed to load security keys for %s.", info->name);
+ goto fail_cred;
+ }
+
+ if (load_srv_kex_config(info, &kcfg) < 0) {
+ log_err("Failed to load KEX config for %s.", info->name);
+ goto fail_kex;
+ }
+
+ /* Decode incoming header (NID_undef = request, no hash) */
+ if (oap_hdr_decode(&peer_hdr, req_buf, NID_undef) < 0) {
+ log_err("Failed to decode OAP header.");
+ goto fail_auth;
+ }
+
+ debug_oap_hdr_rcv(&peer_hdr);
+
+ id = peer_hdr.id.data; /* Logging */
+
+ if (oap_check_hdr(&peer_hdr) < 0) {
+ log_err_id(id, "OAP header failed replay check.");
+ goto fail_auth;
+ }
+
+ oap_hdr_init(&local_hdr, peer_hdr.id, kex_buf, *data, NID_undef);
+
+ if (oap_auth_peer(cli_name, &local_hdr, &peer_hdr) < 0) {
+ log_err_id(id, "Failed to authenticate client.");
+ goto fail_auth;
+ }
+
+ if (do_server_kex(info, &peer_hdr, &kcfg, &local_hdr.kex, sk) < 0)
+ goto fail_kex;
+
+ sk->nid = kcfg.c.nid;
+
+ /* Build response header with hash of client request */
+ local_hdr.nid = sk->nid;
+
+ /* Use client's md_nid, defaulting to SHA-384 for PQC */
+ req_md_nid = peer_hdr.md_nid != NID_undef ?
+ peer_hdr.md_nid : NID_sha384;
+
+ /* Compute request hash using client's md_nid */
+ hash_ret = md_digest(req_md_nid, req_buf, hash_buf);
+ if (hash_ret < 0) {
+ log_err_id(id, "Failed to hash request.");
+ goto fail_auth;
+ }
+ req_hash.data = hash_buf;
+ req_hash.len = (size_t) hash_ret;
+
+ if (oap_hdr_encode(&local_hdr, pkp, crt, &kcfg,
+ req_hash, req_md_nid) < 0) {
+ log_err_id(id, "Failed to create OAP response header.");
+ goto fail_auth;
+ }
+
+ debug_oap_hdr_snd(&local_hdr);
+
+ if (oap_hdr_copy_data(&peer_hdr, data) < 0) {
+ log_err_id(id, "Failed to copy client data.");
+ goto fail_data;
+ }
+
+ /* Transfer ownership of response buffer */
+ *rsp_buf = local_hdr.hdr;
+
+ log_info_id(id, "OAP request processed for %s.", info->name);
+
+ crypt_free_crt(crt);
+ crypt_free_key(pkp);
+
+ return 0;
+
+ fail_data:
+ oap_hdr_fini(&local_hdr);
+ fail_auth:
+ crypt_free_crt(crt);
+ crypt_free_key(pkp);
+ fail_cred:
+ return -EAUTH;
+
+ fail_kex:
+ crypt_free_crt(crt);
+ crypt_free_key(pkp);
+ return -ECRYPT;
+}
diff --git a/src/irmd/oap/tests/CMakeLists.txt b/src/irmd/oap/tests/CMakeLists.txt
new file mode 100644
index 00000000..b534cb72
--- /dev/null
+++ b/src/irmd/oap/tests/CMakeLists.txt
@@ -0,0 +1,64 @@
+get_filename_component(PARENT_PATH ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY)
+get_filename_component(PARENT_DIR ${PARENT_PATH} NAME)
+
+get_filename_component(OAP_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" DIRECTORY)
+get_filename_component(OAP_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}" DIRECTORY)
+get_filename_component(IRMD_SOURCE_DIR "${OAP_SOURCE_DIR}" DIRECTORY)
+get_filename_component(IRMD_BINARY_DIR "${OAP_BINARY_DIR}" DIRECTORY)
+
+compute_test_prefix()
+
+create_test_sourcelist(${PARENT_DIR}_tests test_suite.c
+ # Add new tests here
+ oap_test.c
+)
+
+create_test_sourcelist(${PARENT_DIR}_ml_dsa_tests test_suite_ml_dsa.c
+ # ML-DSA-specific tests
+ oap_test_ml_dsa.c
+)
+
+# OAP test needs io.c compiled with OAP_TEST_MODE
+set(OAP_TEST_SOURCES
+ ${OAP_SOURCE_DIR}/io.c
+ ${OAP_SOURCE_DIR}/hdr.c
+ ${OAP_SOURCE_DIR}/auth.c
+ ${OAP_SOURCE_DIR}/srv.c
+ ${OAP_SOURCE_DIR}/cli.c
+ ${CMAKE_CURRENT_SOURCE_DIR}/common.c
+)
+
+# Regular test executable (ECDSA)
+add_executable(${PARENT_DIR}_test ${${PARENT_DIR}_tests} ${OAP_TEST_SOURCES})
+set_source_files_properties(${OAP_TEST_SOURCES}
+ PROPERTIES COMPILE_DEFINITIONS "OAP_TEST_MODE"
+)
+
+disable_test_logging_for_target(${PARENT_DIR}_test)
+target_link_libraries(${PARENT_DIR}_test ouroboros-irm)
+target_include_directories(${PARENT_DIR}_test PRIVATE
+ ${IRMD_SOURCE_DIR}
+ ${IRMD_BINARY_DIR}
+)
+
+# ML-DSA test executable
+add_executable(${PARENT_DIR}_ml_dsa_test ${${PARENT_DIR}_ml_dsa_tests} ${OAP_TEST_SOURCES})
+set_source_files_properties(${OAP_TEST_SOURCES}
+ TARGET_DIRECTORY ${PARENT_DIR}_ml_dsa_test
+ PROPERTIES COMPILE_DEFINITIONS "OAP_TEST_MODE"
+)
+
+disable_test_logging_for_target(${PARENT_DIR}_ml_dsa_test)
+target_link_libraries(${PARENT_DIR}_ml_dsa_test ouroboros-irm)
+target_include_directories(${PARENT_DIR}_ml_dsa_test PRIVATE
+ ${IRMD_SOURCE_DIR}
+ ${IRMD_BINARY_DIR}
+)
+
+add_dependencies(build_tests ${PARENT_DIR}_test ${PARENT_DIR}_ml_dsa_test)
+
+# Regular tests
+ouroboros_register_tests(TARGET ${PARENT_DIR}_test TESTS ${${PARENT_DIR}_tests})
+
+# ML-DSA tests
+ouroboros_register_tests(TARGET ${PARENT_DIR}_ml_dsa_test TESTS ${${PARENT_DIR}_ml_dsa_tests})
diff --git a/src/irmd/oap/tests/common.c b/src/irmd/oap/tests/common.c
new file mode 100644
index 00000000..0a1af100
--- /dev/null
+++ b/src/irmd/oap/tests/common.c
@@ -0,0 +1,457 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * Common test helper functions for OAP tests
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#include "common.h"
+
+#include <ouroboros/crypt.h>
+
+#include "oap.h"
+
+#include <string.h>
+#include <stdio.h>
+
+int load_srv_kex_config(const struct name_info * info,
+ struct sec_config * cfg)
+{
+ (void) info;
+
+ memset(cfg, 0, sizeof(*cfg));
+
+ if (test_cfg.srv.kex == NID_undef)
+ return 0;
+
+ SET_KEX_ALGO_NID(cfg, test_cfg.srv.kex);
+ SET_KEX_CIPHER_NID(cfg, test_cfg.srv.cipher);
+ SET_KEX_KDF_NID(cfg, test_cfg.srv.kdf);
+ SET_KEX_DIGEST_NID(cfg, test_cfg.srv.md);
+ SET_KEX_KEM_MODE(cfg, test_cfg.srv.kem_mode);
+
+ return 0;
+}
+
+int load_cli_kex_config(const struct name_info * info,
+ struct sec_config * cfg)
+{
+ (void) info;
+
+ memset(cfg, 0, sizeof(*cfg));
+
+ if (test_cfg.cli.kex == NID_undef)
+ return 0;
+
+ SET_KEX_ALGO_NID(cfg, test_cfg.cli.kex);
+ SET_KEX_CIPHER_NID(cfg, test_cfg.cli.cipher);
+ SET_KEX_KDF_NID(cfg, test_cfg.cli.kdf);
+ SET_KEX_DIGEST_NID(cfg, test_cfg.cli.md);
+ SET_KEX_KEM_MODE(cfg, test_cfg.cli.kem_mode);
+
+ return 0;
+}
+
+int load_srv_credentials(const struct name_info * info,
+ void ** pkp,
+ void ** crt)
+{
+ (void) info;
+
+ *pkp = NULL;
+ *crt = NULL;
+
+ if (!test_cfg.srv.auth)
+ return 0;
+
+ return mock_load_credentials(pkp, crt);
+}
+
+int load_cli_credentials(const struct name_info * info,
+ void ** pkp,
+ void ** crt)
+{
+ (void) info;
+
+ *pkp = NULL;
+ *crt = NULL;
+
+ if (!test_cfg.cli.auth)
+ return 0;
+
+ return mock_load_credentials(pkp, crt);
+}
+
+int oap_test_setup(struct oap_test_ctx * ctx,
+ const char * root_ca_str,
+ const char * im_ca_str)
+{
+ memset(ctx, 0, sizeof(*ctx));
+
+ strcpy(ctx->srv.info.name, "test-1.unittest.o7s");
+ strcpy(ctx->cli.info.name, "test-1.unittest.o7s");
+
+ if (oap_auth_init() < 0) {
+ printf("Failed to init OAP.\n");
+ goto fail_init;
+ }
+
+ if (crypt_load_crt_str(root_ca_str, &ctx->root_ca) < 0) {
+ printf("Failed to load root CA cert.\n");
+ goto fail_root_ca;
+ }
+
+ if (crypt_load_crt_str(im_ca_str, &ctx->im_ca) < 0) {
+ printf("Failed to load intermediate CA cert.\n");
+ goto fail_im_ca;
+ }
+
+ if (oap_auth_add_ca_crt(ctx->root_ca) < 0) {
+ printf("Failed to add root CA cert to store.\n");
+ goto fail_add_ca;
+ }
+
+ if (oap_auth_add_ca_crt(ctx->im_ca) < 0) {
+ printf("Failed to add intermediate CA cert to store.\n");
+ goto fail_add_ca;
+ }
+
+ return 0;
+
+ fail_add_ca:
+ crypt_free_crt(ctx->im_ca);
+ fail_im_ca:
+ crypt_free_crt(ctx->root_ca);
+ fail_root_ca:
+ oap_auth_fini();
+ fail_init:
+ memset(ctx, 0, sizeof(*ctx));
+ return -1;
+}
+
+void oap_test_teardown(struct oap_test_ctx * ctx)
+{
+ struct crypt_sk res;
+ buffer_t dummy = BUF_INIT;
+
+ if (ctx->cli.state != NULL) {
+ res.key = ctx->cli.key;
+ oap_cli_complete(ctx->cli.state, &ctx->cli.info, dummy,
+ &ctx->data, &res);
+ ctx->cli.state = NULL;
+ }
+
+ freebuf(ctx->data);
+ freebuf(ctx->resp_hdr);
+ freebuf(ctx->req_hdr);
+
+ crypt_free_crt(ctx->im_ca);
+ crypt_free_crt(ctx->root_ca);
+
+ oap_auth_fini();
+ memset(ctx, 0, sizeof(*ctx));
+}
+
+int oap_cli_prepare_ctx(struct oap_test_ctx * ctx)
+{
+ return oap_cli_prepare(&ctx->cli.state, &ctx->cli.info, &ctx->req_hdr,
+ ctx->data);
+}
+
+int oap_srv_process_ctx(struct oap_test_ctx * ctx)
+{
+ struct crypt_sk res = { .nid = NID_undef, .key = ctx->srv.key };
+ int ret;
+
+ ret = oap_srv_process(&ctx->srv.info, ctx->req_hdr,
+ &ctx->resp_hdr, &ctx->data, &res);
+ if (ret == 0)
+ ctx->srv.nid = res.nid;
+
+ return ret;
+}
+
+int oap_cli_complete_ctx(struct oap_test_ctx * ctx)
+{
+ struct crypt_sk res = { .nid = NID_undef, .key = ctx->cli.key };
+ int ret;
+
+ ret = oap_cli_complete(ctx->cli.state, &ctx->cli.info, ctx->resp_hdr,
+ &ctx->data, &res);
+ ctx->cli.state = NULL;
+
+ if (ret == 0)
+ ctx->cli.nid = res.nid;
+
+ return ret;
+}
+
+int roundtrip_auth_only(const char * root_ca,
+ const char * im_ca_str)
+{
+ struct oap_test_ctx ctx;
+
+ TEST_START();
+
+ if (oap_test_setup(&ctx, root_ca, im_ca_str) < 0)
+ goto fail;
+
+ if (oap_cli_prepare_ctx(&ctx) < 0) {
+ printf("Client prepare failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (oap_srv_process_ctx(&ctx) < 0) {
+ printf("Server process failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (oap_cli_complete_ctx(&ctx) < 0) {
+ printf("Client complete failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (ctx.cli.nid != NID_undef || ctx.srv.nid != NID_undef) {
+ printf("Cipher should not be set for auth-only.\n");
+ goto fail_cleanup;
+ }
+
+ oap_test_teardown(&ctx);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_cleanup:
+ oap_test_teardown(&ctx);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+int roundtrip_kex_only(void)
+{
+ struct name_info cli_info;
+ struct name_info srv_info;
+ struct crypt_sk res;
+ uint8_t cli_key[SYMMKEYSZ];
+ uint8_t srv_key[SYMMKEYSZ];
+ int cli_nid;
+ int srv_nid;
+ buffer_t req_hdr = BUF_INIT;
+ buffer_t resp_hdr = BUF_INIT;
+ buffer_t data = BUF_INIT;
+ void * cli_state = NULL;
+
+ TEST_START();
+
+ memset(&cli_info, 0, sizeof(cli_info));
+ memset(&srv_info, 0, sizeof(srv_info));
+
+ strcpy(cli_info.name, "test-1.unittest.o7s");
+ strcpy(srv_info.name, "test-1.unittest.o7s");
+
+ if (oap_auth_init() < 0) {
+ printf("Failed to init OAP.\n");
+ goto fail;
+ }
+
+ if (oap_cli_prepare(&cli_state, &cli_info, &req_hdr,
+ data) < 0) {
+ printf("Client prepare failed.\n");
+ goto fail_cleanup;
+ }
+
+ res.key = srv_key;
+
+ if (oap_srv_process(&srv_info, req_hdr, &resp_hdr, &data, &res) < 0) {
+ printf("Server process failed.\n");
+ goto fail_cleanup;
+ }
+
+ srv_nid = res.nid;
+
+ res.key = cli_key;
+
+ if (oap_cli_complete(cli_state, &cli_info, resp_hdr, &data, &res) < 0) {
+ printf("Client complete failed.\n");
+ cli_state = NULL;
+ goto fail_cleanup;
+ }
+
+ cli_nid = res.nid;
+ cli_state = NULL;
+
+ if (memcmp(cli_key, srv_key, SYMMKEYSZ) != 0) {
+ printf("Client and server keys do not match!\n");
+ goto fail_cleanup;
+ }
+
+ if (cli_nid == NID_undef || srv_nid == NID_undef) {
+ printf("Cipher should be set for kex-only.\n");
+ goto fail_cleanup;
+ }
+
+ freebuf(resp_hdr);
+ freebuf(req_hdr);
+ oap_auth_fini();
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_cleanup:
+ if (cli_state != NULL) {
+ res.key = cli_key;
+ oap_cli_complete(cli_state, &cli_info, resp_hdr, &data, &res);
+ }
+ freebuf(resp_hdr);
+ freebuf(req_hdr);
+ oap_auth_fini();
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+int corrupted_request(const char * root_ca,
+ const char * im_ca_str)
+{
+ struct oap_test_ctx ctx;
+
+ TEST_START();
+
+ if (oap_test_setup(&ctx, root_ca, im_ca_str) < 0)
+ goto fail;
+
+ if (oap_cli_prepare_ctx(&ctx) < 0) {
+ printf("Client prepare failed.\n");
+ goto fail_cleanup;
+ }
+
+ /* Corrupt the request */
+ if (ctx.req_hdr.len > 100) {
+ ctx.req_hdr.data[50] ^= 0xFF;
+ ctx.req_hdr.data[51] ^= 0xAA;
+ ctx.req_hdr.data[52] ^= 0x55;
+ }
+
+ if (oap_srv_process_ctx(&ctx) == 0) {
+ printf("Server should reject corrupted request.\n");
+ goto fail_cleanup;
+ }
+
+ oap_test_teardown(&ctx);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_cleanup:
+ oap_test_teardown(&ctx);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+int corrupted_response(const char * root_ca,
+ const char * im_ca_str)
+{
+ struct oap_test_ctx ctx;
+ struct crypt_sk res;
+
+ TEST_START();
+
+ if (oap_test_setup(&ctx, root_ca, im_ca_str) < 0)
+ goto fail;
+
+ if (oap_cli_prepare_ctx(&ctx) < 0) {
+ printf("Client prepare failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (oap_srv_process_ctx(&ctx) < 0) {
+ printf("Server process failed.\n");
+ goto fail_cleanup;
+ }
+
+ /* Corrupt the response */
+ if (ctx.resp_hdr.len > 100) {
+ ctx.resp_hdr.data[50] ^= 0xFF;
+ ctx.resp_hdr.data[51] ^= 0xAA;
+ ctx.resp_hdr.data[52] ^= 0x55;
+ }
+
+ res.key = ctx.cli.key;
+
+ if (oap_cli_complete(ctx.cli.state, &ctx.cli.info, ctx.resp_hdr,
+ &ctx.data, &res) == 0) {
+ printf("Client should reject corrupted response.\n");
+ ctx.cli.state = NULL;
+ goto fail_cleanup;
+ }
+
+ ctx.cli.state = NULL;
+
+ oap_test_teardown(&ctx);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_cleanup:
+ oap_test_teardown(&ctx);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+int truncated_request(const char * root_ca,
+ const char * im_ca_str)
+{
+ struct oap_test_ctx ctx;
+ size_t orig_len;
+
+ TEST_START();
+
+ if (oap_test_setup(&ctx, root_ca, im_ca_str) < 0)
+ goto fail;
+
+ if (oap_cli_prepare_ctx(&ctx) < 0) {
+ printf("Client prepare failed.\n");
+ goto fail_cleanup;
+ }
+
+ /* Truncate the request buffer */
+ orig_len = ctx.req_hdr.len;
+ ctx.req_hdr.len = orig_len / 2;
+
+ if (oap_srv_process_ctx(&ctx) == 0) {
+ printf("Server should reject truncated request.\n");
+ ctx.req_hdr.len = orig_len;
+ goto fail_cleanup;
+ }
+
+ ctx.req_hdr.len = orig_len;
+
+ oap_test_teardown(&ctx);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_cleanup:
+ oap_test_teardown(&ctx);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
diff --git a/src/irmd/oap/tests/common.h b/src/irmd/oap/tests/common.h
new file mode 100644
index 00000000..d4b6733a
--- /dev/null
+++ b/src/irmd/oap/tests/common.h
@@ -0,0 +1,100 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * Common test helper functions for OAP tests
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#ifndef IRMD_TESTS_COMMON_H
+#define IRMD_TESTS_COMMON_H
+
+#include <ouroboros/utils.h>
+#include <ouroboros/flow.h>
+#include <ouroboros/name.h>
+#include <test/test.h>
+
+#include <stdbool.h>
+
+/* Per-side security configuration for tests */
+struct test_sec_cfg {
+ int kex; /* KEX algorithm NID */
+ int cipher; /* Cipher NID for encryption */
+ int kdf; /* KDF NID for key derivation */
+ int md; /* Digest NID for signatures */
+ int kem_mode; /* KEM encapsulation mode (0 for ECDH) */
+ bool auth; /* Use authentication (certificates) */
+};
+
+/* Test configuration - set by each test before running roundtrip */
+extern struct test_cfg {
+ struct test_sec_cfg srv;
+ struct test_sec_cfg cli;
+} test_cfg;
+
+/* Each test file defines this with its own certificates */
+extern int mock_load_credentials(void ** pkp,
+ void ** crt);
+
+/* Per-side test context */
+struct oap_test_side {
+ struct name_info info;
+ struct flow_info flow;
+ uint8_t key[SYMMKEYSZ];
+ int nid;
+ void * state;
+};
+
+/* Test context - holds all common state for OAP tests */
+struct oap_test_ctx {
+ struct oap_test_side srv;
+ struct oap_test_side cli;
+
+ buffer_t req_hdr;
+ buffer_t resp_hdr;
+ buffer_t data;
+ void * root_ca;
+ void * im_ca;
+};
+
+int oap_test_setup(struct oap_test_ctx * ctx,
+ const char * root_ca_str,
+ const char * im_ca_str);
+
+void oap_test_teardown(struct oap_test_ctx * ctx);
+
+int oap_cli_prepare_ctx(struct oap_test_ctx * ctx);
+
+int oap_srv_process_ctx(struct oap_test_ctx * ctx);
+
+int oap_cli_complete_ctx(struct oap_test_ctx * ctx);
+
+int roundtrip_auth_only(const char * root_ca,
+ const char * im_ca_str);
+
+int roundtrip_kex_only(void);
+
+int corrupted_request(const char * root_ca,
+ const char * im_ca_str);
+
+int corrupted_response(const char * root_ca,
+ const char * im_ca_str);
+
+int truncated_request(const char * root_ca,
+ const char * im_ca_str);
+
+#endif /* IRMD_TESTS_COMMON_H */
diff --git a/src/irmd/oap/tests/oap_test.c b/src/irmd/oap/tests/oap_test.c
new file mode 100644
index 00000000..2f0f0b4d
--- /dev/null
+++ b/src/irmd/oap/tests/oap_test.c
@@ -0,0 +1,1181 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * Unit tests of Ouroboros Allocation Protocol (OAP)
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#if defined(__linux__) || defined(__CYGWIN__)
+ #ifndef _DEFAULT_SOURCE
+ #define _DEFAULT_SOURCE
+ #endif
+#else
+#define _POSIX_C_SOURCE 200809L
+#endif
+
+#include "config.h"
+
+#include <ouroboros/crypt.h>
+#include <ouroboros/endian.h>
+#include <ouroboros/flow.h>
+#include <ouroboros/name.h>
+#include <ouroboros/random.h>
+#include <ouroboros/time.h>
+
+#include <test/test.h>
+#include <test/certs/ecdsa.h>
+
+#include "oap.h"
+#include "common.h"
+
+#include <stdbool.h>
+#include <string.h>
+
+#ifdef HAVE_OPENSSL
+#include <openssl/evp.h>
+#endif
+
+#define AUTH true
+#define NO_AUTH false
+
+extern const uint16_t kex_supported_nids[];
+extern const uint16_t md_supported_nids[];
+
+struct test_cfg test_cfg;
+
+/* Mock load - called by load_*_credentials in common.c */
+int mock_load_credentials(void ** pkp,
+ void ** crt)
+{
+ *crt = NULL;
+
+ if (crypt_load_privkey_str(server_pkp_ec, pkp) < 0)
+ goto fail_privkey;
+
+ if (crypt_load_crt_str(signed_server_crt_ec, crt) < 0)
+ goto fail_crt;
+
+ return 0;
+
+ fail_crt:
+ crypt_free_key(*pkp);
+ fail_privkey:
+ *pkp = NULL;
+ return -1;
+}
+
+/* Stub KEM functions - ECDSA tests don't use KEM */
+int load_server_kem_keypair(__attribute__((unused)) const char * name,
+ __attribute__((unused)) bool raw_fmt,
+ __attribute__((unused)) void ** pkp)
+{
+ return -1;
+}
+
+int load_server_kem_pk(__attribute__((unused)) const char * name,
+ __attribute__((unused)) struct sec_config * cfg,
+ __attribute__((unused)) buffer_t * pk)
+{
+ return -1;
+}
+
+static void test_default_cfg(void)
+{
+ memset(&test_cfg, 0, sizeof(test_cfg));
+
+ /* Server: X25519, AES-256-GCM, SHA-256, with auth */
+ test_cfg.srv.kex = NID_X25519;
+ test_cfg.srv.cipher = NID_aes_256_gcm;
+ test_cfg.srv.kdf = NID_sha256;
+ test_cfg.srv.md = NID_sha256;
+ test_cfg.srv.auth = AUTH;
+
+ /* Client: same KEX/cipher/kdf/md, no auth */
+ test_cfg.cli.kex = NID_X25519;
+ test_cfg.cli.cipher = NID_aes_256_gcm;
+ test_cfg.cli.kdf = NID_sha256;
+ test_cfg.cli.md = NID_sha256;
+ test_cfg.cli.auth = NO_AUTH;
+}
+
+static int test_oap_auth_init_fini(void)
+{
+ TEST_START();
+
+ if (oap_auth_init() < 0) {
+ printf("Failed to init OAP.\n");
+ goto fail;
+ }
+
+ oap_auth_fini();
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_oap_roundtrip(int kex)
+{
+ struct oap_test_ctx ctx;
+ const char * kex_str = kex_nid_to_str(kex);
+
+ TEST_START("(%s)", kex_str);
+
+ test_default_cfg();
+ test_cfg.srv.kex = kex;
+ test_cfg.cli.kex = kex;
+
+ if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0)
+ goto fail;
+
+ if (oap_cli_prepare_ctx(&ctx) < 0) {
+ printf("Client prepare failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (oap_srv_process_ctx(&ctx) < 0) {
+ printf("Server process failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (oap_cli_complete_ctx(&ctx) < 0) {
+ printf("Client complete failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (memcmp(ctx.cli.key, ctx.srv.key, SYMMKEYSZ) != 0) {
+ printf("Client and server keys do not match!\n");
+ goto fail_cleanup;
+ }
+
+ if (ctx.cli.nid == NID_undef || ctx.srv.nid == NID_undef) {
+ printf("Cipher not set in flow.\n");
+ goto fail_cleanup;
+ }
+
+ oap_test_teardown(&ctx);
+
+ TEST_SUCCESS("(%s)", kex_str);
+ return TEST_RC_SUCCESS;
+
+ fail_cleanup:
+ oap_test_teardown(&ctx);
+ fail:
+ TEST_FAIL("(%s)", kex_str);
+ return TEST_RC_FAIL;
+}
+
+static int test_oap_roundtrip_auth_only(void)
+{
+ memset(&test_cfg, 0, sizeof(test_cfg));
+
+ /* Server: auth only, no encryption */
+ test_cfg.srv.md = NID_sha256;
+ test_cfg.srv.auth = AUTH;
+
+ /* Client: no auth, no encryption */
+ test_cfg.cli.md = NID_sha256;
+ test_cfg.cli.auth = NO_AUTH;
+
+ return roundtrip_auth_only(root_ca_crt_ec, im_ca_crt_ec);
+}
+
+static int test_oap_roundtrip_kex_only(void)
+{
+ memset(&test_cfg, 0, sizeof(test_cfg));
+
+ /* Server: KEX only, no auth */
+ test_cfg.srv.kex = NID_X25519;
+ test_cfg.srv.cipher = NID_aes_256_gcm;
+ test_cfg.srv.kdf = NID_sha256;
+ test_cfg.srv.md = NID_sha256;
+ test_cfg.srv.auth = NO_AUTH;
+
+ /* Client: KEX only, no auth */
+ test_cfg.cli.kex = NID_X25519;
+ test_cfg.cli.cipher = NID_aes_256_gcm;
+ test_cfg.cli.kdf = NID_sha256;
+ test_cfg.cli.md = NID_sha256;
+ test_cfg.cli.auth = NO_AUTH;
+
+ return roundtrip_kex_only();
+}
+
+static int test_oap_piggyback_data(void)
+{
+ struct oap_test_ctx ctx;
+ const char * cli_data_str = "client_data";
+ const char * srv_data_str = "server_data";
+ buffer_t srv_data = BUF_INIT;
+
+ TEST_START();
+
+ test_default_cfg();
+
+ if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0)
+ goto fail;
+
+ /* Client prepares request with piggybacked data */
+ ctx.data.len = strlen(cli_data_str);
+ ctx.data.data = malloc(ctx.data.len);
+ if (ctx.data.data == NULL)
+ goto fail_cleanup;
+ memcpy(ctx.data.data, cli_data_str, ctx.data.len);
+
+ if (oap_cli_prepare_ctx(&ctx) < 0)
+ goto fail_cleanup;
+
+ /* Set server's response data (ctx.data will take cli data) */
+ srv_data.len = strlen(srv_data_str);
+ srv_data.data = (uint8_t *) srv_data_str;
+
+ freebuf(ctx.data);
+ ctx.data.data = srv_data.data;
+ ctx.data.len = srv_data.len;
+ srv_data.data = NULL;
+ srv_data.len = 0;
+
+ if (oap_srv_process_ctx(&ctx) < 0)
+ goto fail_cleanup;
+
+ /* Verify server received client's piggybacked data */
+ if (ctx.data.len != strlen(cli_data_str) ||
+ memcmp(ctx.data.data, cli_data_str, ctx.data.len) != 0) {
+ printf("Server did not receive correct client data.\n");
+ goto fail_cleanup;
+ }
+
+ freebuf(ctx.data);
+
+ if (oap_cli_complete_ctx(&ctx) < 0)
+ goto fail_cleanup;
+
+ /* Verify client received server's piggybacked data */
+ if (ctx.data.len != strlen(srv_data_str) ||
+ memcmp(ctx.data.data, srv_data_str, ctx.data.len) != 0) {
+ printf("Client did not receive correct server data.\n");
+ goto fail_cleanup;
+ }
+
+ /* Free the copied data */
+ free(ctx.data.data);
+ ctx.data.data = NULL;
+ ctx.data.len = 0;
+
+ if (memcmp(ctx.cli.key, ctx.srv.key, SYMMKEYSZ) != 0) {
+ printf("Client and server keys do not match!\n");
+ goto fail_cleanup;
+ }
+
+ oap_test_teardown(&ctx);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_cleanup:
+ freebuf(srv_data);
+ oap_test_teardown(&ctx);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_oap_corrupted_request(void)
+{
+ test_default_cfg();
+ test_cfg.cli.auth = AUTH;
+
+ return corrupted_request(root_ca_crt_ec, im_ca_crt_ec);
+}
+
+static int test_oap_corrupted_response(void)
+{
+ test_default_cfg();
+
+ return corrupted_response(root_ca_crt_ec, im_ca_crt_ec);
+}
+
+static int test_oap_truncated_request(void)
+{
+ test_default_cfg();
+
+ return truncated_request(root_ca_crt_ec, im_ca_crt_ec);
+}
+
+/* After ID (16), timestamp (8), cipher_nid (2), kdf_nid (2), md (2) */
+#define OAP_CERT_LEN_OFFSET 30
+static int test_oap_inflated_length_field(void)
+{
+ struct oap_test_ctx ctx;
+ uint16_t fake;
+
+ test_default_cfg();
+
+ TEST_START();
+
+ if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0)
+ goto fail;
+
+ if (oap_cli_prepare_ctx(&ctx) < 0) {
+ printf("Client prepare failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (ctx.req_hdr.len < OAP_CERT_LEN_OFFSET + 2) {
+ printf("Request too short for test.\n");
+ goto fail_cleanup;
+ }
+
+ /* Set cert length to claim more bytes than packet contains */
+ fake = hton16(60000);
+ memcpy(ctx.req_hdr.data + OAP_CERT_LEN_OFFSET, &fake, sizeof(fake));
+
+ if (oap_srv_process_ctx(&ctx) == 0) {
+ printf("Server should reject inflated length field.\n");
+ goto fail_cleanup;
+ }
+
+ oap_test_teardown(&ctx);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_cleanup:
+ oap_test_teardown(&ctx);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+/* Attacker claims cert is smaller - causes misparse of subsequent fields */
+static int test_oap_deflated_length_field(void)
+{
+ struct oap_test_ctx ctx;
+ uint16_t fake;
+
+ test_default_cfg();
+
+ TEST_START();
+
+ if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0)
+ goto fail;
+
+ if (oap_cli_prepare_ctx(&ctx) < 0) {
+ printf("Client prepare failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (ctx.req_hdr.len < OAP_CERT_LEN_OFFSET + 2) {
+ printf("Request too short for test.\n");
+ goto fail_cleanup;
+ }
+
+ /* Set cert length to claim fewer bytes - will misparse rest */
+ fake = hton16(1);
+ memcpy(ctx.req_hdr.data + OAP_CERT_LEN_OFFSET, &fake, sizeof(fake));
+
+ if (oap_srv_process_ctx(&ctx) == 0) {
+ printf("Server should reject deflated length field.\n");
+ goto fail_cleanup;
+ }
+
+ oap_test_teardown(&ctx);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_cleanup:
+ oap_test_teardown(&ctx);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+/* Header field offsets for byte manipulation */
+#define OAP_CIPHER_NID_OFFSET 24
+#define OAP_KEX_LEN_OFFSET 32
+
+/* Server rejects request when cipher NID set but no KEX data provided */
+static int test_oap_nid_without_kex(void)
+{
+ struct oap_test_ctx ctx;
+ uint16_t cipher_nid;
+ uint16_t zero = 0;
+
+ TEST_START();
+
+ /* Configure unsigned KEX-only mode */
+ memset(&test_cfg, 0, sizeof(test_cfg));
+ test_cfg.srv.kex = NID_X25519;
+ test_cfg.srv.cipher = NID_aes_256_gcm;
+ test_cfg.srv.kdf = NID_sha256;
+ test_cfg.srv.md = NID_sha256;
+ test_cfg.srv.auth = NO_AUTH;
+ test_cfg.cli.kex = NID_X25519;
+ test_cfg.cli.cipher = NID_aes_256_gcm;
+ test_cfg.cli.kdf = NID_sha256;
+ test_cfg.cli.md = NID_sha256;
+ test_cfg.cli.auth = NO_AUTH;
+
+ if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0)
+ goto fail;
+
+ if (oap_cli_prepare_ctx(&ctx) < 0) {
+ printf("Client prepare failed.\n");
+ goto fail_cleanup;
+ }
+
+ /* Tamper: keep cipher_nid but set kex_len=0, truncate KEX data */
+ cipher_nid = hton16(NID_aes_256_gcm);
+ memcpy(ctx.req_hdr.data + OAP_CIPHER_NID_OFFSET, &cipher_nid,
+ sizeof(cipher_nid));
+ memcpy(ctx.req_hdr.data + OAP_KEX_LEN_OFFSET, &zero, sizeof(zero));
+ ctx.req_hdr.len = 36; /* Fixed header only, no KEX data */
+
+ if (oap_srv_process_ctx(&ctx) == 0) {
+ printf("Server should reject cipher NID without KEX data.\n");
+ goto fail_cleanup;
+ }
+
+ oap_test_teardown(&ctx);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_cleanup:
+ oap_test_teardown(&ctx);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+/* Server rejects OAP request with unsupported cipher NID */
+static int test_oap_unsupported_nid(void)
+{
+ struct oap_test_ctx ctx;
+ uint16_t bad_nid;
+
+ TEST_START();
+
+ /* Configure unsigned KEX-only mode */
+ memset(&test_cfg, 0, sizeof(test_cfg));
+ test_cfg.srv.kex = NID_X25519;
+ test_cfg.srv.cipher = NID_aes_256_gcm;
+ test_cfg.srv.kdf = NID_sha256;
+ test_cfg.srv.md = NID_sha256;
+ test_cfg.srv.auth = NO_AUTH;
+ test_cfg.cli.kex = NID_X25519;
+ test_cfg.cli.cipher = NID_aes_256_gcm;
+ test_cfg.cli.kdf = NID_sha256;
+ test_cfg.cli.md = NID_sha256;
+ test_cfg.cli.auth = NO_AUTH;
+
+ if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0)
+ goto fail;
+
+ if (oap_cli_prepare_ctx(&ctx) < 0) {
+ printf("Client prepare failed.\n");
+ goto fail_cleanup;
+ }
+
+ /* Tamper: set cipher_nid to unsupported value */
+ bad_nid = hton16(9999);
+ memcpy(ctx.req_hdr.data + OAP_CIPHER_NID_OFFSET, &bad_nid,
+ sizeof(bad_nid));
+
+ if (oap_srv_process_ctx(&ctx) == 0) {
+ printf("Server should reject unsupported cipher NID.\n");
+ goto fail_cleanup;
+ }
+
+ oap_test_teardown(&ctx);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_cleanup:
+ oap_test_teardown(&ctx);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_oap_roundtrip_all(void)
+{
+ int ret = 0;
+ int i;
+
+ for (i = 0; kex_supported_nids[i] != NID_undef; i++) {
+ const char * algo = kex_nid_to_str(kex_supported_nids[i]);
+
+ /* Skip KEM algorithms - tested in oap_test_ml_dsa */
+ if (IS_KEM_ALGORITHM(algo))
+ continue;
+
+ ret |= test_oap_roundtrip(kex_supported_nids[i]);
+ }
+
+ return ret;
+}
+
+/* Cipher negotiation - strongest cipher and KDF are selected */
+static int test_oap_cipher_mismatch(void)
+{
+ struct oap_test_ctx ctx;
+
+ TEST_START();
+
+ memset(&test_cfg, 0, sizeof(test_cfg));
+
+ /* Server: AES-128-GCM, SHA-256 */
+ test_cfg.srv.kex = NID_X25519;
+ test_cfg.srv.cipher = NID_aes_128_gcm;
+ test_cfg.srv.kdf = NID_sha256;
+ test_cfg.srv.md = NID_sha256;
+ test_cfg.srv.auth = AUTH;
+
+ /* Client: AES-256-GCM, SHA-512 */
+ test_cfg.cli.kex = NID_X25519;
+ test_cfg.cli.cipher = NID_aes_256_gcm;
+ test_cfg.cli.kdf = NID_sha512;
+ test_cfg.cli.md = NID_sha256;
+ test_cfg.cli.auth = NO_AUTH;
+
+ if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0)
+ goto fail;
+
+ if (oap_cli_prepare_ctx(&ctx) < 0) {
+ printf("Client prepare failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (oap_srv_process_ctx(&ctx) < 0) {
+ printf("Server process failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (oap_cli_complete_ctx(&ctx) < 0) {
+ printf("Client complete failed.\n");
+ goto fail_cleanup;
+ }
+
+ /* Verify: both should have the strongest cipher */
+ if (ctx.srv.nid != NID_aes_256_gcm) {
+ printf("Server cipher mismatch: expected %s, got %s\n",
+ crypt_nid_to_str(NID_aes_256_gcm),
+ crypt_nid_to_str(ctx.srv.nid));
+ goto fail_cleanup;
+ }
+
+ if (ctx.cli.nid != NID_aes_256_gcm) {
+ printf("Client cipher mismatch: expected %s, got %s\n",
+ crypt_nid_to_str(NID_aes_256_gcm),
+ crypt_nid_to_str(ctx.cli.nid));
+ goto fail_cleanup;
+ }
+
+ /* Parse response header to check negotiated KDF */
+ if (ctx.resp_hdr.len > 26) {
+ uint16_t resp_kdf_nid;
+ /* KDF NID at offset 26: ID(16) + ts(8) + cipher(2) */
+ resp_kdf_nid = ntoh16(*(uint16_t *)(ctx.resp_hdr.data + 26));
+
+ if (resp_kdf_nid != NID_sha512) {
+ printf("Response KDF mismatch: expected %s, got %s\n",
+ md_nid_to_str(NID_sha512),
+ md_nid_to_str(resp_kdf_nid));
+ goto fail_cleanup;
+ }
+ }
+
+ oap_test_teardown(&ctx);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_cleanup:
+ oap_test_teardown(&ctx);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+/* Server encryption, client none: server rejects (no KEX data) */
+static int test_oap_srv_enc_cli_none(void)
+{
+ struct oap_test_ctx ctx;
+
+ TEST_START();
+
+ memset(&test_cfg, 0, sizeof(test_cfg));
+
+ /* Server: encryption configured */
+ test_cfg.srv.kex = NID_X25519;
+ test_cfg.srv.cipher = NID_aes_256_gcm;
+ test_cfg.srv.kdf = NID_sha256;
+ test_cfg.srv.md = NID_sha256;
+ test_cfg.srv.auth = AUTH;
+
+ /* Client: no encryption */
+ test_cfg.cli.md = NID_sha256;
+ test_cfg.cli.auth = NO_AUTH;
+
+ if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0)
+ goto fail;
+
+ if (oap_cli_prepare_ctx(&ctx) < 0) {
+ printf("Client prepare failed.\n");
+ goto fail_cleanup;
+ }
+
+ /* Server should reject: KEX required but client sent none */
+ if (oap_srv_process_ctx(&ctx) == 0) {
+ printf("Server should have rejected.\n");
+ goto fail_cleanup;
+ }
+
+ oap_test_teardown(&ctx);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_cleanup:
+ oap_test_teardown(&ctx);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+/* Client encryption, server none: use client settings */
+static int test_oap_cli_enc_srv_none(void)
+{
+ struct oap_test_ctx ctx;
+
+ TEST_START();
+
+ memset(&test_cfg, 0, sizeof(test_cfg));
+
+ /* Server: no encryption configured */
+ test_cfg.srv.md = NID_sha256;
+ test_cfg.srv.auth = AUTH;
+
+ /* Client: encryption configured */
+ test_cfg.cli.kex = NID_X25519;
+ test_cfg.cli.cipher = NID_aes_256_gcm;
+ test_cfg.cli.kdf = NID_sha256;
+ test_cfg.cli.md = NID_sha256;
+ test_cfg.cli.auth = NO_AUTH;
+
+ if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0)
+ goto fail;
+
+ if (oap_cli_prepare_ctx(&ctx) < 0) {
+ printf("Client prepare failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (oap_srv_process_ctx(&ctx) < 0) {
+ printf("Server process failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (oap_cli_complete_ctx(&ctx) < 0) {
+ printf("Client complete failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (memcmp(ctx.cli.key, ctx.srv.key, SYMMKEYSZ) != 0) {
+ printf("Key mismatch.\n");
+ goto fail_cleanup;
+ }
+
+ if (ctx.cli.nid != NID_aes_256_gcm) {
+ printf("Expected %s, got %s.\n",
+ crypt_nid_to_str(NID_aes_256_gcm),
+ crypt_nid_to_str(ctx.cli.nid));
+ goto fail_cleanup;
+ }
+
+ if (ctx.srv.nid != NID_aes_256_gcm) {
+ printf("Expected %s, got %s.\n",
+ crypt_nid_to_str(NID_aes_256_gcm),
+ crypt_nid_to_str(ctx.srv.nid));
+ goto fail_cleanup;
+ }
+
+ oap_test_teardown(&ctx);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_cleanup:
+ oap_test_teardown(&ctx);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+/* Client rejects server response with downgraded cipher */
+static int test_oap_cli_rejects_downgrade(void)
+{
+ struct oap_test_ctx ctx;
+ uint16_t weak;
+
+ TEST_START();
+
+ memset(&test_cfg, 0, sizeof(test_cfg));
+
+ test_cfg.srv.kex = NID_X25519;
+ test_cfg.srv.cipher = NID_aes_256_gcm;
+ test_cfg.srv.kdf = NID_sha256;
+ test_cfg.srv.md = NID_sha256;
+ test_cfg.srv.auth = AUTH;
+
+ test_cfg.cli.kex = NID_X25519;
+ test_cfg.cli.cipher = NID_aes_256_gcm;
+ test_cfg.cli.kdf = NID_sha256;
+ test_cfg.cli.md = NID_sha256;
+ test_cfg.cli.auth = NO_AUTH;
+
+ if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0)
+ goto fail;
+
+ if (oap_cli_prepare_ctx(&ctx) < 0) {
+ printf("Client prepare failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (oap_srv_process_ctx(&ctx) < 0) {
+ printf("Server process failed.\n");
+ goto fail_cleanup;
+ }
+
+ /* Tamper: replace cipher NID with weaker one */
+ weak = hton16(NID_aes_128_ctr);
+ memcpy(ctx.resp_hdr.data + OAP_CIPHER_NID_OFFSET,
+ &weak, sizeof(weak));
+
+ /* Client should reject the downgraded cipher */
+ if (oap_cli_complete_ctx(&ctx) == 0) {
+ printf("Client accepted downgrade.\n");
+ goto fail_cleanup;
+ }
+
+ oap_test_teardown(&ctx);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_cleanup:
+ oap_test_teardown(&ctx);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+/* Server rejects client with weaker KEX algorithm */
+static int test_oap_srv_rejects_weak_kex(void)
+{
+ struct oap_test_ctx ctx;
+
+ TEST_START();
+
+ memset(&test_cfg, 0, sizeof(test_cfg));
+
+ /* Server: secp521r1 (strong) */
+ test_cfg.srv.kex = NID_secp521r1;
+ test_cfg.srv.cipher = NID_aes_256_gcm;
+ test_cfg.srv.kdf = NID_sha256;
+ test_cfg.srv.md = NID_sha256;
+ test_cfg.srv.auth = AUTH;
+
+ /* Client: ffdhe2048 (weakest) */
+ test_cfg.cli.kex = NID_ffdhe2048;
+ test_cfg.cli.cipher = NID_aes_256_gcm;
+ test_cfg.cli.kdf = NID_sha256;
+ test_cfg.cli.md = NID_sha256;
+ test_cfg.cli.auth = NO_AUTH;
+
+ if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0)
+ goto fail;
+
+ if (oap_cli_prepare_ctx(&ctx) < 0) {
+ printf("Client prepare failed.\n");
+ goto fail_cleanup;
+ }
+
+ /* Server should reject: client KEX too weak */
+ if (oap_srv_process_ctx(&ctx) == 0) {
+ printf("Server should reject weak KEX.\n");
+ goto fail_cleanup;
+ }
+
+ oap_test_teardown(&ctx);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_cleanup:
+ oap_test_teardown(&ctx);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+/* Test roundtrip with different signature digest algorithms */
+static int test_oap_roundtrip_md(int md)
+{
+ struct oap_test_ctx ctx;
+ const char * md_str = md_nid_to_str(md);
+
+ TEST_START("(%s)", md_str ? md_str : "default");
+
+ memset(&test_cfg, 0, sizeof(test_cfg));
+
+ /* Server: auth + KEX with specified md */
+ test_cfg.srv.kex = NID_X25519;
+ test_cfg.srv.cipher = NID_aes_256_gcm;
+ test_cfg.srv.kdf = NID_sha256;
+ test_cfg.srv.md = md;
+ test_cfg.srv.auth = AUTH;
+
+ /* Client: no auth */
+ test_cfg.cli.kex = NID_X25519;
+ test_cfg.cli.cipher = NID_aes_256_gcm;
+ test_cfg.cli.kdf = NID_sha256;
+ test_cfg.cli.md = md;
+ test_cfg.cli.auth = NO_AUTH;
+
+ if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0)
+ goto fail;
+
+ if (oap_cli_prepare_ctx(&ctx) < 0) {
+ printf("Client prepare failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (oap_srv_process_ctx(&ctx) < 0) {
+ printf("Server process failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (oap_cli_complete_ctx(&ctx) < 0) {
+ printf("Client complete failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (memcmp(ctx.cli.key, ctx.srv.key, SYMMKEYSZ) != 0) {
+ printf("Client and server keys do not match!\n");
+ goto fail_cleanup;
+ }
+
+ oap_test_teardown(&ctx);
+
+ TEST_SUCCESS("(%s)", md_str ? md_str : "default");
+ return TEST_RC_SUCCESS;
+
+ fail_cleanup:
+ oap_test_teardown(&ctx);
+ fail:
+ TEST_FAIL("(%s)", md_str ? md_str : "default");
+ return TEST_RC_FAIL;
+}
+
+static int test_oap_roundtrip_md_all(void)
+{
+ int ret = 0;
+ int i;
+
+ /* Test with default */
+ ret |= test_oap_roundtrip_md(NID_undef);
+
+ /* Test with all supported digest NIDs */
+ for (i = 0; md_supported_nids[i] != NID_undef; i++)
+ ret |= test_oap_roundtrip_md(md_supported_nids[i]);
+
+ return ret;
+}
+
+/* Timestamp is at offset 16 (after the 16-byte ID) */
+#define OAP_TIMESTAMP_OFFSET 16
+/* Test that packets with outdated timestamps are rejected */
+static int test_oap_outdated_packet(void)
+{
+ struct oap_test_ctx ctx;
+ struct timespec old_ts;
+ uint64_t old_stamp;
+
+ test_default_cfg();
+
+ TEST_START();
+
+ if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0)
+ goto fail;
+
+ if (oap_cli_prepare_ctx(&ctx) < 0) {
+ printf("Client prepare failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (ctx.req_hdr.len < OAP_TIMESTAMP_OFFSET + sizeof(uint64_t)) {
+ printf("Request too short for test.\n");
+ goto fail_cleanup;
+ }
+
+ /* Set timestamp to 30 seconds in the past (> 20s replay timer) */
+ clock_gettime(CLOCK_REALTIME, &old_ts);
+ old_ts.tv_sec -= OAP_REPLAY_TIMER + 10;
+ old_stamp = hton64(TS_TO_UINT64(old_ts));
+ memcpy(ctx.req_hdr.data + OAP_TIMESTAMP_OFFSET, &old_stamp,
+ sizeof(old_stamp));
+
+ if (oap_srv_process_ctx(&ctx) == 0) {
+ printf("Server should reject outdated packet.\n");
+ goto fail_cleanup;
+ }
+
+ oap_test_teardown(&ctx);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_cleanup:
+ oap_test_teardown(&ctx);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+/* Test that packets from the future are rejected */
+static int test_oap_future_packet(void)
+{
+ struct oap_test_ctx ctx;
+ struct timespec future_ts;
+ uint64_t future_stamp;
+
+ test_default_cfg();
+
+ TEST_START();
+
+ if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0)
+ goto fail;
+
+ if (oap_cli_prepare_ctx(&ctx) < 0) {
+ printf("Client prepare failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (ctx.req_hdr.len < OAP_TIMESTAMP_OFFSET + sizeof(uint64_t)) {
+ printf("Request too short for test.\n");
+ goto fail_cleanup;
+ }
+
+ /* Set timestamp to 1 second in the future (> 100ms slack) */
+ clock_gettime(CLOCK_REALTIME, &future_ts);
+ future_ts.tv_sec += 1;
+ future_stamp = hton64(TS_TO_UINT64(future_ts));
+ memcpy(ctx.req_hdr.data + OAP_TIMESTAMP_OFFSET, &future_stamp,
+ sizeof(future_stamp));
+
+ if (oap_srv_process_ctx(&ctx) == 0) {
+ printf("Server should reject future packet.\n");
+ goto fail_cleanup;
+ }
+
+ oap_test_teardown(&ctx);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_cleanup:
+ oap_test_teardown(&ctx);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+/* Test that replayed packets (same ID + timestamp) are rejected */
+static int test_oap_replay_packet(void)
+{
+ struct oap_test_ctx ctx;
+ buffer_t saved_req;
+
+ test_default_cfg();
+
+ TEST_START();
+
+ if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0)
+ goto fail;
+
+ if (oap_cli_prepare_ctx(&ctx) < 0) {
+ printf("Client prepare failed.\n");
+ goto fail_cleanup;
+ }
+
+ /* Save the request for replay */
+ saved_req.len = ctx.req_hdr.len;
+ saved_req.data = malloc(saved_req.len);
+ if (saved_req.data == NULL) {
+ printf("Failed to allocate saved request.\n");
+ goto fail_cleanup;
+ }
+ memcpy(saved_req.data, ctx.req_hdr.data, saved_req.len);
+
+ /* First request should succeed */
+ if (oap_srv_process_ctx(&ctx) < 0) {
+ printf("First request should succeed.\n");
+ free(saved_req.data);
+ goto fail_cleanup;
+ }
+
+ /* Free response from first request before replay */
+ freebuf(ctx.resp_hdr);
+
+ /* Restore the saved request for replay */
+ freebuf(ctx.req_hdr);
+ ctx.req_hdr = saved_req;
+
+ /* Replayed request should fail */
+ if (oap_srv_process_ctx(&ctx) == 0) {
+ printf("Server should reject replayed packet.\n");
+ goto fail_cleanup;
+ }
+
+ oap_test_teardown(&ctx);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_cleanup:
+ oap_test_teardown(&ctx);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+/* Test that client rejects server with wrong certificate name */
+static int test_oap_server_name_mismatch(void)
+{
+ struct oap_test_ctx ctx;
+
+ test_default_cfg();
+
+ TEST_START();
+
+ if (oap_test_setup(&ctx, root_ca_crt_ec, im_ca_crt_ec) < 0)
+ goto fail;
+
+ /* Set client's expected name to something different from cert name */
+ strcpy(ctx.cli.info.name, "wrong.server.name");
+
+ if (oap_cli_prepare_ctx(&ctx) < 0) {
+ printf("Client prepare failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (oap_srv_process_ctx(&ctx) < 0) {
+ printf("Server process failed.\n");
+ goto fail_cleanup;
+ }
+
+ /* Client should reject due to name mismatch */
+ if (oap_cli_complete_ctx(&ctx) == 0) {
+ printf("Client should reject server with wrong cert name.\n");
+ goto fail_cleanup;
+ }
+
+ oap_test_teardown(&ctx);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_cleanup:
+ oap_test_teardown(&ctx);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+int oap_test(int argc,
+ char **argv)
+{
+ int ret = 0;
+
+ (void) argc;
+ (void) argv;
+
+ ret |= test_oap_auth_init_fini();
+
+#ifdef HAVE_OPENSSL
+ ret |= test_oap_roundtrip_auth_only();
+ ret |= test_oap_roundtrip_kex_only();
+ ret |= test_oap_piggyback_data();
+
+ ret |= test_oap_roundtrip_all();
+ ret |= test_oap_roundtrip_md_all();
+
+ ret |= test_oap_corrupted_request();
+ ret |= test_oap_corrupted_response();
+ ret |= test_oap_truncated_request();
+ ret |= test_oap_inflated_length_field();
+ ret |= test_oap_deflated_length_field();
+ ret |= test_oap_nid_without_kex();
+ ret |= test_oap_unsupported_nid();
+
+ ret |= test_oap_cipher_mismatch();
+ ret |= test_oap_srv_enc_cli_none();
+ ret |= test_oap_cli_enc_srv_none();
+ ret |= test_oap_cli_rejects_downgrade();
+ ret |= test_oap_srv_rejects_weak_kex();
+
+ ret |= test_oap_outdated_packet();
+ ret |= test_oap_future_packet();
+ ret |= test_oap_replay_packet();
+ ret |= test_oap_server_name_mismatch();
+#else
+ (void) test_oap_roundtrip_auth_only;
+ (void) test_oap_roundtrip_kex_only;
+ (void) test_oap_piggyback_data;
+ (void) test_oap_roundtrip;
+ (void) test_oap_roundtrip_all;
+ (void) test_oap_roundtrip_md;
+ (void) test_oap_roundtrip_md_all;
+ (void) test_oap_corrupted_request;
+ (void) test_oap_corrupted_response;
+ (void) test_oap_truncated_request;
+ (void) test_oap_inflated_length_field;
+ (void) test_oap_deflated_length_field;
+ (void) test_oap_nid_without_kex;
+ (void) test_oap_unsupported_nid;
+ (void) test_oap_cipher_mismatch;
+ (void) test_oap_srv_enc_cli_none;
+ (void) test_oap_cli_enc_srv_none;
+ (void) test_oap_cli_rejects_downgrade;
+ (void) test_oap_srv_rejects_weak_kex;
+ (void) test_oap_outdated_packet;
+ (void) test_oap_future_packet;
+ (void) test_oap_replay_packet;
+ (void) test_oap_server_name_mismatch;
+
+ ret = TEST_RC_SKIP;
+#endif
+ return ret;
+}
diff --git a/src/irmd/oap/tests/oap_test_ml_dsa.c b/src/irmd/oap/tests/oap_test_ml_dsa.c
new file mode 100644
index 00000000..f9e6bdb2
--- /dev/null
+++ b/src/irmd/oap/tests/oap_test_ml_dsa.c
@@ -0,0 +1,447 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * Unit tests of OAP ML-KEM/ML-DSA key exchange
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#if defined(__linux__) || defined(__CYGWIN__)
+#define _DEFAULT_SOURCE
+#else
+#define _POSIX_C_SOURCE 200809L
+#endif
+
+#include "config.h"
+
+#include <ouroboros/crypt.h>
+#include <ouroboros/flow.h>
+#include <ouroboros/name.h>
+#include <ouroboros/random.h>
+#include <test/test.h>
+
+#include <test/certs/ml_dsa.h>
+
+#include "oap.h"
+#include "common.h"
+
+#include <stdbool.h>
+#include <string.h>
+
+#ifdef HAVE_OPENSSL
+#include <openssl/evp.h>
+#endif
+
+#define CLI_AUTH 1
+#define NO_CLI_AUTH 0
+#define CLI_ENCAP KEM_MODE_CLIENT_ENCAP
+#define SRV_ENCAP KEM_MODE_SERVER_ENCAP
+
+extern const uint16_t kex_supported_nids[];
+extern const uint16_t md_supported_nids[];
+
+static int get_random_kdf(void)
+{
+ static int idx = 0;
+ int count;
+
+ if (md_supported_nids[0] == NID_undef)
+ return NID_undef;
+
+ for (count = 0; md_supported_nids[count] != NID_undef; count++)
+ ;
+
+ return md_supported_nids[(idx++) % count];
+}
+
+struct test_cfg test_cfg;
+
+/* KEM keypair storage for tests (server-side keypair for KEM modes) */
+static void * test_kem_pkp = NULL; /* Private key pair */
+static uint8_t test_kem_pk[4096]; /* Public key buffer */
+static size_t test_kem_pk_len = 0;
+
+/* Mock load - called by load_*_credentials in common.c */
+int mock_load_credentials(void ** pkp,
+ void ** crt)
+{
+ *pkp = NULL;
+ *crt = NULL;
+
+ if (crypt_load_privkey_str(server_pkp_ml, pkp) < 0)
+ return -1;
+
+ if (crypt_load_crt_str(signed_server_crt_ml, crt) < 0) {
+ crypt_free_key(*pkp);
+ *pkp = NULL;
+ return -1;
+ }
+
+ return 0;
+}
+
+int load_server_kem_keypair(const char * name,
+ bool raw_fmt,
+ void ** pkp)
+{
+#ifdef HAVE_OPENSSL
+ struct sec_config local_cfg;
+ ssize_t pk_len;
+
+ (void) name;
+ (void) raw_fmt;
+
+ /*
+ * Uses reference counting. The caller will call
+ * EVP_PKEY_free which decrements the count.
+ */
+ if (test_kem_pkp != NULL) {
+ if (EVP_PKEY_up_ref((EVP_PKEY *)test_kem_pkp) != 1)
+ return -1;
+
+ *pkp = test_kem_pkp;
+ return 0;
+ }
+
+ /*
+ * Generate a new KEM keypair from test_cfg.srv.kex.
+ */
+ memset(&local_cfg, 0, sizeof(local_cfg));
+ if (test_cfg.srv.kex == NID_undef)
+ goto fail;
+
+ SET_KEX_ALGO_NID(&local_cfg, test_cfg.srv.kex);
+
+ pk_len = kex_pkp_create(&local_cfg, &test_kem_pkp, test_kem_pk);
+ if (pk_len < 0)
+ goto fail;
+
+ test_kem_pk_len = (size_t) pk_len;
+
+ if (EVP_PKEY_up_ref((EVP_PKEY *)test_kem_pkp) != 1)
+ goto fail_ref;
+
+ *pkp = test_kem_pkp;
+
+ return 0;
+ fail_ref:
+ kex_pkp_destroy(test_kem_pkp);
+ test_kem_pkp = NULL;
+ test_kem_pk_len = 0;
+ fail:
+ return -1;
+
+#else
+ (void) name;
+ (void) raw_fmt;
+ (void) pkp;
+ return -1;
+#endif
+}
+
+int load_server_kem_pk(const char * name,
+ struct sec_config * cfg,
+ buffer_t * pk)
+{
+ ssize_t len;
+
+ (void) name;
+
+ if (test_kem_pk_len > 0) {
+ pk->data = malloc(test_kem_pk_len);
+ if (pk->data == NULL)
+ return -1;
+ memcpy(pk->data, test_kem_pk, test_kem_pk_len);
+ pk->len = test_kem_pk_len;
+ return 0;
+ }
+
+ /* Generate keypair on demand if not already done */
+ len = kex_pkp_create(cfg, &test_kem_pkp, test_kem_pk);
+ if (len < 0)
+ return -1;
+
+ test_kem_pk_len = (size_t) len;
+ pk->data = malloc(test_kem_pk_len);
+ if (pk->data == NULL)
+ return -1;
+ memcpy(pk->data, test_kem_pk, test_kem_pk_len);
+ pk->len = test_kem_pk_len;
+
+ return 0;
+}
+
+static void reset_kem_state(void)
+{
+ if (test_kem_pkp != NULL) {
+ kex_pkp_destroy(test_kem_pkp);
+ test_kem_pkp = NULL;
+ }
+ test_kem_pk_len = 0;
+}
+
+static void test_cfg_init(int kex,
+ int cipher,
+ int kdf,
+ int kem_mode,
+ bool cli_auth)
+{
+ memset(&test_cfg, 0, sizeof(test_cfg));
+
+ /* Server config */
+ test_cfg.srv.kex = kex;
+ test_cfg.srv.cipher = cipher;
+ test_cfg.srv.kdf = kdf;
+ test_cfg.srv.kem_mode = kem_mode;
+ test_cfg.srv.auth = true;
+
+ /* Client config */
+ test_cfg.cli.kex = kex;
+ test_cfg.cli.cipher = cipher;
+ test_cfg.cli.kdf = kdf;
+ test_cfg.cli.kem_mode = kem_mode;
+ test_cfg.cli.auth = cli_auth;
+}
+
+static int oap_test_setup_kem(struct oap_test_ctx * ctx,
+ const char * root_ca,
+ const char * im_ca)
+{
+ reset_kem_state();
+ return oap_test_setup(ctx, root_ca, im_ca);
+}
+
+static void oap_test_teardown_kem(struct oap_test_ctx * ctx)
+{
+ oap_test_teardown(ctx);
+}
+
+static int test_oap_roundtrip_auth_only(void)
+{
+ test_cfg_init(NID_undef, NID_undef, NID_undef, 0, false);
+
+ return roundtrip_auth_only(root_ca_crt_ml, im_ca_crt_ml);
+}
+
+static int test_oap_corrupted_request(void)
+{
+ test_cfg_init(NID_MLKEM768, NID_aes_256_gcm, get_random_kdf(),
+ SRV_ENCAP, CLI_AUTH);
+
+ return corrupted_request(root_ca_crt_ml, im_ca_crt_ml);
+}
+
+static int test_oap_corrupted_response(void)
+{
+ test_cfg_init(NID_MLKEM768, NID_aes_256_gcm, get_random_kdf(),
+ SRV_ENCAP, NO_CLI_AUTH);
+
+ return corrupted_response(root_ca_crt_ml, im_ca_crt_ml);
+}
+
+static int test_oap_truncated_request(void)
+{
+ test_cfg_init(NID_MLKEM768, NID_aes_256_gcm, get_random_kdf(),
+ SRV_ENCAP, NO_CLI_AUTH);
+
+ return truncated_request(root_ca_crt_ml, im_ca_crt_ml);
+}
+
+static int test_oap_roundtrip_kem(int kex,
+ int kem_mode)
+{
+ struct oap_test_ctx ctx;
+ const char * kex_str = kex_nid_to_str(kex);
+ const char * mode_str = kem_mode == CLI_ENCAP ? "cli" : "srv";
+
+ test_cfg_init(kex, NID_aes_256_gcm, get_random_kdf(),
+ kem_mode, NO_CLI_AUTH);
+
+ TEST_START("(%s, %s encaps)", kex_str, mode_str);
+
+ if (oap_test_setup_kem(&ctx, root_ca_crt_ml, im_ca_crt_ml) < 0)
+ goto fail;
+
+ if (oap_cli_prepare_ctx(&ctx) < 0) {
+ printf("Client prepare failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (oap_srv_process_ctx(&ctx) < 0) {
+ printf("Server process failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (oap_cli_complete_ctx(&ctx) < 0) {
+ printf("Client complete failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (memcmp(ctx.cli.key, ctx.srv.key, SYMMKEYSZ) != 0) {
+ printf("Client and server keys do not match!\n");
+ goto fail_cleanup;
+ }
+
+ if (ctx.cli.nid == NID_undef ||
+ ctx.srv.nid == NID_undef) {
+ printf("Cipher not set in flow.\n");
+ goto fail_cleanup;
+ }
+
+ oap_test_teardown_kem(&ctx);
+
+ TEST_SUCCESS("(%s, %s encaps)", kex_str, mode_str);
+ return TEST_RC_SUCCESS;
+
+ fail_cleanup:
+ oap_test_teardown_kem(&ctx);
+ fail:
+ TEST_FAIL("(%s, %s encaps)", kex_str, mode_str);
+ return TEST_RC_FAIL;
+}
+
+static int test_oap_roundtrip_kem_all(void)
+{
+ int ret = 0;
+ int i;
+
+ for (i = 0; kex_supported_nids[i] != NID_undef; i++) {
+ const char * algo = kex_nid_to_str(kex_supported_nids[i]);
+
+ if (!IS_KEM_ALGORITHM(algo))
+ continue;
+
+ ret |= test_oap_roundtrip_kem(kex_supported_nids[i], SRV_ENCAP);
+ ret |= test_oap_roundtrip_kem(kex_supported_nids[i], CLI_ENCAP);
+ }
+
+ return ret;
+}
+
+static int test_oap_kem_srv_uncfg(int kex)
+{
+ struct oap_test_ctx ctx;
+ const char * kex_str = kex_nid_to_str(kex);
+
+ memset(&test_cfg, 0, sizeof(test_cfg));
+
+ /* Server: auth only, no KEX configured */
+ test_cfg.srv.auth = true;
+
+ /* Client: requests KEM with server-side encapsulation */
+ test_cfg.cli.kex = kex;
+ test_cfg.cli.cipher = NID_aes_256_gcm;
+ test_cfg.cli.kdf = get_random_kdf();
+ test_cfg.cli.kem_mode = SRV_ENCAP;
+ test_cfg.cli.auth = false;
+
+ TEST_START("(%s)", kex_str);
+
+ if (oap_test_setup_kem(&ctx, root_ca_crt_ml,
+ im_ca_crt_ml) < 0)
+ goto fail;
+
+ if (oap_cli_prepare_ctx(&ctx) < 0) {
+ printf("Client prepare failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (oap_srv_process_ctx(&ctx) < 0) {
+ printf("Server process failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (oap_cli_complete_ctx(&ctx) < 0) {
+ printf("Client complete failed.\n");
+ goto fail_cleanup;
+ }
+
+ if (memcmp(ctx.cli.key, ctx.srv.key, SYMMKEYSZ) != 0) {
+ printf("Client and server keys do not match!\n");
+ goto fail_cleanup;
+ }
+
+ if (ctx.cli.nid == NID_undef ||
+ ctx.srv.nid == NID_undef) {
+ printf("Cipher not set in flow.\n");
+ goto fail_cleanup;
+ }
+
+ oap_test_teardown_kem(&ctx);
+
+ TEST_SUCCESS("(%s)", kex_str);
+ return TEST_RC_SUCCESS;
+
+ fail_cleanup:
+ oap_test_teardown_kem(&ctx);
+ fail:
+ TEST_FAIL("(%s)", kex_str);
+ return TEST_RC_FAIL;
+}
+
+static int test_oap_kem_srv_uncfg_all(void)
+{
+ int ret = 0;
+ int i;
+
+ for (i = 0; kex_supported_nids[i] != NID_undef; i++) {
+ const char * algo;
+
+ algo = kex_nid_to_str(kex_supported_nids[i]);
+
+ if (!IS_KEM_ALGORITHM(algo))
+ continue;
+
+ ret |= test_oap_kem_srv_uncfg(kex_supported_nids[i]);
+ }
+
+ return ret;
+}
+
+int oap_test_ml_dsa(int argc,
+ char **argv)
+{
+ int ret = 0;
+
+ (void) argc;
+ (void) argv;
+
+#ifdef HAVE_OPENSSL_ML_KEM
+ ret |= test_oap_roundtrip_auth_only();
+
+ ret |= test_oap_roundtrip_kem_all();
+
+ ret |= test_oap_kem_srv_uncfg_all();
+
+ ret |= test_oap_corrupted_request();
+ ret |= test_oap_corrupted_response();
+ ret |= test_oap_truncated_request();
+#else
+ (void) test_oap_roundtrip_auth_only;
+ (void) test_oap_roundtrip_kem;
+ (void) test_oap_roundtrip_kem_all;
+ (void) test_oap_kem_srv_uncfg;
+ (void) test_oap_kem_srv_uncfg_all;
+ (void) test_oap_corrupted_request;
+ (void) test_oap_corrupted_response;
+ (void) test_oap_truncated_request;
+
+ ret = TEST_RC_SKIP;
+#endif
+
+ return ret;
+}
diff --git a/src/irmd/reg/CMakeLists.txt b/src/irmd/reg/CMakeLists.txt
deleted file mode 100644
index d3844908..00000000
--- a/src/irmd/reg/CMakeLists.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-include_directories(${CMAKE_CURRENT_SOURCE_DIR})
-include_directories(${CMAKE_CURRENT_BINARY_DIR})
-
-include_directories(${CMAKE_SOURCE_DIR}/include)
-include_directories(${CMAKE_BINARY_DIR}/include)
-
-if(BUILD_TESTS)
- add_subdirectory(tests)
-endif ()
diff --git a/src/irmd/reg/flow.c b/src/irmd/reg/flow.c
index 4d091b23..93c3e128 100644
--- a/src/irmd/reg/flow.c
+++ b/src/irmd/reg/flow.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* The IPC Resource Manager - Registry - Flows
*
@@ -66,11 +66,11 @@ struct reg_flow * reg_flow_create(const struct flow_info * info)
static void destroy_rbuffs(struct reg_flow * flow)
{
if (flow->n_rb != NULL)
- shm_rbuff_destroy(flow->n_rb);
+ ssm_rbuff_destroy(flow->n_rb);
flow->n_rb = NULL;
if (flow->n_1_rb != NULL)
- shm_rbuff_destroy(flow->n_1_rb);
+ ssm_rbuff_destroy(flow->n_1_rb);
flow->n_1_rb = NULL;
}
@@ -80,7 +80,7 @@ void reg_flow_destroy(struct reg_flow * flow)
switch(flow->info.state) {
case FLOW_ACCEPT_PENDING:
- clrbuf(flow->data);
+ clrbuf(flow->req_data);
/* FALLTHRU */
default:
destroy_rbuffs(flow);
@@ -89,8 +89,10 @@ void reg_flow_destroy(struct reg_flow * flow)
assert(flow->n_rb == NULL);
assert(flow->n_1_rb == NULL);
- assert(flow->data.data == NULL);
- assert(flow->data.len == 0);
+ assert(flow->req_data.data == NULL);
+ assert(flow->req_data.len == 0);
+ assert(flow->rsp_data.data == NULL);
+ assert(flow->rsp_data.len == 0);
assert(list_is_empty(&flow->next));
@@ -103,22 +105,28 @@ static int create_rbuffs(struct reg_flow * flow,
assert(flow != NULL);
assert(info != NULL);
- flow->n_rb = shm_rbuff_create(info->n_pid, info->id);
+ flow->n_rb = ssm_rbuff_create(info->n_pid, info->id);
if (flow->n_rb == NULL)
goto fail_n_rb;
+ if (ssm_rbuff_mlock(flow->n_rb) < 0)
+ log_warn("Failed to mlock n_rb for flow %d.", info->id);
+
assert(flow->info.n_1_pid == 0);
assert(flow->n_1_rb == NULL);
flow->info.n_1_pid = info->n_1_pid;
- flow->n_1_rb = shm_rbuff_create(info->n_1_pid, info->id);
+ flow->n_1_rb = ssm_rbuff_create(info->n_1_pid, info->id);
if (flow->n_1_rb == NULL)
goto fail_n_1_rb;
+ if (ssm_rbuff_mlock(flow->n_1_rb) < 0)
+ log_warn("Failed to mlock n_1_rb for flow %d.", info->id);
+
return 0;
fail_n_1_rb:
- shm_rbuff_destroy(flow->n_rb);
+ ssm_rbuff_destroy(flow->n_rb);
fail_n_rb:
return -ENOMEM;
}
@@ -172,6 +180,7 @@ int reg_flow_update(struct reg_flow * flow,
}
flow->info.state = info->state;
+ flow->info.uid = info->uid;
*info = flow->info;
@@ -179,30 +188,3 @@ int reg_flow_update(struct reg_flow * flow,
fail:
return -ENOMEM;
}
-
-void reg_flow_set_data(struct reg_flow * flow,
- const buffer_t * buf)
-{
- assert(flow != NULL);
- assert(buf != NULL);
- assert(flow->data.data == NULL);
- assert(flow->data.len == 0);
-
- flow->data = *buf;
-}
-
-void reg_flow_get_data(struct reg_flow * flow,
- buffer_t * buf)
-{
- assert(flow != NULL);
- assert(buf != NULL);
-
- *buf = flow->data;
-
- clrbuf(flow->data);
-}
-
-void reg_flow_free_data(struct reg_flow * flow)
-{
- freebuf(flow->data);
-}
diff --git a/src/irmd/reg/flow.h b/src/irmd/reg/flow.h
index d1e4811c..9a4046d3 100644
--- a/src/irmd/reg/flow.h
+++ b/src/irmd/reg/flow.h
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* The IPC Resource Manager - Registry - Flows
*
@@ -28,9 +28,10 @@
#include <ouroboros/name.h>
#include <ouroboros/pthread.h>
#include <ouroboros/qos.h>
-#include <ouroboros/shm_rbuff.h>
+#include <ouroboros/ssm_rbuff.h>
#include <ouroboros/utils.h>
+#include <stdbool.h>
#include <sys/types.h>
#include <time.h>
@@ -40,13 +41,16 @@ struct reg_flow {
struct flow_info info;
int response;
- buffer_t data;
+ buffer_t req_data;
+ buffer_t rsp_data;
struct timespec t0;
char name[NAME_SIZE + 1];
- struct shm_rbuff * n_rb;
- struct shm_rbuff * n_1_rb;
+ bool direct;
+
+ struct ssm_rbuff * n_rb;
+ struct ssm_rbuff * n_1_rb;
};
struct reg_flow * reg_flow_create(const struct flow_info * info);
@@ -56,12 +60,4 @@ void reg_flow_destroy(struct reg_flow * flow);
int reg_flow_update(struct reg_flow * flow,
struct flow_info * info);
-void reg_flow_set_data(struct reg_flow * flow,
- const buffer_t * buf);
-
-void reg_flow_get_data(struct reg_flow * flow,
- buffer_t * buf);
-
-void reg_flow_free_data(struct reg_flow * flow);
-
#endif /* OUROBOROS_IRMD_REG_FLOW_H */
diff --git a/src/irmd/reg/ipcp.c b/src/irmd/reg/ipcp.c
index 474527a4..b193e28f 100644
--- a/src/irmd/reg/ipcp.c
+++ b/src/irmd/reg/ipcp.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* The IPC Resource Manager - Registry - IPCPs
*
@@ -77,7 +77,6 @@ void reg_ipcp_update(struct reg_ipcp * ipcp,
const struct ipcp_info * info)
{
assert(ipcp != NULL);
- assert(info->state != IPCP_NULL);
ipcp->info = *info;
}
diff --git a/src/irmd/reg/ipcp.h b/src/irmd/reg/ipcp.h
index 375973a7..fe9d3bf1 100644
--- a/src/irmd/reg/ipcp.h
+++ b/src/irmd/reg/ipcp.h
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* The IPC Resource Manager - Registry - IPCPs
*
diff --git a/src/irmd/reg/name.c b/src/irmd/reg/name.c
index 4e609711..61a328ec 100644
--- a/src/irmd/reg/name.c
+++ b/src/irmd/reg/name.c
@@ -1,6 +1,6 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* The IPC Resource Manager - Registry - Names
*
@@ -69,9 +69,9 @@ struct reg_name * reg_name_create(const struct name_info * info)
memset(name, 0, sizeof(*name));
list_head_init(&name->next);
- list_head_init(&name->progs.list);
- list_head_init(&name->procs.list);
- list_head_init(&name->active.list);
+ llist_init(&name->progs);
+ llist_init(&name->procs);
+ llist_init(&name->active);
name->info = *info;
@@ -87,13 +87,9 @@ void reg_name_destroy(struct reg_name * name)
assert(list_is_empty(&name->next));
- assert(name->progs.len == 0);
- assert(name->procs.len == 0);
- assert(name->active.len == 0);
-
- assert(list_is_empty(&name->progs.list));
- assert(list_is_empty(&name->procs.list));
- assert(list_is_empty(&name->active.list));
+ assert(llist_is_empty(&name->progs));
+ assert(llist_is_empty(&name->procs));
+ assert(llist_is_empty(&name->active));
free(name);
}
@@ -106,7 +102,7 @@ static struct proc_entry * __reg_name_get_active(const struct reg_name * name,
assert(name != NULL);
assert(pid > 0);
- list_for_each(p, &name->active.list) {
+ llist_for_each(p, &name->active) {
struct proc_entry * entry;
entry = list_entry(p, struct proc_entry, next);
if (entry->pid == pid)
@@ -122,13 +118,12 @@ static void __reg_name_del_all_active(struct reg_name * name,
struct list_head * p;
struct list_head * h;
- list_for_each_safe(p, h, &name->active.list) {
+ llist_for_each_safe(p, h, &name->active) {
struct proc_entry * entry;
entry = list_entry(p, struct proc_entry, next);
if (entry->pid == pid) {
- list_del(&entry->next);
+ llist_del(&entry->next, &name->active);
free(entry);
- --name->active.len;
}
}
}
@@ -141,7 +136,7 @@ static struct proc_entry * __reg_name_get_proc(const struct reg_name * name,
assert(name != NULL);
assert(pid > 0);
- list_for_each(p, &name->procs.list) {
+ llist_for_each(p, &name->procs) {
struct proc_entry * entry;
entry = list_entry(p, struct proc_entry, next);
if (entry->pid == pid)
@@ -159,7 +154,7 @@ static struct prog_entry * __reg_name_get_prog(const struct reg_name * name,
assert(name != NULL);
assert(prog != NULL);
- list_for_each(p, &name->progs.list) {
+ llist_for_each(p, &name->progs) {
struct prog_entry * entry;
entry = list_entry(p, struct prog_entry, next);
if (strcmp(entry->exec[0], prog) == 0)
@@ -194,17 +189,15 @@ int reg_name_add_active(struct reg_name * name,
switch (name->info.pol_lb) {
case LB_RR: /* Round robin policy. */
- list_add_tail(&entry->next, &name->active.list);
+ llist_add_tail(&entry->next, &name->active);
break;
case LB_SPILL: /* Keep accepting flows on the current process */
- list_add(&entry->next, &name->active.list);
+ llist_add(&entry->next, &name->active);
break;
default:
goto fail_unreachable;
}
- ++name->active.len;
-
return 0;
fail_unreachable:
@@ -223,9 +216,7 @@ void reg_name_del_active(struct reg_name * name,
if (entry == NULL)
return;
- list_del(&entry->next);
-
- --name->active.len;
+ llist_del(&entry->next, &name->active);
free(entry);
}
@@ -236,10 +227,10 @@ pid_t reg_name_get_active(struct reg_name * name)
assert(name != NULL);
- if (list_is_empty(&name->active.list))
+ if (llist_is_empty(&name->active))
return -1;
- e = list_first_entry(&name->active.list, struct proc_entry, next);
+ e = llist_first_entry(&name->active, struct proc_entry, next);
return e->pid;
}
@@ -262,9 +253,7 @@ int reg_name_add_proc(struct reg_name * name,
entry->pid = pid;
- list_add(&entry->next, &name->procs.list);
-
- ++name->procs.len;
+ llist_add(&entry->next, &name->procs);
return 0;
@@ -286,12 +275,10 @@ void reg_name_del_proc(struct reg_name * name,
__reg_name_del_all_active(name, pid);
- list_del(&entry->next);
+ llist_del(&entry->next, &name->procs);
free(entry);
- --name->procs.len;
-
assert(__reg_name_get_proc(name, pid) == NULL);
}
@@ -324,12 +311,10 @@ int reg_name_add_prog(struct reg_name * name,
goto fail_exec;
}
- list_add(&entry->next, &name->progs.list);
+ llist_add(&entry->next, &name->progs);
log_dbg("Add prog %s to name %s.", exec[0], name->info.name);
- ++name->progs.len;
-
return 0;
fail_exec:
@@ -350,12 +335,10 @@ void reg_name_del_prog(struct reg_name * name,
if (entry == NULL)
return;
- list_del(&entry->next);
+ llist_del(&entry->next, &name->progs);
__free_prog_entry(entry);
- --name->progs.len;
-
assert(__reg_name_get_prog(name, prog) == NULL);
}
@@ -372,10 +355,10 @@ char ** reg_name_get_exec(const struct reg_name * name)
{
struct prog_entry * e;
- if (list_is_empty(&name->progs.list))
+ if (llist_is_empty(&name->progs))
return NULL;
- e = list_first_entry(&name->progs.list, struct prog_entry, next);
+ e = llist_first_entry(&name->progs, struct prog_entry, next);
return e->exec;
}
diff --git a/src/irmd/reg/name.h b/src/irmd/reg/name.h
index 30a64e1c..59d6d9bd 100644
--- a/src/irmd/reg/name.h
+++ b/src/irmd/reg/name.h
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* The IPC Resource Manager - Registry - Names
*
@@ -38,20 +38,11 @@ struct reg_name {
void * crt;
} cache;
- struct {
- struct list_head list;
- size_t len;
- } progs; /* autostart programs for this name */
+ struct llist progs; /* autostart programs for this name */
- struct {
- struct list_head list;
- size_t len;
- } procs; /* processes bound to this name */
+ struct llist procs; /* processes bound to this name */
- struct {
- struct list_head list;
- size_t len;
- } active; /* processes actively calling accept */
+ struct llist active; /* processes actively calling accept */
};
struct reg_name * reg_name_create(const struct name_info * info);
diff --git a/src/irmd/reg/pool.c b/src/irmd/reg/pool.c
new file mode 100644
index 00000000..4b1486bb
--- /dev/null
+++ b/src/irmd/reg/pool.c
@@ -0,0 +1,97 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * The IPC Resource Manager - Registry - Per-User Pools
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#define _POSIX_C_SOURCE 200809L
+
+#define OUROBOROS_PREFIX "reg/pool"
+
+#include <ouroboros/logs.h>
+#include <ouroboros/ssm_pool.h>
+
+#include "pool.h"
+
+#include <assert.h>
+#include <stdlib.h>
+
+struct reg_pool * reg_pool_create(uid_t uid,
+ gid_t gid)
+{
+ struct reg_pool * pool;
+
+ pool = malloc(sizeof(*pool));
+ if (pool == NULL) {
+ log_err("Failed to malloc pool.");
+ goto fail_malloc;
+ }
+
+ pool->ssm = ssm_pool_create(uid, gid);
+ if (pool->ssm == NULL) {
+ log_err("Failed to create PUP for uid %d.", uid);
+ goto fail_ssm;
+ }
+
+ list_head_init(&pool->next);
+ pool->uid = uid;
+ pool->gid = gid;
+ pool->refcount = 1;
+
+ log_dbg("Created PUP for uid %d gid %d.", uid, gid);
+
+ return pool;
+
+ fail_ssm:
+ free(pool);
+ fail_malloc:
+ return NULL;
+}
+
+void reg_pool_destroy(struct reg_pool * pool)
+{
+ assert(pool != NULL);
+ assert(pool->refcount == 0);
+
+ log_dbg("Destroying PUP for uid %d.", pool->uid);
+
+ ssm_pool_destroy(pool->ssm);
+
+ assert(list_is_empty(&pool->next));
+
+ free(pool);
+}
+
+void reg_pool_ref(struct reg_pool * pool)
+{
+ assert(pool != NULL);
+ assert(pool->refcount > 0);
+
+ pool->refcount++;
+}
+
+int reg_pool_unref(struct reg_pool * pool)
+{
+ assert(pool != NULL);
+ assert(pool->refcount > 0);
+
+ pool->refcount--;
+
+ return pool->refcount == 0 ? 0 : 1;
+}
diff --git a/src/irmd/reg/pool.h b/src/irmd/reg/pool.h
new file mode 100644
index 00000000..576f491c
--- /dev/null
+++ b/src/irmd/reg/pool.h
@@ -0,0 +1,48 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * The IPC Resource Manager - Registry - Per-User Pools
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#ifndef OUROBOROS_IRMD_REG_POOL_H
+#define OUROBOROS_IRMD_REG_POOL_H
+
+#include <ouroboros/list.h>
+#include <ouroboros/ssm_pool.h>
+
+#include <sys/types.h>
+
+struct reg_pool {
+ struct list_head next;
+ uid_t uid;
+ gid_t gid;
+ size_t refcount;
+ struct ssm_pool * ssm;
+};
+
+struct reg_pool * reg_pool_create(uid_t uid,
+ gid_t gid);
+
+void reg_pool_destroy(struct reg_pool * pool);
+
+void reg_pool_ref(struct reg_pool * pool);
+
+int reg_pool_unref(struct reg_pool * pool);
+
+#endif /* OUROBOROS_IRMD_REG_POOL_H */
diff --git a/src/irmd/reg/proc.c b/src/irmd/reg/proc.c
index 9bbdf0eb..8a7e24c9 100644
--- a/src/irmd/reg/proc.c
+++ b/src/irmd/reg/proc.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* The IPC Resource Manager - Registry - Processes
*
@@ -25,6 +25,7 @@
#define OUROBOROS_PREFIX "reg/proc"
#include <ouroboros/logs.h>
+#include <ouroboros/utils.h>
#include "proc.h"
@@ -54,12 +55,11 @@ static void __reg_proc_clear_names(struct reg_proc * proc)
assert(proc != NULL);
- list_for_each_safe(p, h, &proc->names) {
+ llist_for_each_safe(p, h, &proc->names) {
struct name_entry * entry;
entry = list_entry(p, struct name_entry, next);
- list_del(&entry->next);
+ llist_del(&entry->next, &proc->names);
__free_name_entry(entry);
- proc->n_names--;
}
}
@@ -75,17 +75,18 @@ struct reg_proc * reg_proc_create(const struct proc_info * info)
goto fail_malloc;
}
- proc->set = shm_flow_set_create(info->pid);
+ memset(proc, 0, sizeof(*proc));
+
+ proc->set = ssm_flow_set_create(info->pid);
if (proc->set == NULL) {
log_err("Failed to create flow set for %d.", info->pid);
goto fail_set;
}
list_head_init(&proc->next);
- list_head_init(&proc->names);
+ llist_init(&proc->names);
proc->info = *info;
- proc->n_names = 0;
return proc;
@@ -99,15 +100,13 @@ void reg_proc_destroy(struct reg_proc * proc)
{
assert(proc != NULL);
- shm_flow_set_destroy(proc->set);
+ ssm_flow_set_destroy(proc->set);
__reg_proc_clear_names(proc);
assert(list_is_empty(&proc->next));
- assert(proc->n_names == 0);
-
- assert(list_is_empty(&proc->names));
+ assert(llist_is_empty(&proc->names));
free(proc);
}
@@ -117,7 +116,7 @@ static struct name_entry * __reg_proc_get_name(const struct reg_proc * proc,
{
struct list_head * p;
- list_for_each(p, &proc->names) {
+ llist_for_each(p, &proc->names) {
struct name_entry * entry;
entry = list_entry(p, struct name_entry, next);
if (strcmp(entry->name, name) == 0)
@@ -146,9 +145,7 @@ int reg_proc_add_name(struct reg_proc * proc,
goto fail_name;
}
- list_add(&entry->next, &proc->names);
-
- proc->n_names++;
+ llist_add(&entry->next, &proc->names);
return 0;
@@ -167,12 +164,10 @@ void reg_proc_del_name(struct reg_proc * proc,
if(entry == NULL)
return;
- list_del(&entry->next);
+ llist_del(&entry->next, &proc->names);
__free_name_entry(entry);
- proc->n_names--;
-
assert(__reg_proc_get_name(proc, name) == NULL);
}
@@ -181,3 +176,10 @@ bool reg_proc_has_name(const struct reg_proc * proc,
{
return __reg_proc_get_name(proc, name) != NULL;
}
+
+bool reg_proc_is_privileged(const struct reg_proc * proc)
+{
+ assert(proc != NULL);
+
+ return is_ouroboros_member_uid(proc->info.uid);
+}
diff --git a/src/irmd/reg/proc.h b/src/irmd/reg/proc.h
index 499ecc72..18cc2803 100644
--- a/src/irmd/reg/proc.h
+++ b/src/irmd/reg/proc.h
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* The IPC Resource Manager - Registry - Processes
*
@@ -25,17 +25,16 @@
#include <ouroboros/list.h>
#include <ouroboros/proc.h>
-#include <ouroboros/shm_flow_set.h>
+#include <ouroboros/ssm_flow_set.h>
struct reg_proc {
struct list_head next;
struct proc_info info;
- struct list_head names; /* process accepts flows for names */
- size_t n_names; /* number of names */
+ struct llist names; /* process accepts flows for names */
- struct shm_flow_set * set;
+ struct ssm_flow_set * set;
};
struct reg_proc * reg_proc_create(const struct proc_info * info);
@@ -53,4 +52,6 @@ void reg_proc_del_name(struct reg_proc * proc,
bool reg_proc_has_name(const struct reg_proc * proc,
const char * name);
+bool reg_proc_is_privileged(const struct reg_proc * proc);
+
#endif /* OUROBOROS_IRMD_REG_PROC_H */
diff --git a/src/irmd/reg/prog.c b/src/irmd/reg/prog.c
index 9b9e7510..2d7f9f8d 100644
--- a/src/irmd/reg/prog.c
+++ b/src/irmd/reg/prog.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* The IPC Resource Manager - Registry - Programs
*
@@ -55,12 +55,11 @@ static void __reg_prog_clear_names(struct reg_prog * prog)
assert(prog != NULL);
- list_for_each_safe(p, h, &prog->names) {
+ llist_for_each_safe(p, h, &prog->names) {
struct name_entry * entry;
entry = list_entry(p, struct name_entry, next);
- list_del(&entry->next);
+ llist_del(&entry->next, &prog->names);
__free_name_entry(entry);
- prog->n_names--;
}
}
@@ -77,10 +76,9 @@ struct reg_prog * reg_prog_create(const struct prog_info * info)
}
list_head_init(&p->next);
- list_head_init(&p->names);
+ llist_init(&p->names);
p->info = *info;
- p->n_names = 0;
return p;
@@ -96,9 +94,7 @@ void reg_prog_destroy(struct reg_prog * prog)
assert(list_is_empty(&prog->next));
- assert(prog->n_names == 0);
-
- assert(list_is_empty(&prog->names));
+ assert(llist_is_empty(&prog->names));
free(prog);
}
@@ -108,7 +104,7 @@ static struct name_entry * __reg_prog_get_name(const struct reg_prog * prog,
{
struct list_head * p;
- list_for_each(p, &prog->names) {
+ llist_for_each(p, &prog->names) {
struct name_entry * entry;
entry = list_entry(p, struct name_entry, next);
if (strcmp(entry->name, name) == 0)
@@ -137,9 +133,7 @@ int reg_prog_add_name(struct reg_prog * prog,
goto fail_name;
}
- list_add(&entry->next, &prog->names);
-
- prog->n_names++;
+ llist_add(&entry->next, &prog->names);
return 0;
@@ -158,12 +152,10 @@ void reg_prog_del_name(struct reg_prog * prog,
if (entry == NULL)
return;
- list_del(&entry->next);
+ llist_del(&entry->next, &prog->names);
__free_name_entry(entry);
- prog->n_names--;
-
assert(__reg_prog_get_name(prog, name) == NULL);
}
diff --git a/src/irmd/reg/prog.h b/src/irmd/reg/prog.h
index a98fc6a1..e52b8e15 100644
--- a/src/irmd/reg/prog.h
+++ b/src/irmd/reg/prog.h
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* The IPC Resource Manager - Registry - Programs
*
@@ -33,8 +33,7 @@ struct reg_prog {
struct prog_info info;
- struct list_head names; /* names to listen for */
- size_t n_names; /* number of names in list */
+ struct llist names; /* names to listen for */
};
struct reg_prog * reg_prog_create(const struct prog_info * info);
diff --git a/src/irmd/reg/reg.c b/src/irmd/reg/reg.c
index a24a9d1d..0025f695 100644
--- a/src/irmd/reg/reg.c
+++ b/src/irmd/reg/reg.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
The IPC Resource Manager - Registry
*
@@ -35,6 +35,7 @@ The IPC Resource Manager - Registry
#include "flow.h"
#include "ipcp.h"
#include "name.h"
+#include "pool.h"
#include "proc.h"
#include "prog.h"
@@ -46,28 +47,18 @@ The IPC Resource Manager - Registry
#define ID_OFFT 1 /* reserve some flow_ids */
struct {
- struct bmp * flow_ids; /* flow_ids for flows */
-
- struct list_head flows; /* flow information */
- size_t n_flows; /* number of flows */
-
- struct list_head ipcps; /* list of ipcps in system */
- size_t n_ipcps; /* number of ipcps */
-
- struct list_head names; /* registered names known */
- size_t n_names; /* number of names */
-
- struct list_head procs; /* processes */
- size_t n_procs; /* number of processes */
-
- struct list_head progs; /* programs known */
- size_t n_progs; /* number of programs */
-
- struct list_head spawned; /* child processes */
- size_t n_spawned; /* number of child processes */
-
- pthread_mutex_t mtx; /* registry lock */
- pthread_cond_t cond; /* condvar for reg changes */
+ struct bmp * ids; /* flow bitmap */
+
+ struct llist flows; /* list of flows */
+ struct llist ipcps; /* list of ipcps in system */
+ struct llist names; /* registered names known */
+ struct llist pools; /* per-user pools */
+ struct llist procs; /* processes known */
+ struct llist progs; /* programs known */
+ struct llist spawned; /* child processes */
+
+ pthread_mutex_t mtx; /* registry lock */
+ pthread_cond_t cond; /* condvar for reg changes */
} reg;
struct pid_entry {
@@ -81,7 +72,7 @@ static struct reg_flow * __reg_get_flow(int flow_id)
assert(flow_id >= ID_OFFT);
- list_for_each(p, &reg.flows) {
+ llist_for_each(p, &reg.flows) {
struct reg_flow * entry;
entry = list_entry(p, struct reg_flow, next);
if (entry->info.id == flow_id)
@@ -95,7 +86,7 @@ static struct reg_flow * __reg_get_accept_flow(pid_t pid)
{
struct list_head * p;
- list_for_each(p, &reg.flows) {
+ llist_for_each(p, &reg.flows) {
struct reg_flow * entry;
entry = list_entry(p, struct reg_flow, next);
if (entry->info.state != FLOW_ACCEPT_PENDING)
@@ -113,7 +104,7 @@ static struct list_head * __reg_after_flow(int flow_id)
assert(flow_id >= ID_OFFT);
- list_for_each(p, &reg.flows) {
+ llist_for_each(p, &reg.flows) {
struct reg_flow * entry;
entry = list_entry(p, struct reg_flow, next);
if (entry->info.id > flow_id)
@@ -129,7 +120,7 @@ static struct reg_ipcp * __reg_get_ipcp(pid_t pid)
assert(pid > 0);
- list_for_each(p, &reg.ipcps) {
+ llist_for_each(p, &reg.ipcps) {
struct reg_ipcp * entry;
entry = list_entry(p, struct reg_ipcp, next);
if (entry->info.pid == pid)
@@ -143,7 +134,7 @@ static struct reg_ipcp * __reg_get_ipcp_by_layer(const char * layer)
{
struct list_head * p;
- list_for_each(p, &reg.ipcps) {
+ llist_for_each(p, &reg.ipcps) {
struct reg_ipcp * entry;
entry = list_entry(p, struct reg_ipcp, next);
if (strcmp(entry->layer.name, layer) == 0)
@@ -160,7 +151,7 @@ static struct list_head * __reg_after_ipcp(const struct ipcp_info * info)
assert(info != NULL);
- list_for_each(p, &reg.ipcps) {
+ llist_for_each(p, &reg.ipcps) {
struct reg_ipcp * entry;
entry = list_entry(p, struct reg_ipcp, next);
if (entry->info.type < info->type)
@@ -182,7 +173,7 @@ static struct reg_name * __reg_get_name(const char * name)
assert(name != NULL);
- list_for_each(p, &reg.names) {
+ llist_for_each(p, &reg.names) {
struct reg_name * entry;
entry = list_entry(p, struct reg_name, next);
if (strcmp(entry->info.name, name) == 0)
@@ -225,7 +216,7 @@ static struct list_head * __reg_after_name(const char * name)
assert(name != NULL);
- list_for_each(p, &reg.names) {
+ llist_for_each(p, &reg.names) {
struct reg_name * entry;
entry = list_entry(p, struct reg_name, next);
if (strcmp(entry->info.name, name) > 0)
@@ -235,11 +226,25 @@ static struct list_head * __reg_after_name(const char * name)
return p;
}
+static struct reg_pool * __reg_get_pool(uid_t uid)
+{
+ struct list_head * p;
+
+ llist_for_each(p, &reg.pools) {
+ struct reg_pool * entry;
+ entry = list_entry(p, struct reg_pool, next);
+ if (entry->uid == uid)
+ return entry;
+ }
+
+ return NULL;
+}
+
static struct reg_proc * __reg_get_proc(pid_t pid)
{
struct list_head * p;
- list_for_each(p, &reg.procs) {
+ llist_for_each(p, &reg.procs) {
struct reg_proc * entry;
entry = list_entry(p, struct reg_proc, next);
if (entry->info.pid == pid)
@@ -253,7 +258,7 @@ static struct list_head * __reg_after_proc(pid_t pid)
{
struct list_head * p;
- list_for_each(p, &reg.procs) {
+ llist_for_each(p, &reg.procs) {
struct reg_proc * entry;
entry = list_entry(p, struct reg_proc, next);
if (entry->info.pid > pid)
@@ -267,7 +272,7 @@ static void __reg_kill_all_proc(int signal)
{
struct list_head * p;
- list_for_each(p, &reg.procs) {
+ llist_for_each(p, &reg.procs) {
struct reg_proc * entry;
entry = list_entry(p, struct reg_proc, next);
kill(entry->info.pid, signal);
@@ -278,7 +283,7 @@ static pid_t __reg_get_dead_proc(void)
{
struct list_head * p;
- list_for_each(p, &reg.procs) {
+ llist_for_each(p, &reg.procs) {
struct reg_proc * entry;
entry = list_entry(p, struct reg_proc, next);
if (kill(entry->info.pid, 0) < 0)
@@ -293,7 +298,7 @@ static void __reg_cancel_flows_for_proc(pid_t pid)
struct list_head * p;
bool changed = false;
- list_for_each(p, &reg.flows) {
+ llist_for_each(p, &reg.flows) {
struct reg_flow * entry;
entry = list_entry(p, struct reg_flow, next);
if (entry->info.n_pid != pid)
@@ -319,7 +324,7 @@ static struct pid_entry * __reg_get_spawned(pid_t pid)
{
struct list_head * p;
- list_for_each(p, &reg.spawned) {
+ llist_for_each(p, &reg.spawned) {
struct pid_entry * entry;
entry = list_entry(p, struct pid_entry, next);
if (entry->pid == pid)
@@ -333,7 +338,7 @@ static struct list_head * __reg_after_spawned(pid_t pid)
{
struct list_head * p;
- list_for_each(p, &reg.spawned) {
+ llist_for_each(p, &reg.spawned) {
struct pid_entry * entry;
entry = list_entry(p, struct pid_entry, next);
if (entry->pid > pid)
@@ -347,7 +352,7 @@ static void __reg_kill_all_spawned(int signal)
{
struct list_head * p;
- list_for_each(p, &reg.spawned) {
+ llist_for_each(p, &reg.spawned) {
struct pid_entry * entry;
entry = list_entry(p, struct pid_entry, next);
kill(entry->pid, signal);
@@ -356,17 +361,17 @@ static void __reg_kill_all_spawned(int signal)
static pid_t __reg_first_spawned(void)
{
- if (list_is_empty(&reg.spawned))
+ if (llist_is_empty(&reg.spawned))
return -1;
- return list_first_entry(&reg.spawned, struct pid_entry, next)->pid;
+ return llist_first_entry(&reg.spawned, struct pid_entry, next)->pid;
}
static struct reg_prog * __reg_get_prog(const char * name)
{
struct list_head * p;
- list_for_each(p, &reg.progs) {
+ llist_for_each(p, &reg.progs) {
struct reg_prog * entry;
entry = list_entry(p, struct reg_prog, next);
if (strcmp(entry->info.name, name) == 0)
@@ -380,7 +385,7 @@ static char ** __reg_get_exec(const char * name)
{
struct list_head * p;
- list_for_each(p, &reg.names) {
+ llist_for_each(p, &reg.names) {
struct reg_name * entry;
entry = list_entry(p, struct reg_name, next);
if (strcmp(entry->info.name, name) == 0)
@@ -394,7 +399,7 @@ static struct list_head * __reg_after_prog(const char * name)
{
struct list_head * p;
- list_for_each(p, &reg.progs) {
+ llist_for_each(p, &reg.progs) {
struct reg_prog * entry;
entry = list_entry(p, struct reg_prog, next);
if (strcmp(entry->info.name, name) > 0)
@@ -408,7 +413,7 @@ static void __reg_del_name_from_procs(const char * name)
{
struct list_head * p;
- list_for_each(p, &reg.procs) {
+ llist_for_each(p, &reg.procs) {
struct reg_proc * proc;
proc = list_entry(p, struct reg_proc, next);
reg_proc_del_name(proc, name);
@@ -419,7 +424,7 @@ static void __reg_del_name_from_progs(const char * name)
{
struct list_head * p;
- list_for_each(p, &reg.progs) {
+ llist_for_each(p, &reg.progs) {
struct reg_prog * prog;
prog = list_entry(p, struct reg_prog, next);
reg_prog_del_name(prog, name);
@@ -431,13 +436,13 @@ static void __reg_proc_update_names(struct reg_proc * proc)
struct list_head * p;
struct reg_prog * prog;
- assert(list_is_empty(&proc->names));
+ assert(llist_is_empty(&proc->names));
prog = __reg_get_prog(proc->info.prog);
if (prog == NULL)
return;
- list_for_each(p, &reg.names) {
+ llist_for_each(p, &reg.names) {
struct reg_name * name;
name = list_entry(p, struct reg_name, next);
assert(!reg_name_has_proc(name, proc->info.pid));
@@ -452,7 +457,7 @@ static void __reg_del_proc_from_names(pid_t pid)
{
struct list_head * p;
- list_for_each(p, &reg.names) {
+ llist_for_each(p, &reg.names) {
struct reg_name * name;
name = list_entry(p, struct reg_name, next);
reg_name_del_proc(name, pid);
@@ -463,7 +468,7 @@ static void __reg_del_prog_from_names(const char * prog)
{
struct list_head * p;
- list_for_each(p, &reg.names) {
+ llist_for_each(p, &reg.names) {
struct reg_name * name;
name = list_entry(p, struct reg_name, next);
reg_name_del_prog(name, prog);
@@ -478,7 +483,7 @@ static int __reg_add_active_proc(pid_t pid)
assert(pid > 0);
- list_for_each(p, &reg.names) {
+ llist_for_each(p, &reg.names) {
struct reg_name * name;
name = list_entry(p, struct reg_name, next);
if (reg_name_has_proc(name, pid)) {
@@ -500,7 +505,7 @@ static void __reg_del_active_proc(pid_t pid)
assert(pid > 0);
- list_for_each(p, &reg.names) {
+ llist_for_each(p, &reg.names) {
struct reg_name * name;
name = list_entry(p, struct reg_name, next);
reg_name_del_active(name, pid);
@@ -529,20 +534,21 @@ int reg_init(void)
goto fail_cond;
}
- reg.flow_ids = bmp_create(SYS_MAX_FLOWS -ID_OFFT, ID_OFFT);
- if (reg.flow_ids == NULL) {
+ reg.ids = bmp_create(SYS_MAX_FLOWS - ID_OFFT, ID_OFFT);
+ if (reg.ids == NULL) {
log_err("Failed to create flow_ids bitmap.");
goto fail_flow_ids;
}
pthread_condattr_destroy(&cattr);
- list_head_init(&reg.flows);
- list_head_init(&reg.ipcps);
- list_head_init(&reg.names);
- list_head_init(&reg.procs);
- list_head_init(&reg.progs);
- list_head_init(&reg.spawned);
+ llist_init(&reg.flows);
+ llist_init(&reg.ipcps);
+ llist_init(&reg.names);
+ llist_init(&reg.pools);
+ llist_init(&reg.procs);
+ llist_init(&reg.progs);
+ llist_init(&reg.spawned);
return 0;
@@ -563,54 +569,56 @@ void reg_clear(void)
pthread_mutex_lock(&reg.mtx);
- list_for_each_safe(p, h, &reg.spawned) {
+ llist_for_each_safe(p, h, &reg.spawned) {
struct pid_entry * entry;
entry = list_entry(p, struct pid_entry, next);
- list_del(&entry->next);
+ llist_del(&entry->next, &reg.spawned);
free(entry);
- reg.n_spawned--;
}
- list_for_each_safe(p, h, &reg.progs) {
+ llist_for_each_safe(p, h, &reg.progs) {
struct reg_prog * entry;
entry = list_entry(p, struct reg_prog, next);
- list_del(&entry->next);
+ llist_del(&entry->next, &reg.progs);
__reg_del_prog_from_names(entry->info.path);
reg_prog_destroy(entry);
- reg.n_progs--;
}
- list_for_each_safe(p, h, &reg.procs) {
+ llist_for_each_safe(p, h, &reg.procs) {
struct reg_proc * entry;
entry = list_entry(p, struct reg_proc, next);
- list_del(&entry->next);
+ llist_del(&entry->next, &reg.procs);
__reg_del_proc_from_names(entry->info.pid);
reg_proc_destroy(entry);
- reg.n_procs--;
}
- list_for_each_safe(p, h, &reg.flows) {
+ llist_for_each_safe(p, h, &reg.pools) {
+ struct reg_pool * entry;
+ entry = list_entry(p, struct reg_pool, next);
+ llist_del(&entry->next, &reg.pools);
+ entry->refcount = 0; /* Force destroy during cleanup */
+ reg_pool_destroy(entry);
+ }
+
+ llist_for_each_safe(p, h, &reg.flows) {
struct reg_flow * entry;
entry = list_entry(p, struct reg_flow, next);
- list_del(&entry->next);
+ llist_del(&entry->next, &reg.flows);
reg_flow_destroy(entry);
- reg.n_flows--;
}
- list_for_each_safe(p, h, &reg.names) {
+ llist_for_each_safe(p, h, &reg.names) {
struct reg_name * entry;
entry = list_entry(p, struct reg_name, next);
- list_del(&entry->next);
+ llist_del(&entry->next, &reg.names);
reg_name_destroy(entry);
- reg.n_names--;
}
- list_for_each_safe(p, h, &reg.ipcps) {
+ llist_for_each_safe(p, h, &reg.ipcps) {
struct reg_ipcp * entry;
entry = list_entry(p, struct reg_ipcp, next);
- list_del(&entry->next);
+ llist_del(&entry->next, &reg.ipcps);
reg_ipcp_destroy(entry);
- reg.n_ipcps--;
}
pthread_mutex_unlock(&reg.mtx);
@@ -618,21 +626,15 @@ void reg_clear(void)
void reg_fini(void)
{
- assert(list_is_empty(&reg.spawned));
- assert(list_is_empty(&reg.progs));
- assert(list_is_empty(&reg.procs));
- assert(list_is_empty(&reg.names));
- assert(list_is_empty(&reg.ipcps));
- assert(list_is_empty(&reg.flows));
-
- assert(reg.n_spawned == 0);
- assert(reg.n_progs == 0);
- assert(reg.n_procs == 0);
- assert(reg.n_names == 0);
- assert(reg.n_ipcps == 0);
- assert(reg.n_flows == 0);
+ assert(llist_is_empty(&reg.spawned));
+ assert(llist_is_empty(&reg.progs));
+ assert(llist_is_empty(&reg.procs));
+ assert(llist_is_empty(&reg.pools));
+ assert(llist_is_empty(&reg.names));
+ assert(llist_is_empty(&reg.ipcps));
+ assert(llist_is_empty(&reg.flows));
- bmp_destroy(reg.flow_ids);
+ bmp_destroy(reg.ids);
if (pthread_cond_destroy(&reg.cond) != 0)
log_warn("Failed to destroy condvar.");
@@ -652,8 +654,8 @@ int reg_create_flow(struct flow_info * info)
pthread_mutex_lock(&reg.mtx);
- info->id = bmp_allocate(reg.flow_ids);
- if (!bmp_is_id_valid(reg.flow_ids, info->id)) {
+ info->id = bmp_allocate(reg.ids);
+ if (!bmp_is_id_valid(reg.ids, info->id)) {
log_err("Failed to allocate flow id.");
goto fail_id;
}
@@ -664,16 +666,14 @@ int reg_create_flow(struct flow_info * info)
goto fail_flow;
}
- list_add(&f->next, __reg_after_flow(info->id));
-
- reg.n_flows++;
+ llist_add_at(&f->next, __reg_after_flow(info->id), &reg.flows);
pthread_mutex_unlock(&reg.mtx);
return 0;
fail_flow:
- bmp_release(reg.flow_ids, info->id);
+ bmp_release(reg.ids, info->id);
info->id = 0;
fail_id:
pthread_mutex_unlock(&reg.mtx);
@@ -692,11 +692,9 @@ int reg_destroy_flow(int flow_id)
goto no_flow;
}
- list_del(&f->next);
-
- reg.n_flows--;
+ llist_del(&f->next, &reg.flows);
- bmp_release(reg.flow_ids, flow_id);
+ bmp_release(reg.ids, flow_id);
pthread_mutex_unlock(&reg.mtx);
@@ -755,11 +753,10 @@ int reg_create_ipcp(const struct ipcp_info * info)
entry->pid = info->pid;
- list_add_tail(&ipcp->next, __reg_after_ipcp(info));
- list_add(&entry->next, __reg_after_spawned(info->pid));
-
- reg.n_ipcps++;
- reg.n_spawned++;
+ llist_add_tail_at(&ipcp->next, __reg_after_ipcp(info), &reg.ipcps);
+ llist_add_at(&entry->next,
+ __reg_after_spawned(info->pid),
+ &reg.spawned);
pthread_mutex_unlock(&reg.mtx);
@@ -849,16 +846,16 @@ int reg_list_ipcps(ipcp_list_msg_t *** ipcps)
pthread_mutex_lock(&reg.mtx);
- if (reg.n_ipcps == 0)
+ if (llist_is_empty(&reg.ipcps))
goto finish;
- *ipcps = malloc(reg.n_ipcps * sizeof(**ipcps));
+ *ipcps = malloc(reg.ipcps.len * sizeof(**ipcps));
if (*ipcps == NULL) {
log_err("Failed to malloc ipcps.");
goto fail_malloc;
}
- list_for_each(p, &reg.ipcps) {
+ llist_for_each(p, &reg.ipcps) {
struct reg_ipcp * entry;
entry = list_entry(p, struct reg_ipcp, next);
if (__get_ipcp_info(&(*ipcps)[i], entry) < 0)
@@ -900,9 +897,7 @@ int reg_create_name(const struct name_info * info)
goto fail_name;
}
- list_add(&n->next, __reg_after_name(info->name));
-
- reg.n_names++;
+ llist_add_at(&n->next, __reg_after_name(info->name), &reg.names);
pthread_mutex_unlock(&reg.mtx);
return 0;
@@ -931,9 +926,7 @@ int reg_destroy_name(const char * name)
__reg_del_name_from_procs(name);
__reg_del_name_from_progs(name);
- list_del(&n->next);
-
- reg.n_names--;
+ llist_del(&n->next, &reg.names);
pthread_mutex_unlock(&reg.mtx);
@@ -1004,7 +997,7 @@ int reg_get_name_for_hash(char * buf,
pthread_mutex_lock(&reg.mtx);
- list_for_each(p, &reg.names) {
+ llist_for_each(p, &reg.names) {
struct reg_name * n = list_entry(p, struct reg_name, next);
str_hash(algo, thash, n->info.name);
if (memcmp(thash, hash, len) == 0) {
@@ -1046,16 +1039,16 @@ int reg_list_names(name_info_msg_t *** names)
pthread_mutex_lock(&reg.mtx);
- if (reg.n_names == 0)
+ if (llist_is_empty(&reg.names))
goto finish;
- *names = malloc(reg.n_names * sizeof(**names));
+ *names = malloc(reg.names.len * sizeof(**names));
if (*names == NULL) {
log_err("Failed to malloc names.");
goto fail_malloc;
}
- list_for_each(p, &reg.names) {
+ llist_for_each(p, &reg.names) {
struct reg_name * entry;
entry = list_entry(p, struct reg_name, next);
(*names)[i] = name_info_s_to_msg(&entry->info);
@@ -1090,6 +1083,34 @@ int reg_list_names(name_info_msg_t *** names)
return -ENOMEM;
}
+int reg_prepare_pool(uid_t uid,
+ gid_t gid)
+{
+ struct reg_pool * pool;
+
+ if (is_ouroboros_member_uid(uid))
+ return 0;
+
+ pthread_mutex_lock(&reg.mtx);
+
+ pool = __reg_get_pool(uid);
+ if (pool == NULL) {
+ pool = reg_pool_create(uid, gid);
+ if (pool == NULL) {
+ log_err("Failed to create pool for uid %d.", uid);
+ pthread_mutex_unlock(&reg.mtx);
+ return -1;
+ }
+ llist_add(&pool->next, &reg.pools);
+ }
+
+ reg_pool_ref(pool);
+
+ pthread_mutex_unlock(&reg.mtx);
+
+ return 0;
+}
+
int reg_create_proc(const struct proc_info * info)
{
struct reg_proc * proc;
@@ -1100,20 +1121,18 @@ int reg_create_proc(const struct proc_info * info)
if (__reg_get_proc(info->pid) != NULL) {
log_err("Process %d already exists.", info->pid);
- goto fail_proc;
+ goto fail;
}
proc = reg_proc_create(info);
if (proc == NULL) {
log_err("Failed to create process %d.", info->pid);
- goto fail_proc;
+ goto fail;
}
__reg_proc_update_names(proc);
- list_add(&proc->next, __reg_after_proc(info->pid));
-
- reg.n_procs++;
+ llist_add_at(&proc->next, __reg_after_proc(info->pid), &reg.procs);
pthread_cond_broadcast(&reg.cond);
@@ -1121,7 +1140,7 @@ int reg_create_proc(const struct proc_info * info)
return 0;
- fail_proc:
+ fail:
pthread_mutex_unlock(&reg.mtx);
return -1;
}
@@ -1129,6 +1148,7 @@ int reg_create_proc(const struct proc_info * info)
int reg_destroy_proc(pid_t pid)
{
struct reg_proc * proc;
+ struct reg_pool * pool = NULL;
struct pid_entry * spawn;
struct reg_ipcp * ipcp;
@@ -1136,24 +1156,27 @@ int reg_destroy_proc(pid_t pid)
proc = __reg_get_proc(pid);
if (proc != NULL) {
- list_del(&proc->next);
- reg.n_procs--;
+ if (!is_ouroboros_member_uid(proc->info.uid))
+ pool = __reg_get_pool(proc->info.uid);
+ llist_del(&proc->next, &reg.procs);
reg_proc_destroy(proc);
__reg_del_proc_from_names(pid);
__reg_cancel_flows_for_proc(pid);
+ if (pool != NULL && reg_pool_unref(pool) == 0) {
+ llist_del(&pool->next, &reg.pools);
+ reg_pool_destroy(pool);
+ }
}
spawn = __reg_get_spawned(pid);
if (spawn != NULL) {
- list_del(&spawn->next);
- reg.n_spawned--;
+ llist_del(&spawn->next, &reg.spawned);
free(spawn);
}
ipcp = __reg_get_ipcp(pid);
if (ipcp != NULL) {
- list_del(&ipcp->next);
- reg.n_ipcps--;
+ llist_del(&ipcp->next, &reg.ipcps);
reg_ipcp_destroy(ipcp);
}
@@ -1175,6 +1198,38 @@ bool reg_has_proc(pid_t pid)
return ret;
}
+bool reg_is_proc_privileged(pid_t pid)
+{
+ struct reg_proc * proc;
+ bool ret = false;
+
+ pthread_mutex_lock(&reg.mtx);
+
+ proc = __reg_get_proc(pid);
+ if (proc != NULL)
+ ret = reg_proc_is_privileged(proc);
+
+ pthread_mutex_unlock(&reg.mtx);
+
+ return ret;
+}
+
+uid_t reg_get_proc_uid(pid_t pid)
+{
+ struct reg_proc * proc;
+ uid_t ret = 0;
+
+ pthread_mutex_lock(&reg.mtx);
+
+ proc = __reg_get_proc(pid);
+ if (proc != NULL && !is_ouroboros_member_uid(proc->info.uid))
+ ret = proc->info.uid;
+
+ pthread_mutex_unlock(&reg.mtx);
+
+ return ret;
+}
+
void reg_kill_all_proc(int signal)
{
pthread_mutex_lock(&reg.mtx);
@@ -1216,9 +1271,7 @@ int reg_create_spawned(pid_t pid)
entry->pid = pid;
- list_add(&entry->next, __reg_after_spawned(pid));
-
- reg.n_spawned++;
+ llist_add_at(&entry->next, __reg_after_spawned(pid), &reg.spawned);
pthread_mutex_unlock(&reg.mtx);
@@ -1388,9 +1441,7 @@ int reg_create_prog(const struct prog_info * info)
goto fail_prog;
}
- list_add(&prog->next, __reg_after_prog(info->name));
-
- reg.n_progs++;
+ llist_add_at(&prog->next, __reg_after_prog(info->name), &reg.progs);
exists:
pthread_mutex_unlock(&reg.mtx);
@@ -1418,9 +1469,7 @@ int reg_destroy_prog(const char * name)
__reg_del_prog_from_names(prog->info.path);
- list_del(&prog->next);
-
- reg.n_progs--;
+ llist_del(&prog->next, &reg.progs);
pthread_mutex_unlock(&reg.mtx);
@@ -1736,7 +1785,8 @@ int reg_wait_flow_allocated(struct flow_info * info,
}
if (flow != NULL) {
- reg_flow_get_data(flow, pbuf);
+ *pbuf = flow->rsp_data;
+ clrbuf(flow->rsp_data);
*info = flow->info;
}
@@ -1771,8 +1821,8 @@ int reg_respond_alloc(struct flow_info * info,
}
assert(flow->info.state == FLOW_ALLOC_PENDING);
- assert(flow->data.len == 0);
- assert(flow->data.data == NULL);
+ assert(flow->rsp_data.len == 0);
+ assert(flow->rsp_data.data == NULL);
info->n_pid = flow->info.n_pid;
info->n_1_pid = flow->info.n_pid;
@@ -1784,8 +1834,10 @@ int reg_respond_alloc(struct flow_info * info,
flow->response = response;
- if (info->state == FLOW_ALLOCATED)
- reg_flow_set_data(flow, pbuf);
+ if (info->state == FLOW_ALLOCATED) {
+ flow->rsp_data = *pbuf;
+ clrbuf(*pbuf);
+ }
pthread_cond_broadcast(&reg.cond);
@@ -1816,6 +1868,8 @@ int reg_prepare_flow_accept(struct flow_info * info)
ret = reg_flow_update(flow, info);
+ pthread_cond_broadcast(&reg.cond);
+
pthread_mutex_unlock(&reg.mtx);
return ret;
@@ -1893,7 +1947,8 @@ int reg_wait_flow_accepted(struct flow_info * info,
pthread_cleanup_pop(true); /* __cleanup_wait_accept */
if (flow != NULL) {
- reg_flow_get_data(flow, pbuf);
+ *pbuf = flow->req_data;
+ clrbuf(flow->req_data);
*info = flow->info;
}
@@ -1953,8 +2008,63 @@ int reg_respond_accept(struct flow_info * info,
info->n_pid = flow->info.n_pid;
- reg_flow_set_data(flow, pbuf);
- clrbuf(pbuf);
+ flow->req_data = *pbuf;
+ clrbuf(*pbuf);
+
+ if (reg_flow_update(flow, info) < 0) {
+ log_err("Failed to create flow structs.");
+ goto fail_flow;
+ }
+
+ pthread_cond_broadcast(&reg.cond);
+
+ pthread_mutex_unlock(&reg.mtx);
+
+ return 0;
+
+ fail_flow:
+ pthread_mutex_unlock(&reg.mtx);
+ return -1;
+}
+
+int reg_prepare_flow_direct(struct flow_info * info,
+ buffer_t * pbuf,
+ uid_t alloc_uid)
+{
+ struct reg_flow * flow;
+ struct reg_proc * proc;
+ uid_t accept_uid = 0;
+
+ assert(info != NULL);
+ assert(info->state == FLOW_ALLOCATED);
+ assert(info->n_1_pid != 0);
+ assert(pbuf != NULL);
+
+ pthread_mutex_lock(&reg.mtx);
+
+ flow = __reg_get_flow(info->id);
+ if (flow == NULL) {
+ log_err("Flow not found: %d.", info->id);
+ goto fail_flow;
+ }
+
+ assert(flow->info.state == FLOW_ACCEPT_PENDING);
+
+ info->n_pid = flow->info.n_pid;
+
+ proc = __reg_get_proc(info->n_pid);
+ if (proc != NULL && !is_ouroboros_member_uid(proc->info.uid))
+ accept_uid = proc->info.uid;
+
+ if (alloc_uid != accept_uid) {
+ pthread_mutex_unlock(&reg.mtx);
+ return -EPERM;
+ }
+
+ flow->direct = true;
+
+ flow->req_data = *pbuf;
+ clrbuf(*pbuf);
if (reg_flow_update(flow, info) < 0) {
log_err("Failed to create flow structs.");
@@ -1972,6 +2082,109 @@ int reg_respond_accept(struct flow_info * info,
return -1;
}
+bool reg_flow_is_direct(int flow_id)
+{
+ struct reg_flow * flow;
+ bool ret;
+
+ pthread_mutex_lock(&reg.mtx);
+
+ flow = __reg_get_flow(flow_id);
+
+ ret = flow != NULL && flow->direct;
+
+ pthread_mutex_unlock(&reg.mtx);
+
+ return ret;
+}
+
+int reg_respond_flow_direct(int flow_id,
+ buffer_t * pbuf)
+{
+ struct reg_flow * flow;
+
+ assert(pbuf != NULL);
+
+ pthread_mutex_lock(&reg.mtx);
+
+ flow = __reg_get_flow(flow_id);
+ if (flow == NULL) {
+ log_err("Flow %d not found.", flow_id);
+ goto fail;
+ }
+
+ assert(flow->direct);
+ assert(flow->rsp_data.data == NULL);
+
+ flow->rsp_data = *pbuf;
+ clrbuf(*pbuf);
+
+ pthread_cond_broadcast(&reg.cond);
+
+ pthread_mutex_unlock(&reg.mtx);
+
+ return 0;
+ fail:
+ pthread_mutex_unlock(&reg.mtx);
+ return -1;
+}
+
+int reg_wait_flow_direct(int flow_id,
+ buffer_t * pbuf,
+ const struct timespec * abstime)
+{
+ struct reg_flow * flow;
+ int ret = -1;
+
+ assert(pbuf != NULL);
+
+ pthread_mutex_lock(&reg.mtx);
+
+ flow = __reg_get_flow(flow_id);
+ if (flow == NULL)
+ goto fail;
+
+ assert(flow->direct);
+
+ pthread_cleanup_push(__cleanup_mutex_unlock, &reg.mtx);
+
+ while (flow != NULL && flow->rsp_data.data == NULL) {
+ ret = -__timedwait(&reg.cond, &reg.mtx, abstime);
+ if (ret == -ETIMEDOUT)
+ break;
+ flow = __reg_get_flow(flow_id);
+ }
+
+ if (flow != NULL && flow->rsp_data.data != NULL) {
+ *pbuf = flow->rsp_data;
+ clrbuf(flow->rsp_data);
+ ret = 0;
+ }
+
+ pthread_cleanup_pop(true);
+
+ return ret;
+ fail:
+ pthread_mutex_unlock(&reg.mtx);
+ return -1;
+}
+
+static int direct_flow_dealloc(struct reg_flow * flow,
+ pid_t pid)
+{
+ if (!flow->direct)
+ return -1;
+
+ if (pid == flow->info.n_pid && flow->info.n_pid != -1)
+ flow->info.n_pid = -1;
+ else if (pid == flow->info.n_1_pid && flow->info.n_1_pid != -1)
+ flow->info.n_1_pid = -1;
+ else
+ return -1;
+
+ return 0;
+}
+
void reg_dealloc_flow(struct flow_info * info)
{
struct reg_flow * flow;
@@ -1985,13 +2198,32 @@ void reg_dealloc_flow(struct flow_info * info)
flow = __reg_get_flow(info->id);
assert(flow != NULL);
- assert(flow->data.data == NULL);
- assert(flow->data.len == 0);
+ assert(flow->req_data.data == NULL);
+ assert(flow->req_data.len == 0);
+ assert(flow->rsp_data.data == NULL);
+ assert(flow->rsp_data.len == 0);
+
+ info->n_1_pid = flow->info.n_1_pid;
+
+ if (flow->info.state == FLOW_DEALLOC_PENDING) {
+ if (direct_flow_dealloc(flow, info->n_pid) < 0) {
+ info->state = FLOW_DEALLOC_PENDING;
+ pthread_mutex_unlock(&reg.mtx);
+ return;
+ }
+ flow->info.state = FLOW_DEALLOCATED;
+ info->state = FLOW_DEALLOCATED;
+ reg_flow_update(flow, info);
+ pthread_mutex_unlock(&reg.mtx);
+ return;
+ }
+
assert(flow->info.state == FLOW_ALLOCATED);
flow->info.state = FLOW_DEALLOC_PENDING;
info->state = FLOW_DEALLOC_PENDING;
- info->n_1_pid = flow->info.n_1_pid;
+
+ direct_flow_dealloc(flow, info->n_pid);
memset(flow->name, 0, sizeof(flow->name));
@@ -2013,8 +2245,10 @@ void reg_dealloc_flow_resp(struct flow_info * info)
flow = __reg_get_flow(info->id);
assert(flow != NULL);
- assert(flow->data.data == NULL);
- assert(flow->data.len == 0);
+ assert(flow->req_data.data == NULL);
+ assert(flow->req_data.len == 0);
+ assert(flow->rsp_data.data == NULL);
+ assert(flow->rsp_data.len == 0);
assert(flow->info.state == FLOW_DEALLOC_PENDING);
flow->info.state = FLOW_DEALLOCATED;
diff --git a/src/irmd/reg/reg.h b/src/irmd/reg/reg.h
index 7728c80f..6b576471 100644
--- a/src/irmd/reg/reg.h
+++ b/src/irmd/reg/reg.h
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* The IPC Resource Manager - Registry
*
@@ -31,6 +31,8 @@
#include <ouroboros/time.h>
#include <ouroboros/utils.h>
+#include "pool.h"
+
int reg_init(void);
void reg_clear(void);
@@ -50,6 +52,13 @@ int reg_destroy_proc(pid_t pid);
bool reg_has_proc(pid_t pid);
+bool reg_is_proc_privileged(pid_t pid);
+
+int reg_prepare_pool(uid_t uid,
+ gid_t gid);
+
+uid_t reg_get_proc_uid(pid_t pid);
+
void reg_kill_all_proc(int signal);
pid_t reg_get_dead_proc(void);
@@ -141,6 +150,19 @@ int reg_wait_flow_accepting(const char * name,
int reg_respond_accept(struct flow_info * info,
buffer_t * pbuf);
+int reg_prepare_flow_direct(struct flow_info * info,
+ buffer_t * pbuf,
+ uid_t alloc_uid);
+
+int reg_respond_flow_direct(int flow_id,
+ buffer_t * pbuf);
+
+int reg_wait_flow_direct(int flow_id,
+ buffer_t * pbuf,
+ const struct timespec * abstime);
+
+bool reg_flow_is_direct(int flow_id);
+
void reg_dealloc_flow(struct flow_info * info);
void reg_dealloc_flow_resp(struct flow_info * info);
diff --git a/src/irmd/reg/tests/CMakeLists.txt b/src/irmd/reg/tests/CMakeLists.txt
index 73df911b..e8521545 100644
--- a/src/irmd/reg/tests/CMakeLists.txt
+++ b/src/irmd/reg/tests/CMakeLists.txt
@@ -1,7 +1,9 @@
-get_filename_component(tmp ".." ABSOLUTE)
-get_filename_component(src_folder "${tmp}" NAME)
+get_filename_component(PARENT_PATH ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY)
+get_filename_component(PARENT_DIR ${PARENT_PATH} NAME)
-create_test_sourcelist(${src_folder}_tests test_suite.c
+compute_test_prefix()
+
+create_test_sourcelist(${PARENT_DIR}_tests test_suite.c
# Add new tests here
flow_test.c
ipcp_test.c
@@ -11,23 +13,21 @@ create_test_sourcelist(${src_folder}_tests test_suite.c
reg_test.c
)
-add_executable(${src_folder}_test ${${src_folder}_tests})
-target_link_libraries(${src_folder}_test ouroboros-common)
+add_executable(${PARENT_DIR}_test ${${PARENT_DIR}_tests})
-if (CMAKE_BUILD_TYPE MATCHES "Debug*")
- add_compile_flags(${src_folder}_test -DCONFIG_OUROBOROS_DEBUG)
-endif ()
+target_include_directories(${PARENT_DIR}_test PRIVATE
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}/include
+ ${CMAKE_BINARY_DIR}/include
+ ${CMAKE_SOURCE_DIR}/src/irmd
+ ${CMAKE_BINARY_DIR}/src/irmd
+)
-add_dependencies(check ${src_folder}_test)
+disable_test_logging_for_target(${PARENT_DIR}_test)
+target_link_libraries(${PARENT_DIR}_test PRIVATE ouroboros-common)
+ouroboros_target_debug_definitions(${PARENT_DIR}_test)
-set(tests_to_run ${${src_folder}_tests})
-if(CMAKE_VERSION VERSION_LESS "3.29.0")
- remove(tests_to_run test_suite.c)
-else ()
- list(POP_FRONT tests_to_run)
-endif()
+add_dependencies(build_tests ${PARENT_DIR}_test)
-foreach(test ${tests_to_run})
- get_filename_component(test_name ${test} NAME_WE)
- add_test(irmd/reg/${test_name} ${C_TEST_PATH}/${src_folder}_test ${test_name})
-endforeach(test)
+ouroboros_register_tests(TARGET ${PARENT_DIR}_test TESTS ${${PARENT_DIR}_tests})
diff --git a/src/irmd/reg/tests/flow_test.c b/src/irmd/reg/tests/flow_test.c
index 27fd61b0..7e1c1360 100644
--- a/src/irmd/reg/tests/flow_test.c
+++ b/src/irmd/reg/tests/flow_test.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* The IPC Resource Manager - Registry - Flows - Unit Tests
*
@@ -22,11 +22,7 @@
#include "../flow.c"
-#include <ouroboros/test.h>
-
-#include <string.h>
-
-#define TEST_DATA "testpiggybackdata"
+#include <test/test.h>
static int test_reg_flow_create_destroy(void)
{
@@ -219,56 +215,6 @@ static int test_reg_flow_assert_fails(void)
return ret;
}
-static int test_flow_data(void)
-{
- struct reg_flow * f;
-
- struct flow_info info = {
- .id = 1,
- .n_pid = 1,
- .qs = qos_raw,
- .state = FLOW_INIT
- };
-
- char * data;
- buffer_t buf;
- buffer_t rcv = {0, NULL};
-
- TEST_START();
-
- data = strdup(TEST_DATA);
- if (data == NULL) {
- printf("Failed to strdup data.\n");
- goto fail;
- }
-
- buf.data = (uint8_t *) data;
- buf.len = strlen(data);
-
- f = reg_flow_create(&info);
- if (f == NULL) {
- printf("Failed to create flow.\n");
- goto fail;
- }
-
- reg_flow_set_data(f, &buf);
-
- reg_flow_get_data(f, &rcv);
-
- freebuf(buf);
- clrbuf(rcv);
-
- reg_flow_destroy(f);
-
- TEST_SUCCESS();
-
- return TEST_RC_SUCCESS;
- fail:
- free(data);
- TEST_FAIL();
- return TEST_RC_FAIL;
-}
-
int flow_test(int argc,
char ** argv)
{
@@ -280,7 +226,6 @@ int flow_test(int argc,
ret |= test_reg_flow_create_destroy();
ret |= test_reg_flow_update();
ret |= test_reg_flow_assert_fails();
- ret |= test_flow_data();
return ret;
}
diff --git a/src/irmd/reg/tests/ipcp_test.c b/src/irmd/reg/tests/ipcp_test.c
index d7d8e524..5279283e 100644
--- a/src/irmd/reg/tests/ipcp_test.c
+++ b/src/irmd/reg/tests/ipcp_test.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* The IPC Resource Manager - Registry - IPCPs - Unit Tests
*
@@ -20,7 +20,7 @@
* Foundation, Inc., http://www.fsf.org/about/contact/.
*/
-#include <ouroboros/test.h>
+#include <test/test.h>
#include "../ipcp.c"
diff --git a/src/irmd/reg/tests/name_test.c b/src/irmd/reg/tests/name_test.c
index 9071364b..403c8a6c 100644
--- a/src/irmd/reg/tests/name_test.c
+++ b/src/irmd/reg/tests/name_test.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* The IPC Resource Manager - Registry - Names - Unit Tests
*
@@ -23,7 +23,7 @@
#include "../name.c"
-#include <ouroboros/test.h>
+#include <test/test.h>
#define TEST_PID 65534
#define TEST_PROG "/usr/bin/testprog"
@@ -88,7 +88,7 @@ static int test_reg_name_add_proc(void)
reg_name_del_proc(n, TEST_PID);
- if (n->procs.len != 0) {
+ if (!llist_is_empty(&n->procs)) {
printf("Proc not removed from list.\n");
goto fail;
}
@@ -138,7 +138,7 @@ static int test_reg_name_add_prog(void)
reg_name_del_prog(n, TEST_PROG);
- if (n->progs.len != 0) {
+ if (!llist_is_empty(&n->progs)) {
printf("Prog not removed from list.\n");
goto fail;
}
@@ -263,12 +263,12 @@ static int test_reg_name_add_active(enum pol_balance lb)
reg_name_del_proc(n, TEST_PID);
- if (n->procs.len != 0) {
+ if (!llist_is_empty(&n->procs)) {
printf("Procs list not cleared.\n");
goto fail;
}
- if (n->active.len != 0) {
+ if (!llist_is_empty(&n->active)) {
printf("Active list not cleared.\n");
goto fail;
}
diff --git a/src/irmd/reg/tests/proc_test.c b/src/irmd/reg/tests/proc_test.c
index df0527fb..a85f4039 100644
--- a/src/irmd/reg/tests/proc_test.c
+++ b/src/irmd/reg/tests/proc_test.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* The IPC Resource Manager - Registry - Processes - Unit Tests
*
@@ -22,18 +22,22 @@
#include "../proc.c"
-#include <ouroboros/test.h>
+#include <test/test.h>
#define TEST_PID 65534
#define TEST_PROG "usr/bin/testprog"
+#define TEST_PROC { \
+ .pid = TEST_PID, \
+ .prog = TEST_PROG, \
+ .uid = getuid(), \
+ .gid = getgid() \
+}
+
static int test_reg_proc_create_destroy(void)
{
struct reg_proc * proc;
- struct proc_info info = {
- .pid = TEST_PID,
- .prog = TEST_PROG
- };
+ struct proc_info info = TEST_PROC;
TEST_START();
@@ -56,10 +60,7 @@ static int test_reg_proc_create_destroy(void)
static int test_reg_proc_add_name(void)
{
struct reg_proc * proc;
- struct proc_info info = {
- .pid = TEST_PID,
- .prog = TEST_PROG
- };
+ struct proc_info info = TEST_PROC;
char * name = "testname";
@@ -76,7 +77,7 @@ static int test_reg_proc_add_name(void)
goto fail;
}
- if (proc->n_names != 1) {
+ if (proc->names.len != 1) {
printf("n_names not updated.\n");
goto fail;
}
@@ -88,7 +89,7 @@ static int test_reg_proc_add_name(void)
reg_proc_del_name(proc, name);
- if (proc->n_names != 0) {
+ if (!llist_is_empty(&proc->names)) {
printf("n_names not updated.\n");
goto fail;
}
diff --git a/src/irmd/reg/tests/prog_test.c b/src/irmd/reg/tests/prog_test.c
index c394c222..91264aba 100644
--- a/src/irmd/reg/tests/prog_test.c
+++ b/src/irmd/reg/tests/prog_test.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* The IPC Resource Manager - Registry - Programs - Unit Tests
*
@@ -22,7 +22,7 @@
#include "../prog.c"
-#include <ouroboros/test.h>
+#include <test/test.h>
#define TEST_PROG "usr/bin/testprog"
@@ -73,7 +73,7 @@ static int test_reg_prog_add_name(void)
goto fail;
}
- if (prog->n_names != 1) {
+ if (prog->names.len != 1) {
printf("n_names not updated.\n");
goto fail;
}
@@ -85,7 +85,7 @@ static int test_reg_prog_add_name(void)
reg_prog_del_name(prog, name);
- if (prog->n_names != 0) {
+ if (!llist_is_empty(&prog->names)) {
printf("n_names not updated.\n");
goto fail;
}
diff --git a/src/irmd/reg/tests/reg_test.c b/src/irmd/reg/tests/reg_test.c
index 4699beab..b426c0dd 100644
--- a/src/irmd/reg/tests/reg_test.c
+++ b/src/irmd/reg/tests/reg_test.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* The IPC Resource Manager - Registry - Unit Tests
*
@@ -21,9 +21,11 @@
*/
+#include "../pool.c"
+#undef OUROBOROS_PREFIX
#include "../reg.c"
-#include <ouroboros/test.h>
+#include <test/test.h>
#define TEST_PID 3666
#define TEST_N_1_PID 3999
@@ -35,8 +37,14 @@
#define TEST_DATA "testpbufdata"
#define TEST_DATA2 "testpbufdata2"
#define TEST_LAYER "testlayer"
+#define TEST_PROC_INFO { \
+ .pid = TEST_PID, \
+ .prog = TEST_PROG, \
+ .uid = 0, \
+ .gid = 0 \
+}
#define REG_TEST_FAIL() \
- do { TEST_FAIL(); memset(&reg, 0, sizeof(reg)); abort();} while(0)
+ do { TEST_FAIL(); reg_clear(); return TEST_RC_FAIL;} while(0)
static int test_reg_init(void)
{
@@ -81,7 +89,7 @@ static int test_reg_create_flow(void)
goto fail;
}
- if (reg.n_flows != 1) {
+ if (reg.flows.len != 1) {
printf("n_flows was not updated.\n");
goto fail;
}
@@ -96,8 +104,8 @@ static int test_reg_create_flow(void)
goto fail;
}
- if (reg.n_flows != 0) {
- printf("n_flows was not updated.\n");
+ if (!llist_is_empty(&reg.flows)) {
+ printf("flows.len was not updated.\n");
goto fail;
}
@@ -155,7 +163,7 @@ static int test_reg_allocate_flow_timeout(void)
reg_destroy_flow(info.id);
- if (reg.n_flows != 0) {
+ if (!llist_is_empty(&reg.flows)) {
printf("Flow did not destroy.\n");
goto fail;
}
@@ -189,6 +197,8 @@ static void * test_flow_respond_alloc(void * o)
reg_respond_alloc(info, &pbuf, response);
+ freebuf(pbuf);
+
return (void *) 0;
fail:
return (void *) -1;
@@ -208,6 +218,8 @@ static void * test_flow_respond_accept(void * o)
reg_respond_accept(info, &pbuf);
+ freebuf(pbuf);
+
return (void *) 0;
fail:
return (void *) -1;
@@ -259,6 +271,9 @@ static int test_reg_accept_flow_success(void)
if (reg_wait_flow_accepted(&info, &rbuf, &abstime) < 0) {
printf("Flow allocation failed.\n");
+ pthread_join(thr, NULL);
+ reg_destroy_flow(info.id);
+ reg_fini();
goto fail;
}
@@ -353,6 +368,9 @@ static int test_reg_accept_flow_success_no_crypt(void)
if (reg_wait_flow_accepted(&info, &rbuf, &abstime) < 0 ) {
printf("Flow allocation failed.\n");
+ pthread_join(thr, NULL);
+ reg_destroy_flow(info.id);
+ reg_fini();
goto fail;
}
@@ -446,6 +464,9 @@ static int test_reg_allocate_flow_fail(void)
if (reg_wait_flow_allocated(&info, &buf, &abstime) == 0 ) {
printf("Flow allocation succeeded.\n");
+ pthread_join(thr, NULL);
+ reg_destroy_flow(info.id);
+ reg_fini();
goto fail;
}
@@ -468,6 +489,188 @@ static int test_reg_allocate_flow_fail(void)
return TEST_RC_FAIL;
}
+struct direct_alloc_info {
+ struct flow_info info;
+ buffer_t rsp;
+ struct timespec abstime;
+};
+
+static void * test_flow_alloc_direct(void * o)
+{
+ struct direct_alloc_info * dai;
+ buffer_t req;
+
+ dai = (struct direct_alloc_info *) o;
+
+ req.data = (uint8_t *) strdup(TEST_DATA);
+ if (req.data == NULL) {
+ printf("Failed to strdup req data.\n");
+ goto fail;
+ }
+ req.len = strlen(TEST_DATA) + 1;
+
+ if (reg_prepare_flow_direct(&dai->info, &req, 0) < 0) {
+ printf("Failed to prepare direct flow.\n");
+ freebuf(req);
+ goto fail;
+ }
+
+ if (reg_wait_flow_direct(dai->info.id, &dai->rsp, &dai->abstime) < 0) {
+ printf("Failed to wait direct flow.\n");
+ goto fail;
+ }
+
+ return (void *) 0;
+ fail:
+ return (void *) -1;
+}
+
+static int test_reg_direct_flow_success(void)
+{
+ pthread_t thr;
+ struct timespec abstime;
+ struct timespec timeo = TIMESPEC_INIT_S(1);
+ buffer_t rbuf = BUF_INIT;
+ buffer_t rsp;
+ struct direct_alloc_info dai;
+ void * thr_ret;
+
+ struct flow_info info = {
+ .n_pid = TEST_PID,
+ .qs = qos_raw
+ };
+
+ TEST_START();
+
+ clock_gettime(PTHREAD_COND_CLOCK, &abstime);
+
+ ts_add(&abstime, &timeo, &abstime);
+
+ if (reg_init() < 0) {
+ printf("Failed to init registry.\n");
+ goto fail;
+ }
+
+ if (reg_create_flow(&info) < 0) {
+ printf("Failed to add flow.\n");
+ goto fail;
+ }
+
+ if (reg_prepare_flow_accept(&info) < 0) {
+ printf("Failed to prepare for accept.\n");
+ goto fail;
+ }
+
+ dai.info.id = info.id;
+ dai.info.n_1_pid = TEST_N_1_PID;
+ dai.info.mpl = TEST_MPL;
+ dai.info.qs = qos_data;
+ dai.info.state = FLOW_ALLOCATED;
+ dai.rsp.len = 0;
+ dai.rsp.data = NULL;
+ dai.abstime = abstime;
+
+ pthread_create(&thr, NULL, test_flow_alloc_direct, &dai);
+
+ if (reg_wait_flow_accepted(&info, &rbuf, &abstime) < 0) {
+ printf("Flow accept failed.\n");
+ pthread_join(thr, NULL);
+ reg_destroy_flow(info.id);
+ reg_fini();
+ goto fail;
+ }
+
+ if (info.state != FLOW_ALLOCATED) {
+ printf("Flow not in allocated state.\n");
+ goto fail;
+ }
+
+ if (rbuf.data == NULL) {
+ printf("req_data not received.\n");
+ goto fail;
+ }
+
+ if (strcmp((char *) rbuf.data, TEST_DATA) != 0) {
+ printf("req_data content mismatch.\n");
+ goto fail;
+ }
+
+ freebuf(rbuf);
+
+ if (!reg_flow_is_direct(info.id)) {
+ printf("Flow not marked direct.\n");
+ goto fail;
+ }
+
+ rsp.data = (uint8_t *) strdup(TEST_DATA2);
+ if (rsp.data == NULL) {
+ printf("Failed to strdup rsp data.\n");
+ goto fail;
+ }
+ rsp.len = strlen(TEST_DATA2) + 1;
+
+ if (reg_respond_flow_direct(info.id, &rsp) < 0) {
+ printf("Failed to respond direct.\n");
+ freebuf(rsp);
+ goto fail;
+ }
+
+ pthread_join(thr, &thr_ret);
+
+ if (thr_ret != (void *) 0) {
+ printf("Allocator thread failed.\n");
+ goto fail;
+ }
+
+ if (dai.rsp.data == NULL) {
+ printf("rsp_data not received.\n");
+ goto fail;
+ }
+
+ if (strcmp((char *) dai.rsp.data, TEST_DATA2) != 0) {
+ printf("rsp_data content mismatch.\n");
+ goto fail;
+ }
+
+ freebuf(dai.rsp);
+
+ reg_dealloc_flow(&info);
+
+ if (info.state != FLOW_DEALLOC_PENDING) {
+ printf("Flow not in dealloc pending.\n");
+ goto fail;
+ }
+
+ info.n_pid = TEST_PID;
+
+ reg_dealloc_flow(&info);
+
+ if (info.state != FLOW_DEALLOC_PENDING) {
+ printf("Same endpoint dealloc changed state.\n");
+ goto fail;
+ }
+
+ info.n_pid = TEST_N_1_PID;
+
+ reg_dealloc_flow(&info);
+
+ if (info.state != FLOW_DEALLOCATED) {
+ printf("Flow not deallocated.\n");
+ goto fail;
+ }
+
+ reg_destroy_flow(info.id);
+
+ reg_fini();
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ REG_TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
static int test_reg_flow(void) {
int rc = 0;
@@ -476,6 +679,7 @@ static int test_reg_flow(void) {
rc |= test_reg_accept_flow_success();
rc |= test_reg_accept_flow_success_no_crypt();
rc |= test_reg_allocate_flow_fail();
+ rc |= test_reg_direct_flow_success();
return rc;
}
@@ -500,7 +704,7 @@ static int test_reg_create_ipcp(void)
goto fail;
}
- if (reg.n_ipcps != 1) {
+ if (reg.ipcps.len != 1) {
printf("n_ipcps was not updated.\n");
goto fail;
}
@@ -515,8 +719,8 @@ static int test_reg_create_ipcp(void)
goto fail;
}
- if (reg.n_ipcps != 0) {
- printf("n_ipcps was not updated.\n");
+ if (reg.ipcps.len != 0) {
+ printf("ipcps.len was not updated.\n");
goto fail;
}
@@ -744,7 +948,7 @@ static int test_reg_create_name(void)
goto fail;
}
- if (reg.n_names != 1) {
+ if (reg.names.len != 1) {
printf("n_names was not updated.\n");
goto fail;
}
@@ -759,7 +963,7 @@ static int test_reg_create_name(void)
goto fail;
}
- if (reg.n_names != 0) {
+ if (!llist_is_empty(&reg.names)) {
printf("n_names was not updated.\n");
goto fail;
}
@@ -843,10 +1047,7 @@ static int test_reg_name(void)
static int test_reg_create_proc(void)
{
- struct proc_info info = {
- .pid = TEST_PID,
- .prog = TEST_PROG
- };
+ struct proc_info info = TEST_PROC_INFO;
TEST_START();
@@ -860,7 +1061,7 @@ static int test_reg_create_proc(void)
goto fail;
}
- if (reg.n_procs != 1) {
+ if (reg.procs.len != 1) {
printf("n_procs was not updated.\n");
goto fail;
}
@@ -875,7 +1076,7 @@ static int test_reg_create_proc(void)
goto fail;
}
- if (reg.n_procs != 0) {
+ if (!llist_is_empty(&reg.procs)) {
printf("n_procs was not updated.\n");
goto fail;
}
@@ -913,7 +1114,7 @@ static int test_reg_spawned(void)
goto fail;
}
- if (reg.n_spawned != 1) {
+ if (reg.spawned.len != 1) {
printf("n_spawned was not updated.\n");
goto fail;
}
@@ -928,7 +1129,7 @@ static int test_reg_spawned(void)
goto fail;
}
- if (reg.n_spawned != 0) {
+ if (!llist_is_empty(&reg.spawned)) {
printf("n_spawned was not updated.\n");
goto fail;
}
@@ -961,7 +1162,7 @@ static int test_reg_create_prog(void)
goto fail;
}
- if (reg.n_progs != 1) {
+ if (reg.progs.len != 1) {
printf("n_progs was not updated.\n");
goto fail;
}
@@ -976,7 +1177,7 @@ static int test_reg_create_prog(void)
goto fail;
}
- if (reg.n_progs != 0) {
+ if (!llist_is_empty(&reg.progs)) {
printf("n_progs was not updated.\n");
goto fail;
}
@@ -1002,10 +1203,7 @@ static int test_reg_prog(void)
static int test_bind_proc(void)
{
- struct proc_info pinfo = {
- .pid = TEST_PID,
- .prog = TEST_PROG
- };
+ struct proc_info pinfo = TEST_PROC_INFO;
struct name_info ninfo = {
.name = TEST_NAME,
@@ -1158,10 +1356,7 @@ static int test_inherit_prog(void)
.name = TEST_PROG
};
- struct proc_info procinfo = {
- .pid = TEST_PID,
- .prog = TEST_PROG
- };
+ struct proc_info procinfo = TEST_PROC_INFO;
char * exec[] = { TEST_PROG, NULL};
@@ -1296,13 +1491,10 @@ static int test_wait_accepting_fail_name(void)
static void * test_call_flow_accept(void * o)
{
struct timespec abstime;
- struct timespec timeo = TIMESPEC_INIT_MS(1);
+ struct timespec timeo = TIMESPEC_INIT_MS(10);
buffer_t pbuf = BUF_INIT;
- struct proc_info pinfo = {
- .pid = TEST_PID,
- .prog = TEST_PROG
- };
+ struct proc_info pinfo = TEST_PROC_INFO;
struct flow_info info = {
.n_pid = pinfo.pid,
@@ -1326,16 +1518,21 @@ static void * test_call_flow_accept(void * o)
info.state = FLOW_ACCEPT_PENDING;
+ reg_prepare_flow_accept(&info);
+
clock_gettime(PTHREAD_COND_CLOCK, &abstime);
ts_add(&abstime, &timeo, &abstime);
- reg_prepare_flow_accept(&info);
-
if (reg_wait_flow_accepted(&info, &pbuf, &abstime) != -ETIMEDOUT) {
printf("Wait allocated did not timeout.\n");
goto fail;
}
+ if (reg_unbind_proc((char *) o, pinfo.pid) < 0) {
+ printf("Failed to unbind proc.\n");
+ goto fail;
+ }
+
reg_destroy_flow(info.id);
reg_destroy_proc(pinfo.pid);
@@ -1347,7 +1544,7 @@ static void * test_call_flow_accept(void * o)
static int test_wait_accepting_success(void)
{
struct timespec abstime;
- struct timespec timeo = TIMESPEC_INIT_S(1);
+ struct timespec timeo = TIMESPEC_INIT_S(10);
pthread_t thr;
int flow_id;
struct name_info ninfo = {
@@ -1375,7 +1572,10 @@ static int test_wait_accepting_success(void)
flow_id = reg_wait_flow_accepting(ninfo.name, &abstime);
if (flow_id < 0) {
- printf("Wait accept did not return a flow id: %d.", flow_id);
+ printf("Wait accept did not return a flow id: %d.\n", flow_id);
+ pthread_join(thr, NULL);
+ reg_destroy_name(TEST_NAME);
+ reg_fini();
goto fail;
}
@@ -1461,7 +1661,7 @@ static void * test_ipcp_respond(void * o)
static int test_wait_ipcp_boot_fail(void)
{
struct timespec abstime;
- struct timespec timeo = TIMESPEC_INIT_S(1);
+ struct timespec timeo = TIMESPEC_INIT_S(10);
pthread_t thr;
struct ipcp_info info = {
.name = TEST_IPCP,
@@ -1471,7 +1671,7 @@ static int test_wait_ipcp_boot_fail(void)
struct ipcp_info resp_info = {
.name = TEST_IPCP,
.pid = TEST_PID,
- .state = IPCP_INIT
+ .state = IPCP_NULL
};
TEST_START();
@@ -1495,6 +1695,9 @@ static int test_wait_ipcp_boot_fail(void)
if (reg_wait_ipcp_boot(&info, &abstime) == 0) {
printf("IPCP boot reported success.\n");
+ pthread_join(thr, NULL);
+ reg_destroy_proc(info.pid);
+ reg_fini();
goto fail;
}
@@ -1505,8 +1708,8 @@ static int test_wait_ipcp_boot_fail(void)
goto fail;
}
- if (reg.n_ipcps != 0) {
- printf("n_ipcps was not updated.\n");
+ if (!llist_is_empty(&reg.ipcps)) {
+ printf("ipcps.len was not updated.\n");
goto fail;
}
@@ -1524,7 +1727,7 @@ static int test_wait_ipcp_boot_success(void)
{
pthread_t thr;
struct timespec abstime;
- struct timespec timeo = TIMESPEC_INIT_S(1);
+ struct timespec timeo = TIMESPEC_INIT_S(10);
struct ipcp_info info = {
.name = TEST_IPCP,
.pid = TEST_PID,
@@ -1557,6 +1760,9 @@ static int test_wait_ipcp_boot_success(void)
if (reg_wait_ipcp_boot(&info, &abstime) < 0) {
printf("IPCP boot failed.\n");
+ pthread_join(thr, NULL);
+ reg_destroy_proc(info.pid);
+ reg_fini();
goto fail;
}
@@ -1564,6 +1770,8 @@ static int test_wait_ipcp_boot_success(void)
if (info.state != IPCP_OPERATIONAL) {
printf("IPCP boot succeeded in non-operational state.\n");
+ reg_destroy_proc(info.pid);
+ reg_fini();
goto fail;
}
@@ -1636,12 +1844,9 @@ static void * test_proc(void * o)
static int test_wait_proc_success(void)
{
struct timespec abstime;
- struct timespec timeo = TIMESPEC_INIT_S(1);
+ struct timespec timeo = TIMESPEC_INIT_S(10);
pthread_t thr;
- struct proc_info info = {
- .pid = TEST_PID,
- .prog = TEST_PROG
- };
+ struct proc_info info = TEST_PROC_INFO;
TEST_START();
@@ -1657,6 +1862,9 @@ static int test_wait_proc_success(void)
if (reg_wait_proc(info.pid, &abstime) < 0) {
printf("Waiting for proc failed.\n");
+ pthread_join(thr, NULL);
+ reg_destroy_proc(info.pid);
+ reg_fini();
goto fail;
}
diff --git a/src/irmd/tests/CMakeLists.txt b/src/irmd/tests/CMakeLists.txt
deleted file mode 100644
index 4b62261c..00000000
--- a/src/irmd/tests/CMakeLists.txt
+++ /dev/null
@@ -1,27 +0,0 @@
-get_filename_component(tmp ".." ABSOLUTE)
-get_filename_component(src_folder "${tmp}" NAME)
-
-create_test_sourcelist(${src_folder}_tests test_suite.c
- # Add new tests here
- irm_test.c
- oap_test.c
-)
-
-add_executable(${src_folder}_test ${${src_folder}_tests})
-target_link_libraries(${src_folder}_test ouroboros-common)
-
-add_dependencies(check ${src_folder}_test)
-
-set(tests_to_run ${${src_folder}_tests})
-if(CMAKE_VERSION VERSION_LESS "3.29.0")
- remove(tests_to_run test_suite.c)
-else ()
- list(POP_FRONT tests_to_run)
-endif()
-
-foreach(test ${tests_to_run})
- get_filename_component(test_name ${test} NAME_WE)
- add_test(irmd/${test_name} ${C_TEST_PATH}/${src_folder}_test ${test_name})
-endforeach(test)
-
-set_property(TEST irmd/oap_test PROPERTY SKIP_RETURN_CODE 1)
diff --git a/src/irmd/tests/oap_test.c b/src/irmd/tests/oap_test.c
deleted file mode 100644
index 4e7fb2d1..00000000
--- a/src/irmd/tests/oap_test.c
+++ /dev/null
@@ -1,285 +0,0 @@
-/*
- * Ouroboros - Copyright (C) 2016 - 2024
- *
- * Unit tests of Ouroboros flow allocation protocol
- * 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 "oap.c"
-
-#include <ouroboros/random.h>
-#include <ouroboros/test.h>
-
-static const char * pkp_str = \
-"-----BEGIN EC PRIVATE KEY-----\n"
-"MHcCAQEEIC13y+5jdKe80HBJD7WITpQamcn3rrkTX1r0v+JwSk4NoAoGCCqGSM49\n"
-"AwEHoUQDQgAEcC0yLAfUtufH8cdLybrdWPc6U+xRuhDhqqrEcBO5+eob2xyqEaNk\n"
-"nIV/86724zPptGRahWz0rzW2PvNppJdNBg==\n"
-"-----END EC PRIVATE KEY-----\n";
-
-/* Valid signed server certificate for server-2.unittest.o7s */
-static const char * crt_str = \
-"-----BEGIN CERTIFICATE-----\n"
-"MIIDgjCCAyigAwIBAgICEAIwCgYIKoZIzj0EAwIwWzELMAkGA1UEBhMCQkUxDDAK\n"
-"BgNVBAgMA09WTDEMMAoGA1UECgwDbzdzMRUwEwYDVQQLDAx1bml0dGVzdC5vN3Mx\n"
-"GTAXBgNVBAMMEGltMi51bml0dGVzdC5vN3MwHhcNMjUwNzA0MTMxODI5WhcNMzUw\n"
-"NzAyMTMxODI5WjBwMQswCQYDVQQGEwJCRTEMMAoGA1UECAwDT1ZMMQ4wDAYDVQQH\n"
-"DAVHaGVudDEMMAoGA1UECgwDbzdzMRUwEwYDVQQLDAx1bml0dGVzdC5vN3MxHjAc\n"
-"BgNVBAMMFXNlcnZlci0yLnVuaXR0ZXN0Lm83czBZMBMGByqGSM49AgEGCCqGSM49\n"
-"AwEHA0IABHAtMiwH1Lbnx/HHS8m63Vj3OlPsUboQ4aqqxHATufnqG9scqhGjZJyF\n"
-"f/Ou9uMz6bRkWoVs9K81tj7zaaSXTQajggHFMIIBwTAJBgNVHRMEAjAAMBEGCWCG\n"
-"SAGG+EIBAQQEAwIGQDA6BglghkgBhvhCAQ0ELRYrR3JpbGxlZCBDaGVlc2UgR2Vu\n"
-"ZXJhdGVkIFNlcnZlciBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUTt3xHTwE9amoglxh\n"
-"cEMqWv+PpDMwgb8GA1UdIwSBtzCBtIAUFfeZRx8QWWKQr7Aw8zjDu2shvcShgZek\n"
-"gZQwgZExCzAJBgNVBAYTAkJFMQwwCgYDVQQIDANPVkwxDjAMBgNVBAcMBUdoZW50\n"
-"MQwwCgYDVQQKDANvN3MxFTATBgNVBAsMDHVuaXR0ZXN0Lm83czEZMBcGA1UEAwwQ\n"
-"Y2EyLnVuaXR0ZXN0Lm83czEkMCIGCSqGSIb3DQEJARYVZHVtbXlAb3Vyb2Jvcm9z\n"
-"LnJvY2tzggIQAjAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEw\n"
-"EQYDVR0fBAowCDAGoASgAoYAMCoGCCsGAQUFBwEBBB4wHDAMBggrBgEFBQcwAoYA\n"
-"MAwGCCsGAQUFBzABhgAwIAYDVR0RBBkwF4IVc2VydmVyLTEudW5pdHRlc3Qubzdz\n"
-"MAoGCCqGSM49BAMCA0gAMEUCIQDHuDb62w/Uah4nKwUFoJVkr4rgdNGh2Rn3SWaK\n"
-"0FV/gAIgOLKorTwSgrTFdyOUkuPOhRs8BEMpah+dp8UTO8AnLvY=\n"
-"-----END CERTIFICATE-----\n";
-
-static int test_oap_hdr_init_fini(void)
-{
- struct oap_hdr oap_hdr;
- struct timespec now;
- uint64_t stamp;
- buffer_t ephkey = BUF_INIT;
- buffer_t data = BUF_INIT;
- uint8_t buf[OAP_ID_SIZE];
- buffer_t id;
- void * pkp = NULL;
- void * pubcrt = NULL;
-
- TEST_START();
-
- random_buffer(buf, OAP_ID_SIZE);
- id.data = buf;
- id.len = OAP_ID_SIZE;
-
- clock_gettime(CLOCK_REALTIME, &now);
- stamp = TS_TO_UINT64(now);
-
- if (oap_hdr_init(id, pkp, pubcrt, ephkey, data, &oap_hdr) < 0) {
- printf("Failed to init OAP request header.\n");
- goto fail_req_hdr;
- }
-
- if (oap_hdr.hdr.len != OAP_HDR_MIN_SIZE) {
- printf("OAP request header wrong: %zu < %zu.\n",
- oap_hdr.hdr.len, OAP_HDR_MIN_SIZE);
- goto fail_req_hdr_chk;
- }
-
- if (oap_hdr.id.len != OAP_ID_SIZE) {
- printf("OAP request header ID wrong size: %zu != %zu.\n",
- oap_hdr.id.len, (size_t) OAP_ID_SIZE);
- goto fail_req_hdr_chk;
- }
-
- if (memcmp(oap_hdr.id.data, id.data, OAP_ID_SIZE) != 0) {
- printf("OAP request header ID mismatch.\n");
- goto fail_req_hdr_chk;
- }
-
- if (oap_hdr.timestamp < stamp) {
- printf("OAP request header timestamp is too old.\n");
- goto fail_req_hdr_chk;
- }
-
- if (oap_hdr.timestamp > stamp + 1 * BILLION) {
- printf("OAP request header timestamp is too new.\n");
- goto fail_req_hdr_chk;
- }
-
- oap_hdr_fini(&oap_hdr);
-
- TEST_SUCCESS();
-
- return TEST_RC_SUCCESS;
-
- fail_req_hdr_chk:
- oap_hdr_fini(&oap_hdr);
- fail_req_hdr:
- TEST_FAIL();
- return TEST_RC_FAIL;
-}
-
-static int test_oap_hdr_init_fini_data(void)
-
-{
- struct oap_hdr oap_hdr;
- buffer_t data;
- buffer_t ephkey = BUF_INIT;
- uint8_t buf[OAP_ID_SIZE];
- buffer_t id;
- void * pkp = NULL;
- void * pubcrt = NULL;
-
- TEST_START();
-
- random_buffer(buf, OAP_ID_SIZE);
- id.data = buf;
- id.len = OAP_ID_SIZE;
-
- data.len = 100;
- data.data = malloc(data.len);
- if (data.data == NULL) {
- printf("Failed to allocate data buffer.\n");
- goto fail_data;
- }
-
- random_buffer(data.data, data.len);
-
- if (oap_hdr_init(id, pkp, pubcrt, ephkey, data, &oap_hdr) < 0) {
- printf("Failed to create OAP request header.\n");
- goto fail_req_hdr;
- }
-
- if (oap_hdr.hdr.len != OAP_HDR_MIN_SIZE + data.len) {
- printf("OAP request header wrong: %zu < %zu.\n",
- oap_hdr.hdr.len, OAP_HDR_MIN_SIZE + data.len);
- goto fail_req_hdr_sz;
- }
-
- freebuf(data);
- oap_hdr_fini(&oap_hdr);
-
- TEST_SUCCESS();
-
- return TEST_RC_SUCCESS;
-
- fail_req_hdr_sz:
- oap_hdr_fini(&oap_hdr);
- fail_req_hdr:
- freebuf(data);
- fail_data:
- TEST_FAIL();
- return TEST_RC_FAIL;
-}
-
-static int test_oap_hdr_init_fini_signed(void)
-{
- struct oap_hdr oap_hdr;
- buffer_t ephkey = BUF_INIT;
- buffer_t data = BUF_INIT;
- buffer_t sign;
- buffer_t id;
- uint8_t buf[OAP_ID_SIZE];
- void * pkp;
- void * pk;
- void * pubcrt;
- void * pubcrt2;
-
- TEST_START();
-
- random_buffer(buf, OAP_ID_SIZE);
- id.data = buf;
- id.len = OAP_ID_SIZE;
-
- if (crypt_load_privkey_str(pkp_str, &pkp) < 0) {
- printf("Failed to load private key.\n");
- goto fail_pkp;
- }
-
- if (crypt_load_crt_str(crt_str, &pubcrt) < 0) {
- printf("Failed to load public certificate.\n");
- goto fail_pubcrt;
- }
-
- if (oap_hdr_init(id, pkp, pubcrt, ephkey, data, &oap_hdr) < 0) {
- printf("Failed to create OAP request header.\n");
- goto fail_req_hdr;
- }
-
- if (oap_hdr.crt.len == 0) {
- printf("OAP request header has no public certificate.\n");
- goto fail_req_hdr;
- }
-
- if (oap_hdr.sig.len == 0) {
- printf("OAP request header no signature.\n");
- goto fail_req_hdr;
- }
-
- if (crypt_load_crt_der(oap_hdr.crt, &pubcrt2) < 0) {
- printf("Failed to load public certificate from DER.\n");
- goto fail_crt_der;
- }
-
- if (crypt_get_pubkey_crt(pubcrt2, &pk) < 0) {
- printf("Failed to get public key from certificate.\n");
- goto fail_crt_pk;
- }
-
- sign = oap_hdr.hdr;
- sign.len -= (oap_hdr.sig.len + sizeof(uint16_t));
-
- if (auth_verify_sig(pk, sign, oap_hdr.sig) < 0) {
- printf("Failed to verify OAP request header signature.\n");
- goto fail_check_sig;
- }
-
- oap_hdr_fini(&oap_hdr);
-
- crypt_free_crt(pubcrt2);
- crypt_free_crt(pubcrt);
- crypt_free_key(pk);
- crypt_free_key(pkp);
-
- TEST_SUCCESS();
-
- return TEST_RC_SUCCESS;
-
- fail_check_sig:
- crypt_free_key(pk);
- fail_crt_pk:
- crypt_free_crt(pubcrt2);
- fail_crt_der:
- oap_hdr_fini(&oap_hdr);
- fail_req_hdr:
- crypt_free_crt(pubcrt);
- fail_pubcrt:
- crypt_free_key(pkp);
- fail_pkp:
- TEST_FAIL();
- return TEST_RC_FAIL;
-}
-
-int oap_test(int argc,
- char **argv)
-{
- int ret = 0;
-
- (void) argc;
- (void) argv;
-
- ret |= test_oap_hdr_init_fini();
- ret |= test_oap_hdr_init_fini_data();
-#ifdef HAVE_OPENSSL
- ret |= test_oap_hdr_init_fini_signed();
-#else
- (void) test_oap_hdr_init_fini_signed;
-
- ret = TEST_RC_SKIP;
-#endif
- return ret;
-}
diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt
index 14e89976..79263924 100644
--- a/src/lib/CMakeLists.txt
+++ b/src/lib/CMakeLists.txt
@@ -1,263 +1,18 @@
-include_directories(${CMAKE_CURRENT_SOURCE_DIR})
-include_directories(${CMAKE_CURRENT_BINARY_DIR})
-
-include_directories(${CMAKE_SOURCE_DIR}/include)
-include_directories(${CMAKE_BINARY_DIR}/include)
+# Ouroboros libraries build configuration
+# Configuration options are in cmake/config/lib.cmake
protobuf_generate_c(MODEL_PROTO_SRCS MODEL_PROTO_HDRS
- pb/model.proto)
+ "${CMAKE_CURRENT_SOURCE_DIR}/pb/model.proto")
protobuf_generate_c(IPCP_CONFIG_PROTO_SRCS IPCP_CONFIG_PROTO_HDRS
- pb/ipcp_config.proto)
+ "${CMAKE_CURRENT_SOURCE_DIR}/pb/ipcp_config.proto")
protobuf_generate_c(ENROLL_PROTO_SRCS ENROLL_PROTO_HDRS
- pb/enroll.proto)
+ "${CMAKE_CURRENT_SOURCE_DIR}/pb/enroll.proto")
protobuf_generate_c(CEP_PROTO_SRCS CEP_PROTO_HDRS
- pb/cep.proto)
+ "${CMAKE_CURRENT_SOURCE_DIR}/pb/cep.proto")
protobuf_generate_c(IRM_PROTO_SRCS IRM_PROTO_HDRS
- pb/irm.proto)
+ "${CMAKE_CURRENT_SOURCE_DIR}/pb/irm.proto")
protobuf_generate_c(IPCP_PROTO_SRCS IPCP_PROTO_HDRS
- pb/ipcp.proto)
-
-if (NOT APPLE)
- find_library(LIBRT_LIBRARIES rt)
- if (NOT LIBRT_LIBRARIES)
- message(FATAL_ERROR "Could not find librt")
- endif ()
-else ()
- set(LIBRT_LIBRARIES "")
-endif ()
-
-find_library(LIBPTHREAD_LIBRARIES pthread)
-if (NOT LIBPTHREAD_LIBRARIES)
- message(FATAL_ERROR "Could not find libpthread")
-endif ()
-
-include(CheckSymbolExists)
-list(APPEND CMAKE_REQUIRED_DEFINITIONS -D_POSIX_C_SOURCE=200809L)
-list(APPEND CMAKE_REQUIRED_DEFINITIONS -D__XSI_VISIBLE=500)
-list(APPEND CMAKE_REQUIRED_LIBRARIES pthread)
-check_symbol_exists(pthread_mutexattr_setrobust pthread.h HAVE_ROBUST_MUTEX)
-
-if (HAVE_ROBUST_MUTEX)
- set(DISABLE_ROBUST_MUTEXES FALSE CACHE BOOL "Disable robust mutex support")
- if (NOT DISABLE_ROBUST_MUTEXES)
- message(STATUS "Robust mutex support enabled")
- set(HAVE_ROBUST_MUTEX TRUE)
- else ()
- message(STATUS "Robust mutex support disabled by user")
- unset(HAVE_ROBUST_MUTEX)
- endif ()
-else()
- message(STATUS "Robust mutex support not available")
- unset(HAVE_ROBUST_MUTEX)
-endif ()
-
-find_library(FUSE_LIBRARIES fuse QUIET)
-if (FUSE_LIBRARIES)
- #FIXME: Check for version >= 2.6
- set(DISABLE_FUSE FALSE CACHE BOOL "Disable FUSE support")
- if (NOT DISABLE_FUSE)
- message(STATUS "FUSE support enabled")
- set(FUSE_PREFIX "/tmp/ouroboros" CACHE STRING
- "Mountpoint for RIB filesystem")
- set(HAVE_FUSE TRUE CACHE INTERNAL "")
- else ()
- message(STATUS "FUSE support disabled by user")
- unset(HAVE_FUSE CACHE)
- endif ()
-else ()
- message(STATUS "Install FUSE version > 2.6 to enable RIB access")
-endif ()
-
-if (NOT HAVE_FUSE)
- set(FUSE_LIBRARIES "")
- set(FUSE_INCLUDE_DIR "")
-endif ()
-
-mark_as_advanced(FUSE_LIBRARIES)
-
-find_library(LIBGCRYPT_LIBRARIES gcrypt QUIET)
-if (LIBGCRYPT_LIBRARIES)
- find_path(LIBGCRYPT_INCLUDE_DIR gcrypt.h
- HINTS /usr/include /usr/local/include)
- if (LIBGCRYPT_INCLUDE_DIR)
- file(STRINGS ${LIBGCRYPT_INCLUDE_DIR}/gcrypt.h GCSTR
- REGEX "^#define GCRYPT_VERSION ")
- string(REGEX REPLACE "^#define GCRYPT_VERSION \"(.*)\".*$" "\\1"
- GCVER "${GCSTR}")
- if (NOT GCVER VERSION_LESS "1.7.0")
- set(DISABLE_LIBGCRYPT FALSE CACHE BOOL "Disable libgcrypt support")
- if (NOT DISABLE_LIBGCRYPT)
- message(STATUS "libgcrypt support enabled")
- set(HAVE_LIBGCRYPT TRUE CACHE INTERNAL "")
- else ()
- message(STATUS "libgcrypt support disabled by user")
- unset(HAVE_LIBGCRYPT CACHE)
- endif()
- else ()
- message(STATUS "Install version >= \"1.7.0\" to enable libgcrypt support "
- "(found version \"${GCVER}\")")
- endif()
- endif ()
-endif ()
-
-if (NOT HAVE_LIBGCRYPT)
- set(LIBGCRYPT_LIBRARIES "")
- set(LIBGCRYPT_INCLUDE_DIR "")
-endif ()
-
-find_package(OpenSSL QUIET)
-if (OPENSSL_FOUND)
- set(HAVE_OPENSSL_RNG TRUE)
- if (OPENSSL_VERSION VERSION_LESS "1.1.0")
- message(STATUS "Install version >= \"1.1.0\" to enable OpenSSL support "
- "(found version \"${OPENSSL_VERSION}\")")
- else ()
- set(DISABLE_OPENSSL FALSE CACHE BOOL "Disable OpenSSL support")
- if (NOT DISABLE_OPENSSL)
- message(STATUS "OpenSSL support enabled")
- set(HAVE_OPENSSL TRUE CACHE INTERNAL "")
- else()
- message(STATUS "OpenSSL support disabled")
- unset(HAVE_OPENSSL)
- endif()
- endif ()
- set(OPENSSL_SOURCES crypt/openssl.c)
-else()
- message(STATUS "Install openSSL version >= \"1.1.0\" to enable OpenSSL support")
- unset(HAVE_OPENSSL_RNG)
- unset(HAVE_OPENSSL)
- set(OPENSSL_INCLUDE_DIR "")
- set(OPENSSL_LIBRARIES "")
- set(OPENSSL_CRYPTO_LIBRARY "")
- set(OPENSSL_SOURCES "")
-endif ()
-
-if (APPLE OR CMAKE_SYSTEM_NAME STREQUAL "FreeBSD")
- set(SYS_RND_HDR "")
-else ()
- find_path(SYS_RND_HDR sys/random.h PATH /usr/include/ /usr/local/include/)
- if (SYS_RND_HDR)
- message(STATUS "Found sys/random.h in ${SYS_RND_HDR}")
- set(HAVE_SYS_RANDOM TRUE)
- else ()
- set(SYS_RND_HDR "")
- unset(HAVE_SYS_RANDOM)
- endif ()
-endif()
-
-if (NOT ((CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") OR APPLE OR
- HAVE_SYS_RANDOM OR HAVE_OPENSSL_RNG OR HAVE_LIBGCRYPT))
- message(FATAL_ERROR "No secure random generator found, "
- "please install libgcrypt (> 1.7.0) or OpenSSL")
-endif ()
-
-mark_as_advanced(LIBRT_LIBRARIES LIBPTHREAD_LIBRARIES
- LIBGCRYPT_LIBRARIES OPENSSL_LIBRARIES OPENSSL_CRYPTO_LIBRARY
- SYS_RND_INCLUDE_DIR LIBGCRYPT_INCLUDE_DIR SYS_RND_HDR)
-
-set(SHM_BUFFER_SIZE 16384 CACHE STRING
- "Number of blocks in packet buffer, must be a power of 2")
-set(SHM_RBUFF_SIZE 1024 CACHE STRING
- "Number of blocks in rbuff buffer, must be a power of 2")
-set(SYS_MAX_FLOWS 10240 CACHE STRING
- "Maximum number of total flows for this system")
-set(PROG_MAX_FLOWS 4096 CACHE STRING
- "Maximum number of flows in an application")
-set(PROG_RES_FDS 64 CACHE STRING
- "Number of reserved flow descriptors per application")
-set(PROG_MAX_FQUEUES 32 CACHE STRING
- "Maximum number of flow sets per application")
-set(DU_BUFF_HEADSPACE 256 CACHE STRING
- "Bytes of headspace to reserve for future headers")
-set(DU_BUFF_TAILSPACE 32 CACHE STRING
- "Bytes of tailspace to reserve for future tails")
-if (NOT APPLE)
- set(PTHREAD_COND_CLOCK "CLOCK_MONOTONIC" CACHE STRING
- "Clock to use for condition variable timing")
-else ()
- set(PTHREAD_COND_CLOCK "CLOCK_REALTIME" CACHE INTERNAL
- "Clock to use for condition variable timing")
-endif ()
-set(SOCKET_TIMEOUT 500 CACHE STRING
- "Default timeout for responses from IPCPs (ms)")
-set(SHM_PREFIX "ouroboros" CACHE STRING
- "String to prepend to POSIX shared memory filenames")
-set(SHM_RBUFF_PREFIX "/${SHM_PREFIX}.rbuff." CACHE INTERNAL
- "Prefix for rbuff POSIX shared memory filenames")
-set(SHM_LOCKFILE_NAME "/${SHM_PREFIX}.lockfile" CACHE INTERNAL
- "Filename for the POSIX shared memory lockfile")
-set(SHM_FLOW_SET_PREFIX "/${SHM_PREFIX}.set." CACHE INTERNAL
- "Prefix for the POSIX shared memory flow set")
-set(SHM_RDRB_NAME "/${SHM_PREFIX}.rdrb" CACHE INTERNAL
- "Name for the main POSIX shared memory buffer")
-set(SHM_RDRB_BLOCK_SIZE "sysconf(_SC_PAGESIZE)" CACHE STRING
- "Packet buffer block size, multiple of pagesize for performance")
-set(SHM_RDRB_MULTI_BLOCK TRUE CACHE BOOL
- "Packet buffer multiblock packet support")
-set(SHM_RBUFF_LOCKLESS FALSE CACHE BOOL
- "Enable shared memory lockless rbuff support")
-set(QOS_DISABLE_CRC TRUE CACHE BOOL
- "Ignores ber setting on all QoS cubes")
-set(DELTA_T_MPL 60 CACHE STRING
- "Maximum packet lifetime (s)")
-set(DELTA_T_ACK 10 CACHE STRING
- "Maximum time to acknowledge a packet (s)")
-set(DELTA_T_RTX 120 CACHE STRING
- "Maximum time to retransmit a packet (s)")
-set(FRCT_REORDER_QUEUE_SIZE 256 CACHE STRING
- "Size of the reordering queue, must be a power of 2")
-set(FRCT_START_WINDOW 64 CACHE STRING
- "Start window, must be a power of 2")
-set(FRCT_LINUX_RTT_ESTIMATOR TRUE CACHE BOOL
- "Use Linux RTT estimator formula instead of the TCP RFC formula")
-set(FRCT_RTO_MDEV_MULTIPLIER 2 CACHE STRING
- "Multiplier for deviation term in the RTO: RTO = sRTT + (mdev << X)")
-set(FRCT_RTO_INC_FACTOR 0 CACHE STRING
- "Divisor for RTO increase after timeout: RTO += RTX >> X, 0: Karn/Partridge")
-set(FRCT_RTO_MIN 250 CACHE STRING
- "Minimum Retransmission Timeout (RTO) for FRCT (us)")
-set(FRCT_TICK_TIME 5000 CACHE STRING
- "Tick time for FRCT activity (retransmission, acknowledgments) (us)")
-set(RXM_BUFFER_ON_HEAP FALSE CACHE BOOL
- "Store packets for retransmission on the heap instead of in packet buffer")
-set(RXM_BLOCKING TRUE CACHE BOOL
- "Use blocking writes for retransmission")
-set(RXM_MIN_RESOLUTION 20 CACHE STRING
- "Minimum retransmission delay (ns), as a power to 2")
-set(RXM_WHEEL_MULTIPLIER 4 CACHE STRING
- "Factor for retransmission wheel levels as a power to 2")
-set(RXM_WHEEL_LEVELS 3 CACHE STRING
- "Number of levels in the retransmission wheel")
-set(RXM_WHEEL_SLOTS_PER_LEVEL 256 CACHE STRING
- "Number of slots per level in the retransmission wheel, must be a power of 2")
-set(ACK_WHEEL_SLOTS 256 CACHE STRING
- "Number of slots in the acknowledgment wheel, must be a power of 2")
-set(ACK_WHEEL_RESOLUTION 18 CACHE STRING
- "Minimum acknowledgment delay (ns), as a power to 2")
-set(TPM_DEBUG_REPORT_INTERVAL 0 CACHE STRING
- "Interval at wich the TPM will report long running threads (s), 0 disables")
-set(TPM_DEBUG_ABORT_TIMEOUT 0 CACHE STRING
- "TPM abort process after a thread reaches this timeout (s), 0 disables")
-
-if (HAVE_FUSE)
- set(PROC_FLOW_STATS TRUE CACHE BOOL
- "Enable flow statistics tracking for application flows")
- if (PROC_FLOW_STATS)
- message(STATUS "Application flow statistics enabled")
- else ()
- message(STATUS "Application flow statistics disabled")
- endif ()
-endif ()
-
-set(SOURCE_FILES_DEV
- # Add source files here
- cep.c
- dev.c
- )
-
-set(SOURCE_FILES_IRM
- irm.c
-)
+ "${CMAKE_CURRENT_SOURCE_DIR}/pb/ipcp.proto")
set(SOURCE_FILES_COMMON
bitmap.c
@@ -265,7 +20,6 @@ set(SOURCE_FILES_COMMON
crc32.c
crypt.c
hash.c
- list.c
lockfile.c
logs.c
md5.c
@@ -277,62 +31,129 @@ set(SOURCE_FILES_COMMON
serdes-irm.c
serdes-oep.c
sha3.c
- shm_flow_set.c
- shm_rbuff.c
- shm_rdrbuff.c
+ ssm/flow_set.c
+ ssm/rbuff.c
+ ssm/pool.c
sockets.c
tpm.c
utils.c
)
-configure_file("${CMAKE_CURRENT_SOURCE_DIR}/config.h.in"
- "${CMAKE_CURRENT_BINARY_DIR}/config.h" @ONLY)
-
-add_library(ouroboros-common SHARED ${SOURCE_FILES_COMMON} ${IRM_PROTO_SRCS}
- ${IPCP_PROTO_SRCS} ${IPCP_CONFIG_PROTO_SRCS} ${MODEL_PROTO_SRCS}
- ${ENROLL_PROTO_SRCS} ${OPENSSL_SOURCES})
-
-add_library(ouroboros-dev SHARED ${SOURCE_FILES_DEV} ${CEP_PROTO_SRCS})
+if(HAVE_OPENSSL)
+ list(APPEND SOURCE_FILES_COMMON crypt/openssl.c)
+endif()
-add_library(ouroboros-irm SHARED ${SOURCE_FILES_IRM})
+add_library(ouroboros-common SHARED
+ ${SOURCE_FILES_COMMON}
+ ${IRM_PROTO_SRCS}
+ ${IPCP_PROTO_SRCS}
+ ${IPCP_CONFIG_PROTO_SRCS}
+ ${MODEL_PROTO_SRCS}
+ ${ENROLL_PROTO_SRCS})
set_target_properties(ouroboros-common PROPERTIES
VERSION ${PACKAGE_VERSION}
SOVERSION ${PACKAGE_VERSION_MAJOR}.${PACKAGE_VERSION_MINOR})
+
+ouroboros_target_debug_definitions(ouroboros-common)
+
+target_include_directories(ouroboros-common
+ PUBLIC
+ $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
+ $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}/include>
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
+ $<BUILD_INTERFACE:${CMAKE_BINARY_DIR}>
+ $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
+ PRIVATE
+ ${SYS_RND_HDR}
+ ${APPLE_INCLUDE_DIRS})
+
+target_link_libraries(ouroboros-common
+ PRIVATE
+ ${LIBRT_LIBRARIES}
+ Threads::Threads
+ PUBLIC
+ ProtobufC::ProtobufC)
+
+if(HAVE_OPENSSL)
+ target_link_libraries(ouroboros-common PUBLIC OpenSSL::Crypto)
+endif()
+
+if(HAVE_LIBGCRYPT)
+ target_link_libraries(ouroboros-common PUBLIC Gcrypt::Gcrypt)
+endif()
+
+if(HAVE_FUSE)
+ target_link_libraries(ouroboros-common PRIVATE Fuse::Fuse)
+endif()
+
+install(TARGETS ouroboros-common
+ EXPORT OuroborosTargets
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
+
+set(SOURCE_FILES_DEV
+ cep.c
+ dev.c
+)
+
+add_library(ouroboros-dev SHARED
+ ${SOURCE_FILES_DEV}
+ ${CEP_PROTO_SRCS})
+
set_target_properties(ouroboros-dev PROPERTIES
VERSION ${PACKAGE_VERSION}
SOVERSION ${PACKAGE_VERSION_MAJOR}.${PACKAGE_VERSION_MINOR})
+
+ouroboros_target_debug_definitions(ouroboros-dev)
+
+target_include_directories(ouroboros-dev
+ PUBLIC
+ $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
+ $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
+ PRIVATE
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CMAKE_BINARY_DIR}
+ ${SYS_RND_HDR})
+
+target_link_libraries(ouroboros-dev PUBLIC ouroboros-common)
+
+install(TARGETS ouroboros-dev
+ EXPORT OuroborosTargets
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
+
+add_library(ouroboros-irm SHARED irm.c)
+
set_target_properties(ouroboros-irm PROPERTIES
VERSION ${PACKAGE_VERSION}
SOVERSION ${PACKAGE_VERSION_MAJOR}.${PACKAGE_VERSION_MINOR})
-include(AddCompileFlags)
-if (CMAKE_BUILD_TYPE MATCHES "Debug*")
- add_compile_flags(ouroboros-common -DCONFIG_OUROBOROS_DEBUG)
- add_compile_flags(ouroboros-dev -DCONFIG_OUROBOROS_DEBUG)
- add_compile_flags(ouroboros-irm -DCONFIG_OUROBOROS_DEBUG)
-endif ()
-
-target_link_libraries(ouroboros-common ${LIBRT_LIBRARIES}
- ${LIBPTHREAD_LIBRARIES} ${PROTOBUF_C_LIBRARY} ${OPENSSL_CRYPTO_LIBRARY}
- ${LIBGCRYPT_LIBRARIES} ${FUSE_LIBRARIES})
+ouroboros_target_debug_definitions(ouroboros-irm)
-target_link_libraries(ouroboros-dev ouroboros-common)
-target_link_libraries(ouroboros-irm ouroboros-common)
+target_include_directories(ouroboros-irm
+ PUBLIC
+ $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/include>
+ $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
+ PRIVATE
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${CMAKE_BINARY_DIR}
+ ${SYS_RND_HDR})
-install(TARGETS ouroboros-common LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
-install(TARGETS ouroboros-dev LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
-install(TARGETS ouroboros-irm LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
+target_link_libraries(ouroboros-irm PUBLIC ouroboros-common)
-target_include_directories(ouroboros-common PUBLIC ${CMAKE_CURRENT_BINARY_DIR}
- ${SYS_RND_HDR} ${LIBGCRYPT_INCLUDE_DIR} ${OPENSSL_INCLUDE_DIR})
+install(TARGETS ouroboros-irm
+ EXPORT OuroborosTargets
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
-target_include_directories(ouroboros-dev PUBLIC ${CMAKE_CURRENT_BINARY_DIR}
- ${SYS_RND_HDR} ${LIBGCRYPT_INCLUDE_DIR} ${OPENSSL_INCLUDE_DIR})
+configure_file("${CMAKE_CURRENT_SOURCE_DIR}/config.h.in"
+ "${CMAKE_CURRENT_BINARY_DIR}/config.h" @ONLY)
-target_include_directories(ouroboros-irm PUBLIC ${CMAKE_CURRENT_BINARY_DIR}
- ${SYS_RND_HDR} ${LIBGCRYPT_INCLUDE_DIR} ${OPENSSL_INCLUDE_DIR})
+configure_file("${CMAKE_CURRENT_SOURCE_DIR}/ssm/ssm.h.in"
+ "${CMAKE_CURRENT_BINARY_DIR}/ssm.h" @ONLY)
if(BUILD_TESTS)
add_subdirectory(tests)
-endif ()
+ add_subdirectory(ssm/tests)
+endif()
diff --git a/src/lib/bitmap.c b/src/lib/bitmap.c
index b0840c44..05cf4030 100644
--- a/src/lib/bitmap.c
+++ b/src/lib/bitmap.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Bitmap implementation
*
diff --git a/src/lib/btree.c b/src/lib/btree.c
index 1af94b73..37ec5e52 100644
--- a/src/lib/btree.c
+++ b/src/lib/btree.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* B-trees
*
diff --git a/src/lib/cep.c b/src/lib/cep.c
index ba238023..e953e2d9 100644
--- a/src/lib/cep.c
+++ b/src/lib/cep.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* The Ouroboros Connection Establishment Protocol
*
diff --git a/src/lib/config.h.in b/src/lib/config.h.in
index 8326a332..08e9baf6 100644
--- a/src/lib/config.h.in
+++ b/src/lib/config.h.in
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Ouroboros library configuration
*
@@ -21,27 +21,24 @@
*/
#cmakedefine HAVE_SYS_RANDOM
+#cmakedefine HAVE_EXPLICIT_BZERO
#cmakedefine HAVE_LIBGCRYPT
#cmakedefine HAVE_OPENSSL
-
#ifdef HAVE_OPENSSL
+#cmakedefine HAVE_OPENSSL_ML_KEM
+#cmakedefine HAVE_OPENSSL_ML_DSA
+#cmakedefine HAVE_OPENSSL_SLH_DSA
#define HAVE_ENCRYPTION
+#define SECMEM_GUARD @SECMEM_GUARD@
#endif
+#define PROC_SECMEM_MAX @PROC_SECMEM_MAX@
#define SYS_MAX_FLOWS @SYS_MAX_FLOWS@
-#cmakedefine SHM_RBUFF_LOCKLESS
-#cmakedefine SHM_RDRB_MULTI_BLOCK
#cmakedefine QOS_DISABLE_CRC
#cmakedefine HAVE_OPENSSL_RNG
-#define SHM_RBUFF_PREFIX "@SHM_RBUFF_PREFIX@"
#define SHM_LOCKFILE_NAME "@SHM_LOCKFILE_NAME@"
-#define SHM_FLOW_SET_PREFIX "@SHM_FLOW_SET_PREFIX@"
-#define SHM_RDRB_NAME "@SHM_RDRB_NAME@"
-#define SHM_RDRB_BLOCK_SIZE @SHM_RDRB_BLOCK_SIZE@
-#define SHM_BUFFER_SIZE @SHM_BUFFER_SIZE@
-#define SHM_RBUFF_SIZE @SHM_RBUFF_SIZE@
#define FLOW_ALLOC_TIMEOUT @FLOW_ALLOC_TIMEOUT@
#define TPM_DEBUG_REPORT_INTERVAL @TPM_DEBUG_REPORT_INTERVAL@
@@ -69,9 +66,6 @@
#define PROG_RES_FDS @PROG_RES_FDS@
#define PROG_MAX_FQUEUES @PROG_MAX_FQUEUES@
-#define DU_BUFF_HEADSPACE @DU_BUFF_HEADSPACE@
-#define DU_BUFF_TAILSPACE @DU_BUFF_TAILSPACE@
-
/* Default Delta-t parameters */
#cmakedefine FRCT_LINUX_RTT_ESTIMATOR
#define DELT_A (@DELTA_T_ACK@) /* ns */
@@ -96,3 +90,5 @@
#define ACKQ_SLOTS (@ACK_WHEEL_SLOTS@)
#define ACKQ_RES (@ACK_WHEEL_RESOLUTION@) /* 2^N ns */
+
+#define KEY_ROTATION_BIT (@KEY_ROTATION_BIT@) /* Bit for key rotation */
diff --git a/src/lib/crc32.c b/src/lib/crc32.c
index f369ad20..0fdb62b1 100644
--- a/src/lib/crc32.c
+++ b/src/lib/crc32.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* 32-bit Cyclic Redundancy Check
*
diff --git a/src/lib/crypt.c b/src/lib/crypt.c
index 8b18140e..cd3421dd 100644
--- a/src/lib/crypt.c
+++ b/src/lib/crypt.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Cryptographic operations
*
@@ -20,34 +20,253 @@
* Foundation, Inc., http://www.fsf.org/about/contact/.
*/
+#if defined(__linux__) || defined(__CYGWIN__)
+#define _DEFAULT_SOURCE
+#endif
+
#include <config.h>
-#include <ouroboros/crypt.h>
#include <ouroboros/errno.h>
+#include <ouroboros/random.h>
+#include <ouroboros/crypt.h>
+
#ifdef HAVE_OPENSSL
- #include "crypt/openssl.h"
-#endif /* HAVE_OPENSSL */
+#include <openssl/evp.h>
+#include "crypt/openssl.h"
+#endif
#include <assert.h>
+#include <stdio.h>
#include <string.h>
+#include <sys/stat.h>
+
+struct nid_map {
+ uint16_t nid;
+ const char * name;
+};
+
+static const struct nid_map cipher_nid_map[] = {
+ {NID_aes_128_gcm, "aes-128-gcm"},
+ {NID_aes_192_gcm, "aes-192-gcm"},
+ {NID_aes_256_gcm, "aes-256-gcm"},
+ {NID_chacha20_poly1305, "chacha20-poly1305"},
+ {NID_aes_128_ctr, "aes-128-ctr"},
+ {NID_aes_192_ctr, "aes-192-ctr"},
+ {NID_aes_256_ctr, "aes-256-ctr"},
+ {NID_undef, NULL}
+};
+
+/* Ordered in strength preference, lowest first */
+const uint16_t crypt_supported_nids[] = {
+#ifdef HAVE_OPENSSL
+ NID_aes_128_ctr,
+ NID_aes_192_ctr,
+ NID_aes_256_ctr,
+ NID_aes_128_gcm,
+ NID_aes_192_gcm,
+ NID_aes_256_gcm,
+ NID_chacha20_poly1305,
+#endif
+ NID_undef
+};
+
+static const struct nid_map kex_nid_map[] = {
+ {NID_X9_62_prime256v1, "prime256v1"},
+ {NID_secp384r1, "secp384r1"},
+ {NID_secp521r1, "secp521r1"},
+ {NID_X25519, "X25519"},
+ {NID_X448, "X448"},
+ {NID_ffdhe2048, "ffdhe2048"},
+ {NID_ffdhe3072, "ffdhe3072"},
+ {NID_ffdhe4096, "ffdhe4096"},
+ {NID_MLKEM512, "ML-KEM-512"},
+ {NID_MLKEM768, "ML-KEM-768"},
+ {NID_MLKEM1024, "ML-KEM-1024"},
+ {NID_X25519MLKEM768, "X25519MLKEM768"},
+ {NID_X448MLKEM1024, "X448MLKEM1024"},
+ {NID_undef, NULL}
+};
+
+/* Ordered in strength preference, lowest first */
+const uint16_t kex_supported_nids[] = {
+#ifdef HAVE_OPENSSL
+ NID_ffdhe2048,
+ NID_X9_62_prime256v1,
+ NID_X25519,
+ NID_ffdhe3072,
+ NID_secp384r1,
+ NID_ffdhe4096,
+ NID_X448,
+ NID_secp521r1,
+#ifdef HAVE_OPENSSL_ML_KEM
+ NID_MLKEM512,
+ NID_MLKEM768,
+ NID_MLKEM1024,
+ NID_X25519MLKEM768,
+ NID_X448MLKEM1024,
+#endif
+#endif
+ NID_undef
+};
+
+static const struct nid_map md_nid_map[] = {
+ {NID_sha256, "sha256"},
+ {NID_sha384, "sha384"},
+ {NID_sha512, "sha512"},
+ {NID_sha3_256, "sha3-256"},
+ {NID_sha3_384, "sha3-384"},
+ {NID_sha3_512, "sha3-512"},
+ {NID_blake2b512, "blake2b512"},
+ {NID_blake2s256, "blake2s256"},
+ {NID_undef, NULL}
+};
+
+/* Ordered in strength preference, lowest first */
+const uint16_t md_supported_nids[] = {
+#ifdef HAVE_OPENSSL
+ NID_blake2s256,
+ NID_sha256,
+ NID_sha3_256,
+ NID_sha384,
+ NID_sha3_384,
+ NID_blake2b512,
+ NID_sha512,
+ NID_sha3_512,
+#endif
+ NID_undef
+};
struct crypt_ctx {
- void * ctx;
- uint8_t key[SYMMKEYSZ];
+ void * ctx; /* Encryption context */
};
struct auth_ctx {
void * store;
};
-int crypt_dh_pkp_create(void ** pkp,
- uint8_t * pk)
+static int parse_kex_value(const char * value,
+ struct sec_config * cfg)
+{
+ SET_KEX_ALGO(cfg, value);
+ if (cfg->x.nid == NID_undef)
+ return -ENOTSUP;
+
+ return 0;
+}
+
+/* not in header, but non-static for unit testing */
+int parse_sec_config(struct sec_config * cfg,
+ FILE * fp)
+{
+ char line[256];
+ char * equals;
+ char * key;
+ char * value;
+
+ assert(cfg != NULL);
+ assert(fp != NULL);
+
+ /* Set defaults */
+ SET_KEX_ALGO_NID(cfg, NID_X9_62_prime256v1);
+ cfg->x.mode = KEM_MODE_SERVER_ENCAP;
+ SET_KEX_KDF_NID(cfg, NID_sha256);
+ SET_KEX_CIPHER_NID(cfg, NID_aes_256_gcm);
+ SET_KEX_DIGEST_NID(cfg, NID_sha256);
+
+ while (fgets(line, sizeof(line), fp) != NULL) {
+ char * trimmed;
+
+ /* Skip comments and empty lines */
+ if (line[0] == '#' || line[0] == '\n')
+ continue;
+
+ /* Check for 'none' keyword */
+ trimmed = trim_whitespace(line);
+ if (strcmp(trimmed, "none") == 0) {
+ memset(cfg, 0, sizeof(*cfg));
+ return 0;
+ }
+
+ /* Find the = separator */
+ equals = strchr(line, '=');
+ if (equals == NULL)
+ continue;
+
+ /* Split into key and value */
+ *equals = '\0';
+ key = trim_whitespace(line);
+ value = trim_whitespace(equals + 1);
+
+ /* Parse key exchange field */
+ if (strcmp(key, "kex") == 0) {
+ if (parse_kex_value(value, cfg) < 0)
+ return -EINVAL;
+ } else if (strcmp(key, "cipher") == 0) {
+ SET_KEX_CIPHER(cfg, value);
+ if (cfg->c.nid == NID_undef)
+ return -EINVAL;
+ } else if (strcmp(key, "kdf") == 0) {
+ SET_KEX_KDF(cfg, value);
+ if (cfg->k.nid == NID_undef)
+ return -EINVAL;
+ } else if (strcmp(key, "digest") == 0) {
+ SET_KEX_DIGEST(cfg, value);
+ if (cfg->d.nid == NID_undef)
+ return -EINVAL;
+ } else if (strcmp(key, "kem_mode") == 0) {
+ if (strcmp(value, "server") == 0) {
+ cfg->x.mode = KEM_MODE_SERVER_ENCAP;
+ } else if (strcmp(value, "client") == 0) {
+ cfg->x.mode = KEM_MODE_CLIENT_ENCAP;
+ } else {
+ return -EINVAL;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/* Parse key exchange config from file */
+int load_sec_config_file(struct sec_config * cfg,
+ const char * path)
+{
+ FILE * fp;
+ int ret;
+
+ assert(cfg != NULL);
+ assert(path != NULL);
+
+ fp = fopen(path, "r");
+ if (fp == NULL) {
+ /* File doesn't exist - disable encryption */
+ CLEAR_KEX_ALGO(cfg);
+ return 0;
+ }
+
+ ret = parse_sec_config(cfg, fp);
+
+ fclose(fp);
+
+ return ret;
+}
+
+int kex_pkp_create(struct sec_config * cfg,
+ void ** pkp,
+ uint8_t * pk)
{
#ifdef HAVE_OPENSSL
+ assert(cfg != NULL);
assert(pkp != NULL);
+
*pkp = NULL;
- return openssl_ecdh_pkp_create(pkp, pk);
+
+ if (cfg->x.str == NULL || kex_validate_nid(cfg->x.nid) < 0)
+ return -ENOTSUP;
+
+ return openssl_pkp_create(cfg->x.str, (EVP_PKEY **) pkp, pk);
#else
+ (void) cfg;
(void) pkp;
(void) pk;
@@ -57,12 +276,12 @@ int crypt_dh_pkp_create(void ** pkp,
#endif
}
-void crypt_dh_pkp_destroy(void * pkp)
+void kex_pkp_destroy(void * pkp)
{
if (pkp == NULL)
return;
#ifdef HAVE_OPENSSL
- openssl_ecdh_pkp_destroy(pkp);
+ openssl_pkp_destroy((EVP_PKEY *) pkp);
#else
(void) pkp;
@@ -70,12 +289,18 @@ void crypt_dh_pkp_destroy(void * pkp)
#endif
}
-int crypt_dh_derive(void * pkp,
- buffer_t pk,
- uint8_t * s)
+int kex_dhe_derive(struct sec_config * cfg,
+ void * pkp,
+ buffer_t pk,
+ uint8_t * s)
{
+ assert(cfg != NULL);
+
+ if (kex_validate_nid(cfg->x.nid) < 0)
+ return -ENOTSUP;
+
#ifdef HAVE_OPENSSL
- return openssl_ecdh_derive(pkp, pk, s);
+ return openssl_dhe_derive((EVP_PKEY *) pkp, pk, cfg->k.nid, s);
#else
(void) pkp;
(void) pk;
@@ -86,6 +311,289 @@ int crypt_dh_derive(void * pkp,
#endif
}
+ssize_t kex_kem_encap(buffer_t pk,
+ uint8_t * ct,
+ int kdf,
+ uint8_t * s)
+{
+#ifdef HAVE_OPENSSL
+ return openssl_kem_encap(pk, ct, kdf, s);
+#else
+ (void) pk;
+ (void) ct;
+ (void) kdf;
+
+ memset(s, 0, SYMMKEYSZ);
+
+ return -ECRYPT;
+#endif
+}
+
+ssize_t kex_kem_encap_raw(buffer_t pk,
+ uint8_t * ct,
+ int kdf,
+ uint8_t * s)
+{
+#ifdef HAVE_OPENSSL
+ return openssl_kem_encap_raw(pk, ct, kdf, s);
+#else
+ (void) pk;
+ (void) ct;
+ (void) kdf;
+
+ memset(s, 0, SYMMKEYSZ);
+
+ return -ECRYPT;
+#endif
+}
+
+int kex_kem_decap(void * pkp,
+ buffer_t ct,
+ int kdf,
+ uint8_t * s)
+{
+#ifdef HAVE_OPENSSL
+ return openssl_kem_decap((EVP_PKEY *) pkp, ct, kdf, s);
+#else
+ (void) pkp;
+ (void) ct;
+ (void) kdf;
+
+ memset(s, 0, SYMMKEYSZ);
+
+ return -ECRYPT;
+#endif
+}
+
+int kex_get_algo_from_pk_der(buffer_t pk,
+ char * algo)
+{
+#ifdef HAVE_OPENSSL
+ return openssl_get_algo_from_pk_der(pk, algo);
+#else
+ (void) pk;
+ algo[0] = '\0';
+
+ return -ECRYPT;
+#endif
+}
+
+int kex_get_algo_from_pk_raw(buffer_t pk,
+ char * algo)
+{
+#ifdef HAVE_OPENSSL
+ return openssl_get_algo_from_pk_raw(pk, algo);
+#else
+ (void) pk;
+ algo[0] = '\0';
+
+ return -ECRYPT;
+#endif
+}
+
+int kex_validate_algo(const char * algo)
+{
+ if (algo == NULL)
+ return -EINVAL;
+
+ /* Use NID validation instead of string array */
+ return kex_validate_nid(kex_str_to_nid(algo));
+}
+
+int crypt_validate_nid(int nid)
+{
+ const struct nid_map * p;
+
+ if (nid == NID_undef)
+ return -EINVAL;
+
+ for (p = cipher_nid_map; p->name != NULL; p++) {
+ if (p->nid == nid)
+ return 0;
+ }
+
+ return -ENOTSUP;
+}
+
+
+const char * crypt_nid_to_str(uint16_t nid)
+{
+ const struct nid_map * p;
+
+ for (p = cipher_nid_map; p->name != NULL; p++) {
+ if (p->nid == nid)
+ return p->name;
+ }
+
+ return NULL;
+}
+
+uint16_t crypt_str_to_nid(const char * cipher)
+{
+ const struct nid_map * p;
+
+ if (cipher == NULL)
+ return NID_undef;
+
+ /* fast, check if cipher pointer is in the map */
+ for (p = cipher_nid_map; p->name != NULL; p++) {
+ if (cipher == p->name)
+ return p->nid;
+ }
+
+ for (p = cipher_nid_map; p->name != NULL; p++) {
+ if (strcmp(p->name, cipher) == 0)
+ return p->nid;
+ }
+
+ return NID_undef;
+}
+
+const char * kex_nid_to_str(uint16_t nid)
+{
+ const struct nid_map * p;
+
+ for (p = kex_nid_map; p->name != NULL; p++) {
+ if (p->nid == nid)
+ return p->name;
+ }
+
+ return NULL;
+}
+
+uint16_t kex_str_to_nid(const char * algo)
+{
+ const struct nid_map * p;
+
+ if (algo == NULL)
+ return NID_undef;
+
+ /* Fast path: check if algo pointer is in the map */
+ for (p = kex_nid_map; p->name != NULL; p++) {
+ if (algo == p->name)
+ return p->nid;
+ }
+
+ /* Slow path: string comparison */
+ for (p = kex_nid_map; p->name != NULL; p++) {
+ if (strcmp(p->name, algo) == 0)
+ return p->nid;
+ }
+
+ return NID_undef;
+}
+
+int kex_validate_nid(int nid)
+{
+ const struct nid_map * p;
+
+ if (nid == NID_undef)
+ return -EINVAL;
+
+ for (p = kex_nid_map; p->name != NULL; p++) {
+ if (p->nid == nid)
+ return 0;
+ }
+
+ return -ENOTSUP;
+}
+
+const char * md_nid_to_str(uint16_t nid)
+{
+ const struct nid_map * p;
+
+ for (p = md_nid_map; p->name != NULL; p++) {
+ if (p->nid == nid)
+ return p->name;
+ }
+
+ return NULL;
+}
+
+uint16_t md_str_to_nid(const char * kdf)
+{
+ const struct nid_map * p;
+
+ if (kdf == NULL)
+ return NID_undef;
+
+ /* Fast path: check if kdf pointer is in the map */
+ for (p = md_nid_map; p->name != NULL; p++) {
+ if (kdf == p->name)
+ return p->nid;
+ }
+
+ /* Slow path: string comparison */
+ for (p = md_nid_map; p->name != NULL; p++) {
+ if (strcmp(p->name, kdf) == 0)
+ return p->nid;
+ }
+
+ return NID_undef;
+}
+
+int md_validate_nid(int nid)
+{
+ const struct nid_map * p;
+
+ if (nid == NID_undef)
+ return -EINVAL;
+
+ for (p = md_nid_map; p->name != NULL; p++) {
+ if (p->nid == nid)
+ return 0;
+ }
+
+ return -ENOTSUP;
+}
+
+int crypt_cipher_rank(int nid)
+{
+ int i;
+
+ if (nid == NID_undef)
+ return 0;
+
+ for (i = 0; crypt_supported_nids[i] != NID_undef; i++) {
+ if ((int) crypt_supported_nids[i] == nid)
+ return i + 1;
+ }
+
+ return -1;
+}
+
+int crypt_kdf_rank(int nid)
+{
+ int i;
+
+ if (nid == NID_undef)
+ return 0;
+
+ for (i = 0; md_supported_nids[i] != NID_undef; i++) {
+ if ((int) md_supported_nids[i] == nid)
+ return i + 1;
+ }
+
+ return -1;
+}
+
+int crypt_kex_rank(int nid)
+{
+ int i;
+
+ if (nid == NID_undef)
+ return 0;
+
+ for (i = 0; kex_supported_nids[i] != NID_undef; i++) {
+ if ((int) kex_supported_nids[i] == nid)
+ return i + 1;
+ }
+
+ return -1;
+}
+
+/* Hash length now returned by md_digest() */
+
int crypt_encrypt(struct crypt_ctx * ctx,
buffer_t in,
buffer_t * out)
@@ -94,7 +602,7 @@ int crypt_encrypt(struct crypt_ctx * ctx,
assert(ctx->ctx != NULL);
#ifdef HAVE_OPENSSL
- return openssl_encrypt(ctx->ctx, ctx->key, in, out);
+ return openssl_encrypt(ctx->ctx, in, out);
#else
(void) ctx;
(void) in;
@@ -112,7 +620,7 @@ int crypt_decrypt(struct crypt_ctx * ctx,
assert(ctx->ctx != NULL);
#ifdef HAVE_OPENSSL
- return openssl_decrypt(ctx->ctx, ctx->key, in, out);
+ return openssl_decrypt(ctx->ctx, in, out);
#else
(void) ctx;
(void) in;
@@ -122,20 +630,21 @@ int crypt_decrypt(struct crypt_ctx * ctx,
#endif
}
-struct crypt_ctx * crypt_create_ctx(const uint8_t * key)
+struct crypt_ctx * crypt_create_ctx(struct crypt_sk * sk)
{
struct crypt_ctx * crypt;
+ if (crypt_validate_nid(sk->nid) != 0)
+ return NULL;
+
crypt = malloc(sizeof(*crypt));
if (crypt == NULL)
goto fail_crypt;
memset(crypt, 0, sizeof(*crypt));
- if (key != NULL)
- memcpy(crypt->key, key, SYMMKEYSZ);
#ifdef HAVE_OPENSSL
- crypt->ctx=openssl_crypt_create_ctx();
+ crypt->ctx = openssl_crypt_create_ctx(sk);
if (crypt->ctx == NULL)
goto fail_ctx;
#endif
@@ -162,6 +671,34 @@ void crypt_destroy_ctx(struct crypt_ctx * crypt)
free(crypt);
}
+int crypt_get_ivsz(struct crypt_ctx * ctx)
+{
+ if (ctx == NULL)
+ return -EINVAL;
+
+#ifdef HAVE_OPENSSL
+ assert(ctx->ctx != NULL);
+ return openssl_crypt_get_ivsz(ctx->ctx);
+#else
+ assert(ctx->ctx == NULL);
+ return -ENOTSUP;
+#endif
+}
+
+int crypt_get_tagsz(struct crypt_ctx * ctx)
+{
+ if (ctx == NULL)
+ return -EINVAL;
+
+#ifdef HAVE_OPENSSL
+ assert(ctx->ctx != NULL);
+ return openssl_crypt_get_tagsz(ctx->ctx);
+#else
+ assert(ctx->ctx == NULL);
+ return -ENOTSUP;
+#endif
+}
+
int crypt_load_privkey_file(const char * path,
void ** key)
{
@@ -204,11 +741,72 @@ int crypt_load_pubkey_str(const char * str,
#endif
}
+int crypt_load_pubkey_file(const char * path,
+ void ** key)
+{
+ *key = NULL;
+
+#ifdef HAVE_OPENSSL
+ return openssl_load_pubkey_file(path, key);
+#else
+ (void) path;
+
+ return 0;
+#endif
+}
+
+int crypt_load_pubkey_file_to_der(const char * path,
+ buffer_t * buf)
+{
+ assert(buf != NULL);
+
+#ifdef HAVE_OPENSSL
+ return openssl_load_pubkey_file_to_der(path, buf);
+#else
+ (void) path;
+
+ buf->data = NULL;
+ buf->len = 0;
+ return 0;
+#endif
+}
+
+int crypt_load_pubkey_raw_file(const char * path,
+ buffer_t * buf)
+{
+ assert(buf != NULL);
+
+#ifdef HAVE_OPENSSL
+ return openssl_load_pubkey_raw_file(path, buf);
+#else
+ (void) path;
+
+ buf->data = NULL;
+ buf->len = 0;
+ return 0;
+#endif
+}
+
+int crypt_load_privkey_raw_file(const char * path,
+ void ** key)
+{
+ *key = NULL;
+
+#ifdef HAVE_OPENSSL
+ return openssl_load_privkey_raw_file(path, key);
+#else
+ (void) path;
+
+ return 0;
+#endif
+}
+
int crypt_cmp_key(const void * key1,
const void * key2)
{
#ifdef HAVE_OPENSSL
- return openssl_cmp_key(key1, key2);
+ return openssl_cmp_key((const EVP_PKEY *) key1,
+ (const EVP_PKEY *) key2);
#else
(void) key1;
(void) key2;
@@ -223,7 +821,7 @@ void crypt_free_key(void * key)
return;
#ifdef HAVE_OPENSSL
- openssl_free_key(key);
+ openssl_free_key((EVP_PKEY *) key);
#endif
}
@@ -343,6 +941,19 @@ int crypt_check_crt_name(void * crt,
#endif
}
+int crypt_get_crt_name(void * crt,
+ char * name)
+{
+#ifdef HAVE_OPENSSL
+ return openssl_get_crt_name(crt, name);
+#else
+ (void) crt;
+ (void) name;
+
+ return 0;
+#endif
+}
+
struct auth_ctx * auth_create_ctx(void)
{
struct auth_ctx * ctx;
@@ -406,13 +1017,15 @@ int auth_verify_crt(struct auth_ctx * ctx,
}
int auth_sign(void * pkp,
+ int md_nid,
buffer_t msg,
buffer_t * sig)
{
#ifdef HAVE_OPENSSL
- return openssl_sign(pkp, msg, sig);
+ return openssl_sign((EVP_PKEY *) pkp, md_nid, msg, sig);
#else
(void) pkp;
+ (void) md_nid;
(void) msg;
(void) sig;
@@ -423,16 +1036,104 @@ int auth_sign(void * pkp,
}
int auth_verify_sig(void * pk,
+ int md_nid,
buffer_t msg,
buffer_t sig)
{
#ifdef HAVE_OPENSSL
- return openssl_verify_sig(pk, msg, sig);
+ return openssl_verify_sig((EVP_PKEY *) pk, md_nid, msg, sig);
#else
(void) pk;
+ (void) md_nid;
(void) msg;
(void) sig;
return 0;
#endif
}
+
+ssize_t md_digest(int md_nid,
+ buffer_t in,
+ uint8_t * out)
+{
+#ifdef HAVE_OPENSSL
+ return openssl_md_digest(md_nid, in, out);
+#else
+ (void) md_nid;
+ (void) in;
+ (void) out;
+
+ return -1;
+#endif
+}
+
+ssize_t md_len(int md_nid)
+{
+#ifdef HAVE_OPENSSL
+ return openssl_md_len(md_nid);
+#else
+ (void) md_nid;
+ return -1;
+#endif
+}
+
+int crypt_secure_malloc_init(size_t max)
+{
+#ifdef HAVE_OPENSSL
+ return openssl_secure_malloc_init(max, SECMEM_GUARD);
+#else
+ (void) max;
+ return 0;
+#endif
+}
+
+void crypt_secure_malloc_fini(void)
+{
+#ifdef HAVE_OPENSSL
+ openssl_secure_malloc_fini();
+#endif
+}
+
+void * crypt_secure_malloc(size_t size)
+{
+#ifdef HAVE_OPENSSL
+ return openssl_secure_malloc(size);
+#else
+ return malloc(size);
+#endif
+}
+
+void crypt_secure_free(void * ptr,
+ size_t size)
+{
+ if (ptr == NULL)
+ return;
+
+#ifdef HAVE_OPENSSL
+ openssl_secure_free(ptr, size);
+#else
+ memset(ptr, 0, size);
+ free(ptr);
+#endif
+}
+
+void crypt_secure_clear(void * ptr,
+ size_t size)
+{
+ volatile uint8_t * p;
+
+ if (ptr == NULL)
+ return;
+
+#ifdef HAVE_OPENSSL
+ (void) p;
+ openssl_secure_clear(ptr, size);
+#elif defined(HAVE_EXPLICIT_BZERO)
+ (void) p;
+ explicit_bzero(ptr, size);
+#else /* best effort to avoid optimizing out */
+ p = ptr;
+ while (size-- > 0)
+ *p++ = 0;
+#endif
+}
diff --git a/src/lib/crypt/openssl.c b/src/lib/crypt/openssl.c
index 291a3418..573bc0b3 100644
--- a/src/lib/crypt/openssl.c
+++ b/src/lib/crypt/openssl.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* OpenSSL based cryptographic operations
* Elliptic curve Diffie-Hellman key exchange
@@ -23,6 +23,10 @@
* Foundation, Inc., http://www.fsf.org/about/contact/.
*/
+#define _POSIX_C_SOURCE 200809L
+
+#include <config.h>
+
#include <ouroboros/errno.h>
#include <ouroboros/crypt.h>
#include <ouroboros/hash.h>
@@ -32,31 +36,376 @@
#include <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/ec.h>
+#include <openssl/err.h>
+#include <openssl/kdf.h>
#include <openssl/pem.h>
#include <openssl/sha.h>
+#include <openssl/provider.h>
#include <openssl/x509v3.h>
#include <openssl/x509_vfy.h>
#include <assert.h>
+#include <stdio.h>
+
+#define IS_EC_GROUP(str) (strcmp(str, "EC") == 0)
+#define IS_DH_GROUP(str) (strcmp(str, "DH") == 0)
+
+#define HKDF_INFO_DHE "o7s-ossl-dhe"
+#define HKDF_INFO_ENCAP "o7s-ossl-encap"
+#define HKDF_INFO_ROTATION "o7s-key-rotation"
+#define HKDF_SALT_LEN 32 /* SHA-256 output size */
+
+struct ossl_crypt_ctx {
+ EVP_CIPHER_CTX * evp_ctx;
+ const EVP_CIPHER * cipher;
+ int ivsz;
+ int tagsz;
+
+ struct {
+ uint8_t * cur; /* current key */
+ uint8_t * prv; /* rotated key */
+ } keys;
+
+ struct {
+ uint32_t cntr; /* counter */
+ uint32_t mask; /* phase mask */
+ uint32_t age; /* counter within epoch */
+ uint8_t phase; /* current key phase */
+ uint8_t salt[HKDF_SALT_LEN];
+ } rot; /* rotation logic */
+};
+
+struct kdf_info {
+ buffer_t secret;
+ int nid;
+ buffer_t salt;
+ buffer_t info;
+ buffer_t key;
+};
+
+/* Key rotation macros */
+#define HAS_PHASE_BIT_TOGGLED(ctx) \
+ (((ctx)->rot.cntr & (ctx)->rot.mask) != \
+ (((ctx)->rot.cntr - 1) & (ctx)->rot.mask))
+
+#define HAS_GRACE_EXPIRED(ctx) \
+ ((ctx)->rot.age >= ((ctx)->rot.mask >> 1))
+
+#define ROTATION_TOO_RECENT(ctx) \
+ ((ctx)->rot.age < ((ctx)->rot.mask - ((ctx)->rot.mask >> 2)))
+
+/* Convert hash NID to OpenSSL digest name string for HKDF */
+static const char * hash_nid_to_digest_name(int nid)
+{
+ const EVP_MD * md;
+ const char * name;
+
+ md = EVP_get_digestbynid(nid);
+ if (md == NULL)
+ return "SHA256"; /* fallback to SHA-256 */
+
+ name = EVP_MD_get0_name(md);
+ if (name == NULL)
+ return "SHA256"; /* fallback to SHA-256 */
+
+ return name;
+}
+
+/* Extract public key bytes from a key pair for salt derivation */
+static int get_pk_bytes_from_key(EVP_PKEY * key,
+ buffer_t * pk)
+{
+ const char * name;
+ int ret;
+
+ assert(key != NULL);
+ assert(pk != NULL);
+
+ name = EVP_PKEY_get0_type_name(key);
+ if (name == NULL)
+ goto fail_name;
+
+ if (IS_HYBRID_KEM(name)) {
+ pk->len = EVP_PKEY_get1_encoded_public_key(key, &pk->data);
+ if (pk->len == 0)
+ goto fail_name;
+ } else {
+ /* Pure ML-KEM: use DER encoding to match encap */
+ pk->data = NULL;
+ ret = i2d_PUBKEY(key, &pk->data);
+ if (ret <= 0)
+ goto fail_name;
+ pk->len = (size_t) ret;
+ }
+
+ return 0;
+ fail_name:
+ return -ECRYPT;
+}
+
+/* Derive salt from public key bytes by hashing them */
+static int derive_salt_from_pk_bytes(buffer_t pk,
+ uint8_t * salt,
+ size_t salt_len)
+{
+ uint8_t hash[EVP_MAX_MD_SIZE];
+ unsigned hash_len;
+
+ assert(pk.data != NULL);
+ assert(salt != NULL);
+
+ if (EVP_Digest(pk.data, pk.len, hash, &hash_len,
+ EVP_sha256(), NULL) != 1)
+ goto fail_digest;
+
+ memcpy(salt, hash, salt_len < hash_len ? salt_len : hash_len);
+
+ return 0;
+ fail_digest:
+ return -ECRYPT;
+}
+
+/* Derive salt from two public key byte buffers (DHE) in canonical order */
+static int derive_salt_from_pk_bytes_dhe(buffer_t local,
+ buffer_t remote,
+ uint8_t * salt,
+ size_t salt_len)
+{
+ uint8_t * concat;
+ size_t concat_len;
+ uint8_t hash[EVP_MAX_MD_SIZE];
+ unsigned hash_len;
+ size_t min_len;
+ int cmp;
+
+ assert(local.data != NULL);
+ assert(remote.data != NULL);
+ assert(salt != NULL);
+
+ concat_len = local.len + remote.len;
+ concat = OPENSSL_malloc(concat_len);
+ if (concat == NULL)
+ goto fail_malloc;
+
+ /* Canonical order: compare and concatenate smaller first */
+ min_len = local.len < remote.len ? local.len : remote.len;
+ cmp = memcmp(local.data, remote.data, min_len);
+ if (cmp < 0 || (cmp == 0 && local.len < remote.len)) {
+ memcpy(concat, local.data, local.len);
+ memcpy(concat + local.len, remote.data, remote.len);
+ } else {
+ memcpy(concat, remote.data, remote.len);
+ memcpy(concat + remote.len, local.data, local.len);
+ }
+
+ if (EVP_Digest(concat, concat_len, hash, &hash_len,
+ EVP_sha256(), NULL) != 1)
+ goto fail_digest;
+
+ OPENSSL_free(concat);
+
+ memcpy(salt, hash, salt_len < hash_len ? salt_len : hash_len);
+
+ return 0;
+ fail_digest:
+ OPENSSL_free(concat);
+ fail_malloc:
+ return -ECRYPT;
+}
+
+/* Derive key using HKDF */
+#define OPc_u_str OSSL_PARAM_construct_utf8_string
+#define OPc_o_str OSSL_PARAM_construct_octet_string
+static int derive_key_hkdf(struct kdf_info * ki)
+{
+ EVP_KDF * kdf;
+ EVP_KDF_CTX * kctx;
+ OSSL_PARAM params[5];
+ const char * digest;
+ int idx;
+
+ digest = hash_nid_to_digest_name(ki->nid);
+
+ kdf = EVP_KDF_fetch(NULL, "HKDF", NULL);
+ if (kdf == NULL)
+ goto fail_fetch;
+
+ kctx = EVP_KDF_CTX_new(kdf);
+ if (kctx == NULL)
+ goto fail_ctx;
+
+ idx = 0;
+ params[idx++] = OPc_u_str("digest", (char *) digest, 0);
+ params[idx++] = OPc_o_str("key", ki->secret.data, ki->secret.len);
+ params[idx++] = OPc_o_str("salt", ki->salt.data, ki->salt.len);
+ params[idx++] = OPc_o_str("info", ki->info.data, ki->info.len);
+
+ params[idx] = OSSL_PARAM_construct_end();
+
+ if (EVP_KDF_derive(kctx, ki->key.data, ki->key.len, params) != 1)
+ goto fail_derive;
+
+ EVP_KDF_CTX_free(kctx);
+ EVP_KDF_free(kdf);
+
+ return 0;
+
+ fail_derive:
+ EVP_KDF_CTX_free(kctx);
+ fail_ctx:
+ EVP_KDF_free(kdf);
+ fail_fetch:
+ return -ECRYPT;
+}
+
+/* Key rotation helper functions implementation */
+static int should_rotate_key_rx(struct ossl_crypt_ctx * ctx,
+ uint8_t rx_phase)
+{
+ assert(ctx != NULL);
+
+ /* Phase must have changed */
+ if (rx_phase == ctx->rot.phase)
+ return 0;
+
+ if (ROTATION_TOO_RECENT(ctx))
+ return 0;
+
+ return 1;
+}
+
+static int rotate_key(struct ossl_crypt_ctx * ctx)
+{
+ struct kdf_info ki;
+ uint8_t * tmp;
+
+ assert(ctx != NULL);
+
+ /* Swap keys - move current to prev */
+ tmp = ctx->keys.prv;
+ ctx->keys.prv = ctx->keys.cur;
+
+ if (tmp != NULL) {
+ /* Reuse old prev_key memory for new key */
+ ctx->keys.cur = tmp;
+ } else {
+ /* First rotation - allocate new memory */
+ ctx->keys.cur = OPENSSL_secure_malloc(SYMMKEYSZ);
+ if (ctx->keys.cur == NULL)
+ return -ECRYPT;
+ }
+
+ /* Derive new key from previous key using HKDF */
+ ki.secret.data = ctx->keys.prv;
+ ki.secret.len = SYMMKEYSZ;
+ ki.nid = NID_sha256;
+ ki.salt.data = ctx->rot.salt;
+ ki.salt.len = HKDF_SALT_LEN;
+ ki.info.data = (uint8_t *) HKDF_INFO_ROTATION;
+ ki.info.len = strlen(HKDF_INFO_ROTATION);
+ ki.key.data = ctx->keys.cur;
+ ki.key.len = SYMMKEYSZ;
+
+ if (derive_key_hkdf(&ki) != 0)
+ return -ECRYPT;
+
+ ctx->rot.age = 0;
+ ctx->rot.phase = !ctx->rot.phase;
+
+ return 0;
+}
+
+static void cleanup_old_key(struct ossl_crypt_ctx * ctx)
+{
+ assert(ctx != NULL);
+
+ if (ctx->keys.prv == NULL)
+ return;
+
+ if (!HAS_GRACE_EXPIRED(ctx))
+ return;
+
+ OPENSSL_secure_clear_free(ctx->keys.prv, SYMMKEYSZ);
+ ctx->keys.prv = NULL;
+}
+
+static int try_decrypt(struct ossl_crypt_ctx * ctx,
+ uint8_t * key,
+ uint8_t * iv,
+ uint8_t * input,
+ int in_sz,
+ uint8_t * out,
+ int * out_sz)
+{
+ uint8_t * tag;
+ int tmp_sz;
+ int ret;
+
+ tag = input + in_sz;
+
+ EVP_CIPHER_CTX_reset(ctx->evp_ctx);
+
+ ret = EVP_DecryptInit_ex(ctx->evp_ctx, ctx->cipher, NULL, key, iv);
+ if (ret != 1)
+ return -1;
+
+ if (ctx->tagsz > 0) {
+ ret = EVP_CIPHER_CTX_ctrl(ctx->evp_ctx, EVP_CTRL_AEAD_SET_TAG,
+ ctx->tagsz, tag);
+ if (ret != 1)
+ return -1;
+ }
+
+ ret = EVP_DecryptUpdate(ctx->evp_ctx, out, &tmp_sz, input, in_sz);
+ if (ret != 1)
+ return -1;
+
+ *out_sz = tmp_sz;
+
+ ret = EVP_DecryptFinal_ex(ctx->evp_ctx, out + tmp_sz, &tmp_sz);
+ if (ret != 1)
+ return -1;
+
+ *out_sz += tmp_sz;
+
+ return 0;
+}
/*
* Derive the common secret from
- * - your public key pair (kp)
- * - the remote public key (pub).
+ * - your public key pair (pkp)
+ * - the remote public key bytes (remote_pk).
* Store it in a preallocated buffer (s).
*/
-static int __openssl_ecdh_derive_secret(EVP_PKEY * kp,
- EVP_PKEY * pub,
- uint8_t * s)
+static int __openssl_dhe_derive(EVP_PKEY * pkp,
+ EVP_PKEY * pub,
+ buffer_t remote_pk,
+ int kdf,
+ uint8_t * s)
{
- EVP_PKEY_CTX * ctx;
- int ret;
- uint8_t * secret;
- size_t secret_len;
-
- ctx = EVP_PKEY_CTX_new(kp, NULL);
+ EVP_PKEY_CTX * ctx;
+ struct kdf_info ki;
+ buffer_t local_pk;
+ int ret;
+ uint8_t * secret;
+ size_t secret_len;
+ uint8_t salt_buf[HKDF_SALT_LEN];
+
+ /* Extract local public key bytes */
+ local_pk.data = NULL;
+ ret = i2d_PUBKEY(pkp, &local_pk.data);
+ if (ret <= 0)
+ goto fail_local;
+ local_pk.len = (size_t) ret;
+
+ /* Derive salt from both public keys */
+ if (derive_salt_from_pk_bytes_dhe(local_pk, remote_pk, salt_buf,
+ HKDF_SALT_LEN) < 0)
+ goto fail_salt;
+
+ ctx = EVP_PKEY_CTX_new(pkp, NULL);
if (ctx == NULL)
- goto fail_new;
+ goto fail_salt;
ret = EVP_PKEY_derive_init(ctx);
if (ret != 1)
@@ -81,37 +430,78 @@ static int __openssl_ecdh_derive_secret(EVP_PKEY * kp,
if (ret != 1)
goto fail_derive;
- /* Hash the secret for use as AES key. */
- mem_hash(HASH_SHA3_256, s, secret, secret_len);
+ ki.nid = kdf;
+ ki.secret.len = secret_len;
+ ki.secret.data = secret;
+ ki.info.len = strlen(HKDF_INFO_DHE);
+ ki.info.data = (uint8_t *) HKDF_INFO_DHE;
+ ki.key.len = SYMMKEYSZ;
+ ki.key.data = s;
+ ki.salt.len = HKDF_SALT_LEN;
+ ki.salt.data = salt_buf;
+
+ /* Derive symmetric key from shared secret using HKDF */
+ ret = derive_key_hkdf(&ki);
OPENSSL_free(secret);
EVP_PKEY_CTX_free(ctx);
+ OPENSSL_free(local_pk.data);
+
+ if (ret != 0)
+ return ret;
return 0;
fail_derive:
OPENSSL_free(secret);
fail_ctx:
EVP_PKEY_CTX_free(ctx);
- fail_new:
+ fail_salt:
+ OPENSSL_free(local_pk.data);
+ fail_local:
return -ECRYPT;
}
-static int __openssl_ecdh_gen_key(void ** kp)
+static int __openssl_dhe_gen_key(const char * algo,
+ EVP_PKEY ** kp)
{
EVP_PKEY_CTX * ctx = NULL;
EVP_PKEY_CTX * kctx = NULL;
EVP_PKEY * params = NULL;
+ int nid;
+ int type;
int ret;
- ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL);
+ assert(algo != NULL);
+ assert(kp != NULL);
+
+ nid = OBJ_txt2nid(algo);
+ if (nid == NID_undef)
+ return -ECRYPT;
+
+ /* X25519 and X448: direct keygen context */
+ if (nid == EVP_PKEY_X25519 || nid == EVP_PKEY_X448) {
+ kctx = EVP_PKEY_CTX_new_id(nid, NULL);
+ if (kctx == NULL)
+ goto fail_kctx;
+
+ goto keygen;
+ }
+ /* EC and FFDHE: parameter generation first */
+ type = (strncmp(algo, "ffdhe", 5) == 0) ? EVP_PKEY_DH : EVP_PKEY_EC;
+
+ ctx = EVP_PKEY_CTX_new_id(type, NULL);
if (ctx == NULL)
- goto fail_new_id;
+ goto fail_ctx;
ret = EVP_PKEY_paramgen_init(ctx);
if (ret != 1)
goto fail_paramgen;
- ret = EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, NID_X9_62_prime256v1);
+ if (type == EVP_PKEY_EC)
+ ret = EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, nid);
+ else /* EVP_PKEY_DH */
+ ret = EVP_PKEY_CTX_set_dh_nid(ctx, nid);
+
if (ret != 1)
goto fail_paramgen;
@@ -121,191 +511,569 @@ static int __openssl_ecdh_gen_key(void ** kp)
kctx = EVP_PKEY_CTX_new(params, NULL);
if (kctx == NULL)
- goto fail_keygen_init;
+ goto fail_kctx;
+ EVP_PKEY_free(params);
+ EVP_PKEY_CTX_free(ctx);
+ keygen:
ret = EVP_PKEY_keygen_init(kctx);
if (ret != 1)
goto fail_keygen;
- ret = EVP_PKEY_keygen(kctx, (EVP_PKEY **) kp);
+ ret = EVP_PKEY_keygen(kctx, kp);
if (ret != 1)
goto fail_keygen;
- EVP_PKEY_free(params);
EVP_PKEY_CTX_free(kctx);
- EVP_PKEY_CTX_free(ctx);
return 0;
+
fail_keygen:
EVP_PKEY_CTX_free(kctx);
- fail_keygen_init:
- EVP_PKEY_free(params);
+ return -ECRYPT;
+ fail_kctx:
+ if (params != NULL)
+ EVP_PKEY_free(params);
fail_paramgen:
- EVP_PKEY_CTX_free(ctx);
- fail_new_id:
+ if (ctx != NULL)
+ EVP_PKEY_CTX_free(ctx);
+ fail_ctx:
+ return -ECRYPT;
+}
+
+static int __openssl_kem_gen_key(const char * algo,
+ EVP_PKEY ** kp)
+{
+ EVP_PKEY_CTX * kctx;
+ int ret;
+
+ assert(algo != NULL);
+ assert(kp != NULL);
+
+ /* PQC KEM (ML-KEM-512, ML-KEM-768, ML-KEM-1024) or hybrid */
+ kctx = EVP_PKEY_CTX_new_from_name(NULL, algo, NULL);
+ if (kctx == NULL)
+ goto fail_kctx;
+
+ ret = EVP_PKEY_keygen_init(kctx);
+ if (ret != 1)
+ goto fail_keygen;
+
+ ret = EVP_PKEY_keygen(kctx, kp);
+ if (ret != 1)
+ goto fail_keygen;
+
+ EVP_PKEY_CTX_free(kctx);
+
+ return 0;
+
+ fail_keygen:
+ EVP_PKEY_CTX_free(kctx);
+ fail_kctx:
return -ECRYPT;
}
-ssize_t openssl_ecdh_pkp_create(void ** pkp,
- uint8_t * pk)
+/* Determine hybrid KEM algorithm from raw key/ciphertext length */
+static const char * __openssl_hybrid_algo_from_len(size_t len)
{
- uint8_t * pos;
- ssize_t len;
+ switch(len) {
+ case X25519MLKEM768_PKSZ:
+ return "X25519MLKEM768";
+ case X25519MLKEM768_CTSZ:
+ return "X25519MLKEM768";
+ case X448MLKEM1024_PKSZ:
+ return "X448MLKEM1024";
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+static int __openssl_kex_gen_key(const char * algo,
+ EVP_PKEY ** kp)
+{
+ assert(algo != NULL);
+ assert(kp != NULL);
+
+ /* Dispatch based on algorithm name prefix */
+ if (IS_KEM_ALGORITHM(algo))
+ return __openssl_kem_gen_key(algo, kp);
+
+ return __openssl_dhe_gen_key(algo, kp);
+}
+
+ssize_t openssl_pkp_create(const char * algo,
+ EVP_PKEY ** pkp,
+ uint8_t * pk)
+{
+ uint8_t * pos;
+ buffer_t raw;
+ ssize_t len;
+ assert(algo != NULL);
assert(pkp != NULL);
assert(*pkp == NULL);
assert(pk != NULL);
- if (__openssl_ecdh_gen_key(pkp) < 0)
+ if (__openssl_kex_gen_key(algo, pkp) < 0)
goto fail_key;
- pos = pk; /* i2d_PUBKEY increments the pointer, don't use pk! */
- len = i2d_PUBKEY(*pkp, &pos);
- if (len < 0)
- goto fail_pubkey;
+ if (IS_HYBRID_KEM(algo)) { /* Raw encode hybrid KEM */
+ raw.len = EVP_PKEY_get1_encoded_public_key(*pkp, &raw.data);
+ if (raw.len == 0)
+ goto fail_pubkey;
+
+ memcpy(pk, raw.data, raw.len);
+ OPENSSL_free(raw.data);
- return len;
+ return (ssize_t) raw.len;
+ } else { /* DER encode standard algorithms */
+ pos = pk; /* i2d_PUBKEY increments the pointer, don't use pk! */
+ len = i2d_PUBKEY(*pkp, &pos);
+ if (len < 0)
+ goto fail_pubkey;
+
+ return len;
+ }
fail_pubkey:
EVP_PKEY_free(*pkp);
fail_key:
return -ECRYPT;
}
-void openssl_ecdh_pkp_destroy(void * pkp)
+/* Common KEM encapsulation - pub key and salt already prepared */
+static ssize_t __openssl_kem_encap(EVP_PKEY * pub,
+ uint8_t * salt,
+ uint8_t * ct,
+ int kdf,
+ uint8_t * s)
+{
+ EVP_PKEY_CTX * ctx;
+ struct kdf_info ki;
+ uint8_t * secret;
+ size_t secret_len;
+ size_t ct_len;
+ int ret;
+
+ ctx = EVP_PKEY_CTX_new(pub, NULL);
+ if (ctx == NULL)
+ goto fail_ctx;
+
+ ret = EVP_PKEY_encapsulate_init(ctx, NULL);
+ if (ret != 1)
+ goto fail_encap;
+
+ /* Get required lengths */
+ ret = EVP_PKEY_encapsulate(ctx, NULL, &ct_len, NULL, &secret_len);
+ if (ret != 1 || ct_len > MSGBUFSZ)
+ goto fail_encap;
+
+ /* Allocate buffer for secret */
+ secret = OPENSSL_malloc(secret_len);
+ if (secret == NULL)
+ goto fail_encap;
+
+ /* Perform encapsulation */
+ ret = EVP_PKEY_encapsulate(ctx, ct, &ct_len, secret, &secret_len);
+ if (ret != 1)
+ goto fail_secret;
+
+ ki.secret.len = secret_len;
+ ki.secret.data = secret;
+ ki.nid = kdf;
+ ki.info.len = strlen(HKDF_INFO_ENCAP);
+ ki.info.data = (uint8_t *) HKDF_INFO_ENCAP;
+ ki.key.len = SYMMKEYSZ;
+ ki.key.data = s;
+ ki.salt.len = HKDF_SALT_LEN;
+ ki.salt.data = salt;
+
+ /* Derive symmetric key from shared secret using HKDF */
+ ret = derive_key_hkdf(&ki);
+
+ OPENSSL_free(secret);
+ EVP_PKEY_CTX_free(ctx);
+
+ if (ret != 0)
+ return -ECRYPT;
+
+ return (ssize_t) ct_len;
+
+ fail_secret:
+ OPENSSL_free(secret);
+ fail_encap:
+ EVP_PKEY_CTX_free(ctx);
+ fail_ctx:
+ return -ECRYPT;
+}
+
+/* ML-KEM encapsulation - DER-encoded public key */
+ssize_t openssl_kem_encap(buffer_t pk,
+ uint8_t * ct,
+ int kdf,
+ uint8_t * s)
+{
+ EVP_PKEY * pub;
+ uint8_t * pos;
+ uint8_t salt[HKDF_SALT_LEN];
+ ssize_t ret;
+
+ assert(pk.data != NULL);
+ assert(ct != NULL);
+ assert(s != NULL);
+
+ if (derive_salt_from_pk_bytes(pk, salt, HKDF_SALT_LEN) < 0)
+ goto fail_salt;
+
+ pos = pk.data;
+ pub = d2i_PUBKEY(NULL, (const uint8_t **) &pos, (long) pk.len);
+ if (pub == NULL)
+ goto fail_salt;
+
+ ret = __openssl_kem_encap(pub, salt, ct, kdf, s);
+
+ EVP_PKEY_free(pub);
+
+ return ret;
+ fail_salt:
+ return -ECRYPT;
+}
+
+/* Hybrid KEM encapsulation: raw-encoded public key */
+ssize_t openssl_kem_encap_raw(buffer_t pk,
+ uint8_t * ct,
+ int kdf,
+ uint8_t * s)
+{
+ EVP_PKEY * pub;
+ const char * algo;
+ uint8_t salt[HKDF_SALT_LEN];
+ ssize_t ret;
+
+ assert(pk.data != NULL);
+ assert(ct != NULL);
+ assert(s != NULL);
+
+ if (derive_salt_from_pk_bytes(pk, salt, HKDF_SALT_LEN) < 0)
+ goto fail_salt;
+
+ algo = __openssl_hybrid_algo_from_len(pk.len);
+ if (algo == NULL)
+ goto fail_salt;
+
+ pub = EVP_PKEY_new_raw_public_key_ex(NULL, algo, NULL,
+ pk.data, pk.len);
+ if (pub == NULL)
+ goto fail_salt;
+
+ ret = __openssl_kem_encap(pub, salt, ct, kdf, s);
+
+ EVP_PKEY_free(pub);
+
+ return ret;
+ fail_salt:
+ return -ECRYPT;
+}
+
+/* KEM decapsulation - used by party that generated the keypair */
+int openssl_kem_decap(EVP_PKEY * priv,
+ buffer_t ct,
+ int kdf,
+ uint8_t * s)
+{
+ EVP_PKEY_CTX * ctx;
+ struct kdf_info ki;
+ buffer_t pk;
+ uint8_t * secret;
+ size_t secret_len;
+ int ret;
+ uint8_t salt[HKDF_SALT_LEN];
+
+ /* Extract public key bytes from private key */
+ if (get_pk_bytes_from_key(priv, &pk) < 0)
+ goto fail_pk;
+
+ if (derive_salt_from_pk_bytes(pk, salt, HKDF_SALT_LEN) < 0)
+ goto fail_salt;
+
+ ctx = EVP_PKEY_CTX_new(priv, NULL);
+ if (ctx == NULL)
+ goto fail_salt;
+
+ ret = EVP_PKEY_decapsulate_init(ctx, NULL);
+ if (ret != 1)
+ goto fail_ctx;
+
+ /* Get required secret length */
+ ret = EVP_PKEY_decapsulate(ctx, NULL, &secret_len, ct.data, ct.len);
+ if (ret != 1)
+ goto fail_ctx;
+
+ /* Allocate buffer for secret */
+ secret = OPENSSL_malloc(secret_len);
+ if (secret == NULL)
+ goto fail_ctx;
+
+ /* Perform decapsulation */
+ ret = EVP_PKEY_decapsulate(ctx, secret, &secret_len, ct.data, ct.len);
+ if (ret != 1)
+ goto fail_secret;
+
+ ki.secret.len = secret_len;
+ ki.secret.data = secret;
+ ki.nid = kdf;
+ ki.info.len = strlen(HKDF_INFO_ENCAP);
+ ki.info.data = (uint8_t *) HKDF_INFO_ENCAP;
+ ki.key.len = SYMMKEYSZ;
+ ki.key.data = s;
+ ki.salt.len = HKDF_SALT_LEN;
+ ki.salt.data = salt;
+
+ /* Derive symmetric key from shared secret using HKDF */
+ ret = derive_key_hkdf(&ki);
+
+ OPENSSL_free(secret);
+ EVP_PKEY_CTX_free(ctx);
+ OPENSSL_free(pk.data);
+
+ if (ret != 0)
+ return ret;
+
+ return 0;
+
+ fail_secret:
+ OPENSSL_free(secret);
+ fail_ctx:
+ EVP_PKEY_CTX_free(ctx);
+ fail_salt:
+ OPENSSL_free(pk.data);
+ fail_pk:
+ return -ECRYPT;
+}
+
+void openssl_pkp_destroy(EVP_PKEY * pkp)
+{
+ EVP_PKEY_free(pkp);
+}
+
+int __openssl_get_curve(EVP_PKEY * pub,
+ char * algo)
+{
+ int ret;
+ size_t len = KEX_ALGO_BUFSZ;
+
+ ret = EVP_PKEY_get_utf8_string_param(pub, "group", algo, len, &len);
+ return ret == 1 ? 0 : -ECRYPT;
+}
+
+int openssl_get_algo_from_pk_der(buffer_t pk,
+ char * algo)
+{
+ uint8_t * pos;
+ EVP_PKEY * pub;
+ char * type_str;
+
+ assert(pk.data != NULL);
+ assert(algo != NULL);
+
+ pos = pk.data;
+ pub = d2i_PUBKEY(NULL, (const uint8_t **) &pos, (long) pk.len);
+ if (pub == NULL)
+ goto fail_decode;
+
+ type_str = (char *) EVP_PKEY_get0_type_name(pub);
+ if (type_str == NULL)
+ goto fail_pub;
+
+ strcpy(algo, type_str);
+
+ if ((IS_EC_GROUP(algo) || IS_DH_GROUP(algo)) &&
+ __openssl_get_curve(pub, algo) < 0)
+ goto fail_pub;
+
+ EVP_PKEY_free(pub);
+ return 0;
+
+ fail_pub:
+ EVP_PKEY_free(pub);
+ fail_decode:
+ return -ECRYPT;
+}
+
+int openssl_get_algo_from_pk_raw(buffer_t pk,
+ char * algo)
{
- EVP_PKEY_free((EVP_PKEY *) pkp);
+ const char * hybrid_algo;
+
+ assert(pk.data != NULL);
+ assert(algo != NULL);
+
+ hybrid_algo = __openssl_hybrid_algo_from_len(pk.len);
+ if (hybrid_algo == NULL)
+ return -ECRYPT;
+
+ strcpy(algo, hybrid_algo);
+
+ return 0;
}
-int openssl_ecdh_derive(void * pkp,
- buffer_t pk,
- uint8_t * s)
+int openssl_dhe_derive(EVP_PKEY * pkp,
+ buffer_t pk,
+ int kdf,
+ uint8_t * s)
{
uint8_t * pos;
EVP_PKEY * pub;
+ assert(pkp != NULL);
+ assert(pk.data != NULL);
+ assert(s != NULL);
+
+ /* X.509 DER decoding for DHE */
pos = pk.data; /* d2i_PUBKEY increments pos, don't use key ptr! */
pub = d2i_PUBKEY(NULL, (const uint8_t **) &pos, (long) pk.len);
if (pub == NULL)
- goto fail_pubkey;
+ goto fail_decode;
- if (__openssl_ecdh_derive_secret(pkp, pub, s) < 0)
- goto fail_key;
+ if (__openssl_dhe_derive(pkp, pub, pk, kdf, s) < 0)
+ goto fail_derive;
EVP_PKEY_free(pub);
return 0;
- fail_pubkey:
+ fail_derive:
EVP_PKEY_free(pub);
- fail_key:
+ fail_decode:
return -ECRYPT;
}
-/*
- * AES encryption calls. If FRCT is disabled, we should generate a
- * 128-bit random IV and append it to the packet. If the flow is
- * reliable, we could initialize the context once, and consider the
- * stream a single encrypted message to avoid initializing the
- * encryption context for each packet.
- */
-
-int openssl_encrypt(void * ctx,
- uint8_t * key,
- buffer_t in,
- buffer_t * out)
+int openssl_encrypt(struct ossl_crypt_ctx * ctx,
+ buffer_t in,
+ buffer_t * out)
{
- uint8_t * ptr;
- uint8_t * iv;
- int in_sz;
- int out_sz;
- int tmp_sz;
- int ret;
+ uint8_t * ptr;
+ uint8_t * iv;
+ int in_sz;
+ int out_sz;
+ int tmp_sz;
+ int ret;
+
+ assert(ctx != NULL);
in_sz = (int) in.len;
- out->data = malloc(in.len + EVP_MAX_BLOCK_LENGTH + IVSZ);
+ out->data = malloc(in.len + EVP_MAX_BLOCK_LENGTH + \
+ ctx->ivsz + ctx->tagsz);
if (out->data == NULL)
goto fail_malloc;
iv = out->data;
- ptr = out->data + IVSZ;
+ ptr = out->data + ctx->ivsz;
+
+ if (random_buffer(iv, ctx->ivsz) < 0)
+ goto fail_encrypt;
- if (random_buffer(iv, IVSZ) < 0)
- goto fail_iv;
+ /* Set IV bit 7 to current key phase (KEY_ROTATION_BIT of counter) */
+ if (ctx->rot.cntr & ctx->rot.mask)
+ iv[0] |= 0x80;
+ else
+ iv[0] &= 0x7F;
- EVP_CIPHER_CTX_reset(ctx);
+ EVP_CIPHER_CTX_reset(ctx->evp_ctx);
- ret = EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv);
+ ret = EVP_EncryptInit_ex(ctx->evp_ctx, ctx->cipher, NULL,
+ ctx->keys.cur, iv);
if (ret != 1)
- goto fail_iv;
+ goto fail_encrypt;
- ret = EVP_EncryptUpdate(ctx, ptr, &tmp_sz, in.data, in_sz);
+ ret = EVP_EncryptUpdate(ctx->evp_ctx, ptr, &tmp_sz, in.data, in_sz);
if (ret != 1)
goto fail_encrypt;
out_sz = tmp_sz;
- ret = EVP_EncryptFinal_ex(ctx, ptr + tmp_sz, &tmp_sz);
+ ret = EVP_EncryptFinal_ex(ctx->evp_ctx, ptr + tmp_sz, &tmp_sz);
if (ret != 1)
goto fail_encrypt;
out_sz += tmp_sz;
- EVP_CIPHER_CTX_cleanup(ctx);
+ /* For AEAD ciphers, get and append the authentication tag */
+ if (ctx->tagsz > 0) {
+ ret = EVP_CIPHER_CTX_ctrl(ctx->evp_ctx, EVP_CTRL_AEAD_GET_TAG,
+ ctx->tagsz, ptr + out_sz);
+ if (ret != 1)
+ goto fail_encrypt;
+ out_sz += ctx->tagsz;
+ }
assert(out_sz >= in_sz);
- out->len = (size_t) out_sz + IVSZ;
+ out->len = (size_t) out_sz + ctx->ivsz;
+
+ /* Increment packet counter and check for key rotation */
+ ctx->rot.cntr++;
+ ctx->rot.age++;
+
+ if (HAS_PHASE_BIT_TOGGLED(ctx)) {
+ if (rotate_key(ctx) != 0)
+ goto fail_encrypt;
+ }
+
+ cleanup_old_key(ctx);
return 0;
fail_encrypt:
- EVP_CIPHER_CTX_cleanup(ctx);
- fail_iv:
free(out->data);
fail_malloc:
clrbuf(*out);
return -ECRYPT;
}
-int openssl_decrypt(void * ctx,
- uint8_t * key,
- buffer_t in,
- buffer_t * out)
+int openssl_decrypt(struct ossl_crypt_ctx * ctx,
+ buffer_t in,
+ buffer_t * out)
{
- uint8_t * ptr;
uint8_t * iv;
uint8_t * input;
- int ret;
+ uint8_t rx_phase;
int out_sz;
int in_sz;
- int tmp_sz;
- in_sz = (int) in.len - IVSZ;
- if (in_sz < 0)
+ assert(ctx != NULL);
+
+ in_sz = (int) in.len - ctx->ivsz;
+ if (in_sz < ctx->tagsz)
return -ECRYPT;
- out->data = malloc(in_sz);
+ in_sz -= ctx->tagsz;
+
+ out->data = malloc(in_sz + EVP_MAX_BLOCK_LENGTH);
if (out->data == NULL)
goto fail_malloc;
iv = in.data;
- ptr = out->data;
- input = in.data + IVSZ;
+ input = in.data + ctx->ivsz;
- EVP_CIPHER_CTX_reset(ctx);
+ /* Extract phase from IV bit 7 and check for key rotation */
+ rx_phase = (iv[0] & 0x80) ? 1 : 0;
- ret = EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), NULL, key, iv);
- if (ret != 1)
- goto fail_decrypt_init;
+ if (should_rotate_key_rx(ctx, rx_phase)) {
+ if (rotate_key(ctx) != 0)
+ goto fail_decrypt;
+ }
- ret = EVP_DecryptUpdate(ctx, ptr, &tmp_sz, input, in_sz);
- if (ret != 1)
- goto fail_decrypt;
+ ctx->rot.cntr++;
+ ctx->rot.age++;
- out_sz = tmp_sz;
- ret = EVP_DecryptFinal_ex(ctx, ptr + tmp_sz, &tmp_sz);
- if (ret != 1)
- goto fail_decrypt;
-
- out_sz += tmp_sz;
+ if (try_decrypt(ctx, ctx->keys.cur, iv, input, in_sz, out->data,
+ &out_sz) != 0) {
+ if (ctx->keys.prv == NULL)
+ goto fail_decrypt;
+ if (try_decrypt(ctx, ctx->keys.prv, iv, input, in_sz,
+ out->data, &out_sz) != 0)
+ goto fail_decrypt;
+ }
assert(out_sz <= in_sz);
@@ -313,22 +1081,95 @@ int openssl_decrypt(void * ctx,
return 0;
fail_decrypt:
- EVP_CIPHER_CTX_cleanup(ctx);
- fail_decrypt_init:
free(out->data);
fail_malloc:
clrbuf(*out);
return -ECRYPT;
}
-void * openssl_crypt_create_ctx(void)
+struct ossl_crypt_ctx * openssl_crypt_create_ctx(struct crypt_sk * sk)
{
- return (void *) EVP_CIPHER_CTX_new();
+ struct ossl_crypt_ctx * ctx;
+
+ assert(sk != NULL);
+ assert(sk->key != NULL);
+ assert(sk->rot_bit > 0 && sk->rot_bit < 32);
+
+ ctx = malloc(sizeof(*ctx));
+ if (ctx == NULL)
+ goto fail_malloc;
+
+ memset(ctx, 0, sizeof(*ctx));
+
+ ctx->keys.cur = OPENSSL_secure_malloc(SYMMKEYSZ);
+ if (ctx->keys.cur == NULL)
+ goto fail_key;
+
+ memcpy(ctx->keys.cur, sk->key, SYMMKEYSZ);
+
+ ctx->keys.prv = NULL;
+
+ /* Derive rotation salt from initial shared secret */
+ if (EVP_Digest(sk->key, SYMMKEYSZ, ctx->rot.salt, NULL,
+ EVP_sha256(), NULL) != 1)
+ goto fail_cipher;
+
+ ctx->cipher = EVP_get_cipherbynid(sk->nid);
+ if (ctx->cipher == NULL)
+ goto fail_cipher;
+
+ ctx->ivsz = EVP_CIPHER_iv_length(ctx->cipher);
+
+ /* Set tag size for AEAD ciphers (GCM, CCM, OCB, ChaCha20-Poly1305) */
+ if (EVP_CIPHER_flags(ctx->cipher) & EVP_CIPH_FLAG_AEAD_CIPHER)
+ ctx->tagsz = 16; /* Standard AEAD tag length (128 bits) */
+
+ ctx->rot.cntr = 0;
+ ctx->rot.mask = (1U << sk->rot_bit);
+ ctx->rot.age = 0;
+ ctx->rot.phase = 0;
+
+ ctx->evp_ctx = EVP_CIPHER_CTX_new();
+ if (ctx->evp_ctx == NULL)
+ goto fail_cipher;
+
+ return ctx;
+
+ fail_cipher:
+ OPENSSL_secure_clear_free(ctx->keys.cur, SYMMKEYSZ);
+ fail_key:
+ free(ctx);
+ fail_malloc:
+ return NULL;
}
-void openssl_crypt_destroy_ctx(void * ctx)
+void openssl_crypt_destroy_ctx(struct ossl_crypt_ctx * ctx)
{
- EVP_CIPHER_CTX_free((EVP_CIPHER_CTX *) ctx);
+ if (ctx == NULL)
+ return;
+
+ if (ctx->keys.cur != NULL)
+ OPENSSL_secure_clear_free(ctx->keys.cur, SYMMKEYSZ);
+
+ if (ctx->keys.prv != NULL)
+ OPENSSL_secure_clear_free(ctx->keys.prv, SYMMKEYSZ);
+
+ EVP_CIPHER_CTX_free(ctx->evp_ctx);
+ free(ctx);
+}
+
+int openssl_crypt_get_ivsz(struct ossl_crypt_ctx * ctx)
+{
+ assert(ctx != NULL);
+
+ return ctx->ivsz;
+}
+
+int openssl_crypt_get_tagsz(struct ossl_crypt_ctx * ctx)
+{
+ assert(ctx != NULL);
+
+ return ctx->tagsz;
}
/* AUTHENTICATION */
@@ -442,14 +1283,24 @@ int openssl_load_privkey_file(const char * path,
{
FILE * fp;
EVP_PKEY * pkey;
+ unsigned long err;
+ char errbuf[256];
fp = fopen(path, "r");
- if (fp == NULL)
+ if (fp == NULL) {
+ fprintf(stderr, "Failed to open %s\n", path);
goto fail_file;
+ }
pkey = PEM_read_PrivateKey(fp, NULL, NULL, "");
- if (pkey == NULL)
+ if (pkey == NULL) {
+ err = ERR_get_error();
+ ERR_error_string_n(err, errbuf, sizeof(errbuf));
+ fprintf(stderr,
+ "OpenSSL error loading privkey from %s: %s\n",
+ path, errbuf);
goto fail_key;
+ }
fclose(fp);
@@ -518,6 +1369,46 @@ int openssl_load_pubkey_file(const char * path,
return -1;
}
+int openssl_load_pubkey_file_to_der(const char * path,
+ buffer_t * buf)
+{
+ FILE * fp;
+ EVP_PKEY * pkey;
+ int ret;
+
+ assert(path != NULL);
+ assert(buf != NULL);
+
+ memset(buf, 0, sizeof(*buf));
+
+ fp = fopen(path, "r");
+ if (fp == NULL)
+ goto fail_file;
+
+ pkey = PEM_read_PUBKEY(fp, NULL, NULL, NULL);
+ if (pkey == NULL)
+ goto fail_key;
+
+ /* Extract public key bytes in DER format */
+ ret = get_pk_bytes_from_key(pkey, buf);
+ if (ret < 0)
+ goto fail_extract;
+
+ EVP_PKEY_free(pkey);
+
+ fclose(fp);
+
+ return 0;
+
+ fail_extract:
+ EVP_PKEY_free(pkey);
+ fail_key:
+ fclose(fp);
+ fail_file:
+ clrbuf(*buf);
+ return -1;
+}
+
int openssl_load_pubkey_str(const char * str,
void ** key)
{
@@ -547,28 +1438,125 @@ int openssl_load_pubkey_str(const char * str,
return -1;
}
-int openssl_cmp_key(const void * key1,
- const void * key2)
+int openssl_load_pubkey_raw_file(const char * path,
+ buffer_t * buf)
+{
+ FILE * fp;
+ uint8_t tmp_buf[MSGBUFSZ];
+ size_t bytes_read;
+ const char * algo;
+
+ assert(path != NULL);
+ assert(buf != NULL);
+
+ fp = fopen(path, "rb");
+ if (fp == NULL)
+ goto fail_file;
+
+ bytes_read = fread(tmp_buf, 1, MSGBUFSZ, fp);
+ if (bytes_read == 0)
+ goto fail_read;
+
+ /* Validate that this is a known hybrid KEM format */
+ algo = __openssl_hybrid_algo_from_len(bytes_read);
+ if (algo == NULL)
+ goto fail_read;
+
+ buf->data = malloc(bytes_read);
+ if (buf->data == NULL)
+ goto fail_malloc;
+
+ memcpy(buf->data, tmp_buf, bytes_read);
+ buf->len = bytes_read;
+
+ fclose(fp);
+ return 0;
+
+ fail_malloc:
+ fail_read:
+ fclose(fp);
+ fail_file:
+ clrbuf(*buf);
+ return -1;
+}
+
+/* Determine hybrid KEM algorithm from raw private key length */
+static const char * __openssl_hybrid_algo_from_sk_len(size_t len)
{
- EVP_PKEY * pkey1;
- EVP_PKEY * pkey2;
+ switch(len) {
+ case X25519MLKEM768_SKSZ:
+ return "X25519MLKEM768";
+ case X448MLKEM1024_SKSZ:
+ return "X448MLKEM1024";
+ default:
+ break;
+ }
+
+ return NULL;
+}
+
+int openssl_load_privkey_raw_file(const char * path,
+ void ** key)
+{
+ FILE * fp;
+ uint8_t tmp_buf[4096];
+ size_t bytes_read;
+ const char * algo;
+ EVP_PKEY * pkey;
+
+ assert(path != NULL);
+ assert(key != NULL);
+
+ fp = fopen(path, "rb");
+ if (fp == NULL)
+ goto fail_file;
+
+ bytes_read = fread(tmp_buf, 1, sizeof(tmp_buf), fp);
+ if (bytes_read == 0)
+ goto fail_read;
+
+ /* Determine algorithm from key size */
+ algo = __openssl_hybrid_algo_from_sk_len(bytes_read);
+ if (algo == NULL)
+ goto fail_read;
+
+ pkey = EVP_PKEY_new_raw_private_key_ex(NULL, algo, NULL,
+ tmp_buf, bytes_read);
+ /* Clear sensitive data from stack */
+ OPENSSL_cleanse(tmp_buf, bytes_read);
+
+ if (pkey == NULL)
+ goto fail_read;
+
+ fclose(fp);
+
+ *key = (void *) pkey;
+
+ return 0;
+
+ fail_read:
+ fclose(fp);
+ fail_file:
+ *key = NULL;
+ return -1;
+}
+int openssl_cmp_key(const EVP_PKEY * key1,
+ const EVP_PKEY * key2)
+{
assert(key1 != NULL);
assert(key2 != NULL);
- pkey1 = (EVP_PKEY *) key1;
- pkey2 = (EVP_PKEY *) key2;
-
#if OPENSSL_VERSION_NUMBER >= 0x30000000L
- return EVP_PKEY_eq(pkey1, pkey2) == 1 ? 0 : -1;
+ return EVP_PKEY_eq(key1, key2) == 1 ? 0 : -1;
#else
- return EVP_PKEY_cmp(pkey1, pkey2) == 1 ? 0 : -1;
+ return EVP_PKEY_cmp(key1, key2) == 1 ? 0 : -1;
#endif
}
-void openssl_free_key(void * key)
+void openssl_free_key(EVP_PKEY * key)
{
- EVP_PKEY_free((EVP_PKEY *) key);
+ EVP_PKEY_free(key);
}
int openssl_check_crt_name(void * crt,
@@ -600,12 +1588,48 @@ int openssl_check_crt_name(void * crt,
return -1;
}
+int openssl_get_crt_name(void * crt,
+ char * name)
+{
+ char * subj;
+ char * cn;
+ char * end;
+ X509 * xcrt;
+
+ xcrt = (X509 *) crt;
+
+ subj = X509_NAME_oneline(X509_get_subject_name(xcrt), NULL, 0);
+ if (subj == NULL)
+ goto fail_subj;
+
+ cn = strstr(subj, "CN=");
+ if (cn == NULL)
+ goto fail_cn;
+
+ cn += 3; /* Skip "CN=" */
+
+ /* Find end of CN (comma or slash for next field) */
+ end = strpbrk(cn, ",/");
+ if (end != NULL)
+ *end = '\0';
+
+ strcpy(name, cn);
+ free(subj);
+
+ return 0;
+ fail_cn:
+ free(subj);
+ fail_subj:
+ return -1;
+}
+
int openssl_crt_str(const void * crt,
char * str)
{
- BIO * bio;
- X509 * xcrt;
- char * p;
+ BIO * bio;
+ X509 * xcrt;
+ char * p;
+ ssize_t len;
xcrt = (X509 *) crt;
@@ -615,11 +1639,12 @@ int openssl_crt_str(const void * crt,
X509_print(bio, xcrt);
- BIO_get_mem_data(bio, &p);
- if (p == NULL)
+ len = (ssize_t) BIO_get_mem_data(bio, &p);
+ if (len <= 0 || p == NULL)
goto fail_p;
- sprintf(str, "%s", p);
+ memcpy(str, p, len);
+ str[len] = '\0';
BIO_free(bio);
@@ -704,37 +1729,48 @@ int openssl_verify_crt(void * store,
return -1;
}
-int openssl_sign(void * pkp,
+static const EVP_MD * select_md(EVP_PKEY * pkey,
+ int nid)
+{
+ if (EVP_PKEY_get_id(pkey) < 0)
+ return NULL; /* Provider-based (PQC) */
+
+ if (nid == NID_undef)
+ return NULL; /* Classical requires explicit nid */
+
+ return EVP_get_digestbynid(nid);
+}
+
+int openssl_sign(EVP_PKEY * pkp,
+ int nid,
buffer_t msg,
buffer_t * sig)
{
- EVP_PKEY * pkey;
- EVP_MD_CTX * mdctx;
- size_t required;
+ EVP_MD_CTX * mdctx;
+ const EVP_MD * md;
+ size_t required;
assert(pkp != NULL);
assert(sig != NULL);
- pkey = (EVP_PKEY *) pkp;
-
mdctx = EVP_MD_CTX_new();
if (!mdctx)
goto fail_ctx;
- if (EVP_DigestSignInit(mdctx, NULL, EVP_sha256(), NULL, pkey) != 1)
- goto fail_digest;
+ md = select_md(pkp, nid);
- if (EVP_DigestSignUpdate(mdctx, msg.data, msg.len) != 1)
+ if (EVP_DigestSignInit(mdctx, NULL, md, NULL, pkp) != 1)
goto fail_digest;
- if (EVP_DigestSignFinal(mdctx, NULL, &required) != 1)
+ /* Get required signature buffer size */
+ if (EVP_DigestSign(mdctx, NULL, &required, msg.data, msg.len) != 1)
goto fail_digest;
sig->data = malloc(required);
if (sig->data == NULL)
goto fail_digest;
- if (EVP_DigestSignFinal(mdctx, sig->data, &required) != 1)
+ if (EVP_DigestSign(mdctx, sig->data, &required, msg.data, msg.len) != 1)
goto fail_sign;
sig->len = required;
@@ -751,29 +1787,27 @@ int openssl_sign(void * pkp,
return -1;
}
-int openssl_verify_sig(void * pk,
- buffer_t msg,
- buffer_t sig)
+int openssl_verify_sig(EVP_PKEY * pk,
+ int nid,
+ buffer_t msg,
+ buffer_t sig)
{
- EVP_PKEY * pkey;
- EVP_MD_CTX * mdctx;
- int ret;
+ EVP_MD_CTX * mdctx;
+ const EVP_MD * md;
+ int ret;
assert(pk != NULL);
- pkey = (EVP_PKEY *) pk;
-
mdctx = EVP_MD_CTX_new();
if (!mdctx)
goto fail_ctx;
- if (EVP_DigestVerifyInit(mdctx, NULL, EVP_sha256(), NULL, pkey) != 1)
- goto fail_digest;
+ md = select_md(pk, nid);
- if (EVP_DigestVerifyUpdate(mdctx, msg.data, msg.len) != 1)
+ if (EVP_DigestVerifyInit(mdctx, NULL, md, NULL, pk) != 1)
goto fail_digest;
- ret = EVP_DigestVerifyFinal(mdctx, sig.data, sig.len);
+ ret = EVP_DigestVerify(mdctx, sig.data, sig.len, msg.data, msg.len);
if (ret != 1)
goto fail_digest;
@@ -786,3 +1820,61 @@ int openssl_verify_sig(void * pk,
clrbuf(sig);
return -1;
}
+
+ssize_t openssl_md_digest(int nid,
+ buffer_t in,
+ uint8_t * out)
+{
+ const EVP_MD * md;
+ unsigned int len;
+
+ assert(in.data != NULL);
+ assert(out != NULL);
+
+ md = EVP_get_digestbynid(nid);
+ if (md == NULL)
+ return -1;
+
+ if (EVP_Digest(in.data, in.len, out, &len, md, NULL) != 1)
+ return -1;
+
+ return (ssize_t) len;
+}
+
+ssize_t openssl_md_len(int nid)
+{
+ const EVP_MD * md;
+
+ md = EVP_get_digestbynid(nid);
+ if (md == NULL)
+ return -1;
+
+ return (ssize_t) EVP_MD_get_size(md);
+}
+
+int openssl_secure_malloc_init(size_t max,
+ size_t guard)
+{
+ return CRYPTO_secure_malloc_init(max, guard) == 1 ? 0 : -1;
+}
+
+void openssl_secure_malloc_fini(void)
+{
+ CRYPTO_secure_malloc_done();
+}
+
+void * openssl_secure_malloc(size_t size)
+{
+ return OPENSSL_secure_malloc(size);
+}
+
+void openssl_secure_free(void * ptr)
+{
+ OPENSSL_secure_free(ptr);
+}
+
+void openssl_secure_clear(void * ptr,
+ size_t size)
+{
+ OPENSSL_cleanse(ptr, size);
+}
diff --git a/src/lib/crypt/openssl.h b/src/lib/crypt/openssl.h
index d4ee73b9..b95d1b0b 100644
--- a/src/lib/crypt/openssl.h
+++ b/src/lib/crypt/openssl.h
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* OpenSSL based cryptographic operations
* Elliptic curve Diffie-Hellman key exchange
@@ -26,28 +26,56 @@
#ifndef OUROBOROS_LIB_CRYPT_OPENSSL_H
#define OUROBOROS_LIB_CRYPT_OPENSSL_H
-ssize_t openssl_ecdh_pkp_create(void ** pkp,
- uint8_t * pk);
+struct ossl_crypt_ctx;
-void openssl_ecdh_pkp_destroy(void * pkp);
+ssize_t openssl_pkp_create(const char * algo,
+ EVP_PKEY ** pkp,
+ uint8_t * pk);
-int openssl_ecdh_derive(void * pkp,
- buffer_t pk,
- uint8_t * s);
+void openssl_pkp_destroy(EVP_PKEY * pkp);
-int openssl_encrypt(void * ctx,
- uint8_t * key,
- buffer_t in,
- buffer_t * out);
+int openssl_dhe_derive(EVP_PKEY * pkp,
+ buffer_t pk,
+ int kdf_nid,
+ uint8_t * s);
-int openssl_decrypt(void * ctx,
- uint8_t * key,
- buffer_t in,
- buffer_t * out);
+ssize_t openssl_kem_encap(buffer_t pk,
+ uint8_t * ct,
+ int kdf_nid,
+ uint8_t * s);
-void * openssl_crypt_create_ctx(void);
+/* no X509 DER support yet for DHKEM public keys */
+ssize_t openssl_kem_encap_raw(buffer_t pk,
+ uint8_t * ct,
+ int kdf_nid,
+ uint8_t * s);
-void openssl_crypt_destroy_ctx(void * ctx);
+int openssl_kem_decap(EVP_PKEY * priv,
+ buffer_t ct,
+ int kdf_nid,
+ uint8_t * s);
+
+int openssl_get_algo_from_pk_der(buffer_t pk,
+ char * algo);
+
+int openssl_get_algo_from_pk_raw(buffer_t pk,
+ char * algo);
+
+int openssl_encrypt(struct ossl_crypt_ctx * ctx,
+ buffer_t in,
+ buffer_t * out);
+
+int openssl_decrypt(struct ossl_crypt_ctx * ctx,
+ buffer_t in,
+ buffer_t * out);
+
+struct ossl_crypt_ctx * openssl_crypt_create_ctx(struct crypt_sk * sk);
+
+void openssl_crypt_destroy_ctx(struct ossl_crypt_ctx * ctx);
+
+int openssl_crypt_get_ivsz(struct ossl_crypt_ctx * ctx);
+
+int openssl_crypt_get_tagsz(struct ossl_crypt_ctx * ctx);
/* AUTHENTICATION */
@@ -76,15 +104,25 @@ int openssl_load_pubkey_file(const char * path,
int openssl_load_pubkey_str(const char * str,
void ** key);
+int openssl_load_pubkey_file_to_der(const char * path,
+ buffer_t * buf);
+int openssl_load_pubkey_raw_file(const char * path,
+ buffer_t * buf);
+
+int openssl_load_privkey_raw_file(const char * path,
+ void ** key);
-int openssl_cmp_key(const void * key1,
- const void * key2);
+int openssl_cmp_key(const EVP_PKEY * key1,
+ const EVP_PKEY * key2);
-void openssl_free_key(void * key);
+void openssl_free_key(EVP_PKEY * key);
int openssl_check_crt_name(void * crt,
const char * name);
+int openssl_get_crt_name(void * crt,
+ char * name);
+
int openssl_crt_str(const void * crt,
char * str);
@@ -101,12 +139,34 @@ int openssl_auth_add_crt_to_store(void * store,
int openssl_verify_crt(void * store,
void * crt);
-int openssl_sign(void * pkp,
+int openssl_sign(EVP_PKEY * pkp,
+ int md_nid,
buffer_t msg,
buffer_t * sig);
-int openssl_verify_sig(void * pk,
- buffer_t msg,
- buffer_t sig);
+int openssl_verify_sig(EVP_PKEY * pk,
+ int md_nid,
+ buffer_t msg,
+ buffer_t sig);
+
+ssize_t openssl_md_digest(int md_nid,
+ buffer_t in,
+ uint8_t * out);
+
+ssize_t openssl_md_len(int md_nid);
+
+/* Secure memory allocation */
+int openssl_secure_malloc_init(size_t max,
+ size_t guard);
+
+void openssl_secure_malloc_fini(void);
+
+void * openssl_secure_malloc(size_t size);
+
+void openssl_secure_free(void * ptr,
+ size_t size);
+
+void openssl_secure_clear(void * ptr,
+ size_t size);
#endif /* OUROBOROS_LIB_CRYPT_OPENSSL_H */
diff --git a/src/lib/dev.c b/src/lib/dev.c
index cb483aca..9cfc24ee 100644
--- a/src/lib/dev.c
+++ b/src/lib/dev.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* API for applications
*
@@ -27,6 +27,7 @@
#endif
#include "config.h"
+#include "ssm.h"
#include <ouroboros/bitmap.h>
#include <ouroboros/cep.h>
@@ -45,9 +46,9 @@
#include <ouroboros/pthread.h>
#include <ouroboros/random.h>
#include <ouroboros/serdes-irm.h>
-#include <ouroboros/shm_flow_set.h>
-#include <ouroboros/shm_rdrbuff.h>
-#include <ouroboros/shm_rbuff.h>
+#include <ouroboros/ssm_flow_set.h>
+#include <ouroboros/ssm_pool.h>
+#include <ouroboros/ssm_rbuff.h>
#include <ouroboros/sockets.h>
#include <ouroboros/utils.h>
#ifdef PROC_FLOW_STATS
@@ -92,14 +93,16 @@ struct flow {
struct flow_info info;
- struct shm_rbuff * rx_rb;
- struct shm_rbuff * tx_rb;
- struct shm_flow_set * set;
+ struct ssm_rbuff * rx_rb;
+ struct ssm_rbuff * tx_rb;
+ struct ssm_flow_set * set;
uint16_t oflags;
ssize_t part_idx;
struct crypt_ctx * crypt;
+ int headsz; /* IV */
+ int tailsz; /* Tag + CRC */
struct timespec snd_act;
struct timespec rcv_act;
@@ -118,14 +121,14 @@ struct flow_set {
};
struct fqueue {
- struct flowevent fqueue[SHM_BUFFER_SIZE]; /* Safe copy from shm. */
+ struct flowevent fqueue[SSM_RBUFF_SIZE]; /* Safe copy from shm. */
size_t fqsize;
size_t next;
};
struct {
- struct shm_rdrbuff * rdrb;
- struct shm_flow_set * fqset;
+ struct ssm_pool * pool;
+ struct ssm_flow_set * fqset;
struct bmp * fds;
struct bmp * fqueues;
@@ -143,14 +146,14 @@ struct {
fset_t * frct_set;
pthread_rwlock_t lock;
-} ai;
+} proc;
static void flow_destroy(struct fmap * p)
{
- pthread_mutex_lock(&ai.mtx);
+ pthread_mutex_lock(&proc.mtx);
if (p->state == FLOW_DESTROY) {
- pthread_mutex_unlock(&ai.mtx);
+ pthread_mutex_unlock(&proc.mtx);
return;
}
@@ -159,12 +162,12 @@ static void flow_destroy(struct fmap * p)
else
p->state = FLOW_NULL;
- pthread_cond_signal(&ai.cond);
+ pthread_cond_signal(&proc.cond);
- pthread_cleanup_push(__cleanup_mutex_unlock, &ai.mtx);
+ pthread_cleanup_push(__cleanup_mutex_unlock, &proc.mtx);
while (p->state != FLOW_NULL)
- pthread_cond_wait(&ai.cond, &ai.mtx);
+ pthread_cond_wait(&proc.cond, &proc.mtx);
p->fd = -1;
p->state = FLOW_INIT;
@@ -175,17 +178,17 @@ static void flow_destroy(struct fmap * p)
static void flow_set_state(struct fmap * p,
enum flow_state state)
{
- pthread_mutex_lock(&ai.mtx);
+ pthread_mutex_lock(&proc.mtx);
if (p->state == FLOW_DESTROY) {
- pthread_mutex_unlock(&ai.mtx);
+ pthread_mutex_unlock(&proc.mtx);
return;
}
p->state = state;
- pthread_cond_broadcast(&ai.cond);
+ pthread_cond_broadcast(&proc.cond);
- pthread_mutex_unlock(&ai.mtx);
+ pthread_mutex_unlock(&proc.mtx);
}
static enum flow_state flow_wait_assign(int flow_id)
@@ -193,26 +196,26 @@ static enum flow_state flow_wait_assign(int flow_id)
enum flow_state state;
struct fmap * p;
- p = &ai.id_to_fd[flow_id];
+ p = &proc.id_to_fd[flow_id];
- pthread_mutex_lock(&ai.mtx);
+ pthread_mutex_lock(&proc.mtx);
if (p->state == FLOW_ALLOCATED) {
- pthread_mutex_unlock(&ai.mtx);
+ pthread_mutex_unlock(&proc.mtx);
return FLOW_ALLOCATED;
}
if (p->state == FLOW_INIT)
p->state = FLOW_ALLOC_PENDING;
- pthread_cleanup_push(__cleanup_mutex_unlock, &ai.mtx);
+ pthread_cleanup_push(__cleanup_mutex_unlock, &proc.mtx);
while (p->state == FLOW_ALLOC_PENDING)
- pthread_cond_wait(&ai.cond, &ai.mtx);
+ pthread_cond_wait(&proc.cond, &proc.mtx);
if (p->state == FLOW_DESTROY) {
p->state = FLOW_NULL;
- pthread_cond_broadcast(&ai.cond);
+ pthread_cond_broadcast(&proc.cond);
}
state = p->state;
@@ -224,13 +227,13 @@ static enum flow_state flow_wait_assign(int flow_id)
return state;
}
-static int proc_announce(const char * prog)
+static int proc_announce(const struct proc_info * proc)
{
uint8_t buf[SOCK_BUF_SIZE];
buffer_t msg = {SOCK_BUF_SIZE, buf};
int err;
- if (proc_announce__irm_req_ser(&msg, prog) < 0)
+ if (proc_announce__irm_req_ser(&msg, proc) < 0)
return -ENOMEM;
err = send_recv_msg(&msg);
@@ -252,8 +255,8 @@ static void proc_exit(void)
send_recv_msg(&msg);
}
-static int sdb_encrypt(struct flow * flow,
- struct shm_du_buff * sdb)
+static int spb_encrypt(struct flow * flow,
+ struct ssm_pk_buff * spb)
{
buffer_t in;
buffer_t out;
@@ -263,17 +266,17 @@ static int sdb_encrypt(struct flow * flow,
if (flow->crypt == NULL)
return 0; /* No encryption */
- in.data = shm_du_buff_head(sdb);
- in.len = shm_du_buff_len(sdb);
+ in.data = ssm_pk_buff_head(spb);
+ in.len = ssm_pk_buff_len(spb);
if (crypt_encrypt(flow->crypt, in, &out) < 0)
goto fail_encrypt;
- head = shm_du_buff_head_alloc(sdb, IVSZ);
+ head = ssm_pk_buff_head_alloc(spb, flow->headsz);
if (head == NULL)
goto fail_alloc;
- tail = shm_du_buff_tail_alloc(sdb, (out.len - in.len) - IVSZ);
+ tail = ssm_pk_buff_tail_alloc(spb, flow->tailsz);
if (tail == NULL)
goto fail_alloc;
@@ -288,8 +291,8 @@ static int sdb_encrypt(struct flow * flow,
return -ECRYPT;
}
-static int sdb_decrypt(struct flow * flow,
- struct shm_du_buff * sdb)
+static int spb_decrypt(struct flow * flow,
+ struct ssm_pk_buff * spb)
{
buffer_t in;
buffer_t out;
@@ -298,15 +301,15 @@ static int sdb_decrypt(struct flow * flow,
if (flow->crypt == NULL)
return 0; /* No decryption */
- in.data = shm_du_buff_head(sdb);
- in.len = shm_du_buff_len(sdb);
+ in.data = ssm_pk_buff_head(spb);
+ in.len = ssm_pk_buff_len(spb);
if (crypt_decrypt(flow->crypt, in, &out) < 0)
return -ENOMEM;
- head = shm_du_buff_head_release(sdb, IVSZ) + IVSZ;
- shm_du_buff_tail_release(sdb, (in.len - out.len) - IVSZ);
+ head = ssm_pk_buff_head_release(spb, flow->headsz) + flow->headsz;
+ ssm_pk_buff_tail_release(spb, flow->tailsz);
memcpy(head, out.data, out.len);
@@ -335,27 +338,27 @@ void * flow_tx(void * o)
static void flow_send_keepalive(struct flow * flow,
struct timespec now)
{
- struct shm_du_buff * sdb;
+ struct ssm_pk_buff * spb;
ssize_t idx;
uint8_t * ptr;
- idx = shm_rdrbuff_alloc(ai.rdrb, 0, &ptr, &sdb);
+ idx = ssm_pool_alloc(proc.pool, 0, &ptr, &spb);
if (idx < 0)
return;
- pthread_rwlock_wrlock(&ai.lock);
+ pthread_rwlock_wrlock(&proc.lock);
flow->snd_act = now;
- if (shm_rbuff_write(flow->tx_rb, idx))
- shm_rdrbuff_remove(ai.rdrb, idx);
+ if (ssm_rbuff_write(flow->tx_rb, idx))
+ ssm_pool_remove(proc.pool, idx);
else
- shm_flow_set_notify(flow->set, flow->info.id, FLOW_PKT);
+ ssm_flow_set_notify(flow->set, flow->info.id, FLOW_PKT);
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
}
-/* Needs rdlock on ai. */
+/* Needs rdlock on proc. */
static void _flow_keepalive(struct flow * flow)
{
struct timespec now;
@@ -371,24 +374,24 @@ static void _flow_keepalive(struct flow * flow)
flow_id = flow->info.id;
timeo = flow->info.qs.timeout;
- acl = shm_rbuff_get_acl(flow->rx_rb);
+ acl = ssm_rbuff_get_acl(flow->rx_rb);
if (timeo == 0 || acl & (ACL_FLOWPEER | ACL_FLOWDOWN))
return;
clock_gettime(PTHREAD_COND_CLOCK, &now);
if (ts_diff_ns(&now, &r_act) > (int64_t) timeo * MILLION) {
- shm_rbuff_set_acl(flow->rx_rb, ACL_FLOWPEER);
- shm_flow_set_notify(ai.fqset, flow_id, FLOW_PEER);
+ ssm_rbuff_set_acl(flow->rx_rb, ACL_FLOWPEER);
+ ssm_flow_set_notify(proc.fqset, flow_id, FLOW_PEER);
return;
}
if (ts_diff_ns(&now, &s_act) > (int64_t) timeo * (MILLION >> 2)) {
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
flow_send_keepalive(flow, now);
- pthread_rwlock_rdlock(&ai.lock);
+ pthread_rwlock_rdlock(&proc.lock);
}
}
@@ -397,15 +400,15 @@ static void handle_keepalives(void)
struct list_head * p;
struct list_head * h;
- pthread_rwlock_rdlock(&ai.lock);
+ pthread_rwlock_rdlock(&proc.lock);
- list_for_each_safe(p, h, &ai.flow_list) {
+ list_for_each_safe(p, h, &proc.flow_list) {
struct flow * flow;
flow = list_entry(p, struct flow, next);
_flow_keepalive(flow);
}
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
}
static void __cleanup_fqueue_destroy(void * fq)
@@ -426,7 +429,7 @@ void * flow_rx(void * o)
pthread_cleanup_push(__cleanup_fqueue_destroy, fq);
/* fevent will filter all FRCT packets for us */
- while ((ret = fevent(ai.frct_set, fq, &tic)) != 0) {
+ while ((ret = fevent(proc.frct_set, fq, &tic)) != 0) {
if (ret == -ETIMEDOUT) {
handle_keepalives();
continue;
@@ -443,67 +446,69 @@ void * flow_rx(void * o)
static void flow_clear(int fd)
{
- memset(&ai.flows[fd], 0, sizeof(ai.flows[fd]));
+ memset(&proc.flows[fd], 0, sizeof(proc.flows[fd]));
- ai.flows[fd].info.id = -1;
+ proc.flows[fd].info.id = -1;
}
static void __flow_fini(int fd)
{
assert(fd >= 0 && fd < SYS_MAX_FLOWS);
- if (ai.flows[fd].frcti != NULL) {
- ai.n_frcti--;
- if (ai.n_frcti == 0) {
- pthread_cancel(ai.tx);
- pthread_join(ai.tx, NULL);
+ if (proc.flows[fd].frcti != NULL) {
+ proc.n_frcti--;
+ if (proc.n_frcti == 0) {
+ pthread_cancel(proc.tx);
+ pthread_join(proc.tx, NULL);
}
- shm_flow_set_del(ai.fqset, 0, ai.flows[fd].info.id);
+ ssm_flow_set_del(proc.fqset, 0, proc.flows[fd].info.id);
- frcti_destroy(ai.flows[fd].frcti);
+ frcti_destroy(proc.flows[fd].frcti);
}
- if (ai.flows[fd].info.id != -1) {
- flow_destroy(&ai.id_to_fd[ai.flows[fd].info.id]);
- bmp_release(ai.fds, fd);
+ if (proc.flows[fd].info.id != -1) {
+ flow_destroy(&proc.id_to_fd[proc.flows[fd].info.id]);
+ bmp_release(proc.fds, fd);
}
- if (ai.flows[fd].rx_rb != NULL) {
- shm_rbuff_set_acl(ai.flows[fd].rx_rb, ACL_FLOWDOWN);
- shm_rbuff_close(ai.flows[fd].rx_rb);
+ if (proc.flows[fd].rx_rb != NULL) {
+ ssm_rbuff_set_acl(proc.flows[fd].rx_rb, ACL_FLOWDOWN);
+ ssm_rbuff_close(proc.flows[fd].rx_rb);
}
- if (ai.flows[fd].tx_rb != NULL) {
- shm_rbuff_set_acl(ai.flows[fd].tx_rb, ACL_FLOWDOWN);
- shm_rbuff_close(ai.flows[fd].tx_rb);
+ if (proc.flows[fd].tx_rb != NULL) {
+ ssm_rbuff_set_acl(proc.flows[fd].tx_rb, ACL_FLOWDOWN);
+ ssm_rbuff_close(proc.flows[fd].tx_rb);
}
- if (ai.flows[fd].set != NULL) {
- shm_flow_set_notify(ai.flows[fd].set,
- ai.flows[fd].info.id,
+ if (proc.flows[fd].set != NULL) {
+ ssm_flow_set_notify(proc.flows[fd].set,
+ proc.flows[fd].info.id,
FLOW_DEALLOC);
- shm_flow_set_close(ai.flows[fd].set);
+ ssm_flow_set_close(proc.flows[fd].set);
}
- crypt_destroy_ctx(ai.flows[fd].crypt);
+ crypt_destroy_ctx(proc.flows[fd].crypt);
- list_del(&ai.flows[fd].next);
+ list_del(&proc.flows[fd].next);
flow_clear(fd);
}
static void flow_fini(int fd)
{
- pthread_rwlock_wrlock(&ai.lock);
+ pthread_rwlock_wrlock(&proc.lock);
__flow_fini(fd);
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
}
+#define IS_ENCRYPTED(crypt) ((crypt)->nid != NID_undef)
+#define IS_ORDERED(flow) (flow.qs.in_order != 0)
static int flow_init(struct flow_info * info,
- buffer_t * sk)
+ struct crypt_sk * sk)
{
struct timespec now;
struct flow * flow;
@@ -512,27 +517,27 @@ static int flow_init(struct flow_info * info,
clock_gettime(PTHREAD_COND_CLOCK, &now);
- pthread_rwlock_wrlock(&ai.lock);
+ pthread_rwlock_wrlock(&proc.lock);
- fd = bmp_allocate(ai.fds);
- if (!bmp_is_id_valid(ai.fds, fd)) {
+ fd = bmp_allocate(proc.fds);
+ if (!bmp_is_id_valid(proc.fds, fd)) {
err = -EBADF;
goto fail_fds;
}
- flow = &ai.flows[fd];
+ flow = &proc.flows[fd];
flow->info = *info;
- flow->rx_rb = shm_rbuff_open(info->n_pid, info->id);
+ flow->rx_rb = ssm_rbuff_open(info->n_pid, info->id);
if (flow->rx_rb == NULL)
goto fail_rx_rb;
- flow->tx_rb = shm_rbuff_open(info->n_1_pid, info->id);
+ flow->tx_rb = ssm_rbuff_open(info->n_1_pid, info->id);
if (flow->tx_rb == NULL)
goto fail_tx_rb;
- flow->set = shm_flow_set_open(info->n_1_pid);
+ flow->set = ssm_flow_set_open(info->n_1_pid);
if (flow->set == NULL)
goto fail_set;
@@ -541,56 +546,61 @@ static int flow_init(struct flow_info * info,
flow->snd_act = now;
flow->rcv_act = now;
flow->crypt = NULL;
+ flow->headsz = 0;
+ flow->tailsz = 0;
- if (sk!= NULL && sk->data != NULL) {
- assert(sk->len == SYMMKEYSZ);
- flow->crypt = crypt_create_ctx(sk->data);
+ if (IS_ENCRYPTED(sk)) {
+ /* Set to lower value in tests, should we make configurable? */
+ sk->rot_bit = KEY_ROTATION_BIT;
+ flow->crypt = crypt_create_ctx(sk);
if (flow->crypt == NULL)
goto fail_crypt;
+ flow->headsz = crypt_get_ivsz(flow->crypt);
+ flow->tailsz = crypt_get_tagsz(flow->crypt);
}
assert(flow->frcti == NULL);
- if (info->qs.in_order != 0) {
+ if (IS_ORDERED(flow->info)) {
flow->frcti = frcti_create(fd, DELT_A, DELT_R, info->mpl);
if (flow->frcti == NULL)
goto fail_frcti;
- if (shm_flow_set_add(ai.fqset, 0, info->id))
+ if (ssm_flow_set_add(proc.fqset, 0, info->id))
goto fail_flow_set_add;
- ++ai.n_frcti;
- if (ai.n_frcti == 1 &&
- pthread_create(&ai.tx, NULL, flow_tx, NULL) < 0)
+ ++proc.n_frcti;
+ if (proc.n_frcti == 1 &&
+ pthread_create(&proc.tx, NULL, flow_tx, NULL) < 0)
goto fail_tx_thread;
}
- list_add_tail(&flow->next, &ai.flow_list);
+ list_add_tail(&flow->next, &proc.flow_list);
- ai.id_to_fd[info->id].fd = fd;
+ proc.id_to_fd[info->id].fd = fd;
- flow_set_state(&ai.id_to_fd[info->id], FLOW_ALLOCATED);
+ flow_set_state(&proc.id_to_fd[info->id], FLOW_ALLOCATED);
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return fd;
fail_tx_thread:
- shm_flow_set_del(ai.fqset, 0, info->id);
+ ssm_flow_set_del(proc.fqset, 0, info->id);
fail_flow_set_add:
frcti_destroy(flow->frcti);
fail_frcti:
crypt_destroy_ctx(flow->crypt);
fail_crypt:
- shm_flow_set_close(flow->set);
+ ssm_flow_set_close(flow->set);
fail_set:
- shm_rbuff_close(flow->tx_rb);
+ ssm_rbuff_close(flow->tx_rb);
fail_tx_rb:
- shm_rbuff_close(flow->rx_rb);
+ ssm_rbuff_close(flow->rx_rb);
fail_rx_rb:
- bmp_release(ai.fds, fd);
+ bmp_release(proc.fds, fd);
fail_fds:
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return err;
}
@@ -608,6 +618,7 @@ static void init(int argc,
char ** argv,
char ** envp)
{
+ struct proc_info info;
char * prog = argv[0];
int i;
#ifdef PROC_FLOW_STATS
@@ -625,7 +636,11 @@ static void init(int argc,
goto fail_prog;
}
- if (proc_announce(prog)) {
+ memset(&info, 0, sizeof(info));
+ info.pid = getpid();
+ strncpy(info.prog, prog, PROG_NAME_SIZE);
+
+ if (proc_announce(&info)) {
fprintf(stderr, "FATAL: Could not announce to IRMd.\n");
goto fail_prog;
}
@@ -640,26 +655,30 @@ static void init(int argc,
gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
}
#endif
- ai.fds = bmp_create(PROG_MAX_FLOWS - PROG_RES_FDS, PROG_RES_FDS);
- if (ai.fds == NULL) {
+ proc.fds = bmp_create(PROG_MAX_FLOWS - PROG_RES_FDS, PROG_RES_FDS);
+ if (proc.fds == NULL) {
fprintf(stderr, "FATAL: Could not create fd bitmap.\n");
goto fail_fds;
}
- ai.fqueues = bmp_create(PROG_MAX_FQUEUES, 0);
- if (ai.fqueues == NULL) {
+ proc.fqueues = bmp_create(PROG_MAX_FQUEUES, 0);
+ if (proc.fqueues == NULL) {
fprintf(stderr, "FATAL: Could not create fqueue bitmap.\n");
goto fail_fqueues;
}
- ai.rdrb = shm_rdrbuff_open();
- if (ai.rdrb == NULL) {
+ if (is_ouroboros_member_uid(getuid()))
+ proc.pool = ssm_pool_open(0);
+ else
+ proc.pool = ssm_pool_open(getuid());
+
+ if (proc.pool == NULL) {
fprintf(stderr, "FATAL: Could not open packet buffer.\n");
goto fail_rdrb;
}
- ai.flows = malloc(sizeof(*ai.flows) * PROG_MAX_FLOWS);
- if (ai.flows == NULL) {
+ proc.flows = malloc(sizeof(*proc.flows) * PROG_MAX_FLOWS);
+ if (proc.flows == NULL) {
fprintf(stderr, "FATAL: Could not malloc flows.\n");
goto fail_flows;
}
@@ -667,38 +686,38 @@ static void init(int argc,
for (i = 0; i < PROG_MAX_FLOWS; ++i)
flow_clear(i);
- ai.id_to_fd = malloc(sizeof(*ai.id_to_fd) * SYS_MAX_FLOWS);
- if (ai.id_to_fd == NULL) {
+ proc.id_to_fd = malloc(sizeof(*proc.id_to_fd) * SYS_MAX_FLOWS);
+ if (proc.id_to_fd == NULL) {
fprintf(stderr, "FATAL: Could not malloc id_to_fd.\n");
goto fail_id_to_fd;
}
for (i = 0; i < SYS_MAX_FLOWS; ++i)
- ai.id_to_fd[i].state = FLOW_INIT;
+ proc.id_to_fd[i].state = FLOW_INIT;
- if (pthread_mutex_init(&ai.mtx, NULL)) {
+ if (pthread_mutex_init(&proc.mtx, NULL)) {
fprintf(stderr, "FATAL: Could not init mutex.\n");
goto fail_mtx;
}
- if (pthread_cond_init(&ai.cond, NULL) < 0) {
+ if (pthread_cond_init(&proc.cond, NULL) < 0) {
fprintf(stderr, "FATAL: Could not init condvar.\n");
goto fail_cond;
}
- if (pthread_rwlock_init(&ai.lock, NULL) < 0) {
+ if (pthread_rwlock_init(&proc.lock, NULL) < 0) {
fprintf(stderr, "FATAL: Could not initialize flow lock.\n");
goto fail_flow_lock;
}
- ai.fqset = shm_flow_set_open(getpid());
- if (ai.fqset == NULL) {
+ proc.fqset = ssm_flow_set_open(getpid());
+ if (proc.fqset == NULL) {
fprintf(stderr, "FATAL: Could not open flow set.\n");
goto fail_fqset;
}
- ai.frct_set = fset_create();
- if (ai.frct_set == NULL || ai.frct_set->idx != 0) {
+ proc.frct_set = fset_create();
+ if (proc.frct_set == NULL || proc.frct_set->idx != 0) {
fprintf(stderr, "FATAL: Could not create FRCT set.\n");
goto fail_frct_set;
}
@@ -708,6 +727,11 @@ static void init(int argc,
goto fail_timerwheel;
}
+ if (crypt_secure_malloc_init(PROC_SECMEM_MAX) < 0) {
+ fprintf(stderr, "FATAL: Could not init secure malloc.\n");
+ goto fail_timerwheel;
+ }
+
#if defined PROC_FLOW_STATS
if (strstr(argv[0], "ipcpd") == NULL) {
sprintf(procstr, "proc.%d", getpid());
@@ -717,12 +741,12 @@ static void init(int argc,
}
}
#endif
- if (pthread_create(&ai.rx, NULL, flow_rx, NULL) < 0) {
+ if (pthread_create(&proc.rx, NULL, flow_rx, NULL) < 0) {
fprintf(stderr, "FATAL: Could not start monitor thread.\n");
goto fail_monitor;
}
- list_head_init(&ai.flow_list);
+ list_head_init(&proc.flow_list);
return;
@@ -733,27 +757,27 @@ static void init(int argc,
#endif
timerwheel_fini();
fail_timerwheel:
- fset_destroy(ai.frct_set);
+ fset_destroy(proc.frct_set);
fail_frct_set:
- shm_flow_set_close(ai.fqset);
+ ssm_flow_set_close(proc.fqset);
fail_fqset:
- pthread_rwlock_destroy(&ai.lock);
+ pthread_rwlock_destroy(&proc.lock);
fail_flow_lock:
- pthread_cond_destroy(&ai.cond);
+ pthread_cond_destroy(&proc.cond);
fail_cond:
- pthread_mutex_destroy(&ai.mtx);
+ pthread_mutex_destroy(&proc.mtx);
fail_mtx:
- free(ai.id_to_fd);
+ free(proc.id_to_fd);
fail_id_to_fd:
- free(ai.flows);
+ free(proc.flows);
fail_flows:
- shm_rdrbuff_close(ai.rdrb);
+ ssm_pool_close(proc.pool);
fail_rdrb:
- bmp_destroy(ai.fqueues);
+ bmp_destroy(proc.fqueues);
fail_fqueues:
- bmp_destroy(ai.fds);
+ bmp_destroy(proc.fds);
fail_fds:
- memset(&ai, 0, sizeof(ai));
+ memset(&proc, 0, sizeof(proc));
fail_prog:
exit(EXIT_FAILURE);
}
@@ -762,51 +786,52 @@ static void fini(void)
{
int i;
- if (ai.fds == NULL)
+ if (proc.fds == NULL)
return;
- pthread_cancel(ai.rx);
- pthread_join(ai.rx, NULL);
+ pthread_cancel(proc.rx);
+ pthread_join(proc.rx, NULL);
- pthread_rwlock_wrlock(&ai.lock);
+ pthread_rwlock_wrlock(&proc.lock);
for (i = 0; i < PROG_MAX_FLOWS; ++i) {
- if (ai.flows[i].info.id != -1) {
+ struct flow * flow = &proc.flows[i];
+ if (flow->info.id != -1) {
ssize_t idx;
- shm_rbuff_set_acl(ai.flows[i].rx_rb, ACL_FLOWDOWN);
- while ((idx = shm_rbuff_read(ai.flows[i].rx_rb)) >= 0)
- shm_rdrbuff_remove(ai.rdrb, idx);
+ ssm_rbuff_set_acl(flow->rx_rb, ACL_FLOWDOWN);
+ while ((idx = ssm_rbuff_read(flow->rx_rb)) >= 0)
+ ssm_pool_remove(proc.pool, idx);
__flow_fini(i);
}
}
- pthread_cond_destroy(&ai.cond);
- pthread_mutex_destroy(&ai.mtx);
+ pthread_cond_destroy(&proc.cond);
+ pthread_mutex_destroy(&proc.mtx);
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
#ifdef PROC_FLOW_STATS
rib_fini();
#endif
timerwheel_fini();
- fset_destroy(ai.frct_set);
+ fset_destroy(proc.frct_set);
- shm_flow_set_close(ai.fqset);
+ ssm_flow_set_close(proc.fqset);
- pthread_rwlock_destroy(&ai.lock);
+ pthread_rwlock_destroy(&proc.lock);
- free(ai.flows);
- free(ai.id_to_fd);
+ free(proc.flows);
+ free(proc.id_to_fd);
- shm_rdrbuff_close(ai.rdrb);
+ ssm_pool_close(proc.pool);
- bmp_destroy(ai.fds);
- bmp_destroy(ai.fqueues);
+ bmp_destroy(proc.fds);
+ bmp_destroy(proc.fqueues);
proc_exit();
- memset(&ai, 0, sizeof(ai));
+ memset(&proc, 0, sizeof(proc));
}
#if defined(__MACH__) && defined(__APPLE__)
@@ -823,12 +848,13 @@ __attribute__((section(FINI_SECTION))) __typeof__(fini) * __fini = fini;
int flow_accept(qosspec_t * qs,
const struct timespec * timeo)
{
- struct flow_info flow;
- uint8_t buf[SOCK_BUF_SIZE];
- buffer_t msg = {SOCK_BUF_SIZE, buf};
- buffer_t sk;
- int fd;
- int err;
+ struct flow_info flow;
+ struct crypt_sk crypt;
+ uint8_t buf[SOCK_BUF_SIZE];
+ buffer_t msg = {SOCK_BUF_SIZE, buf};
+ uint8_t key[SYMMKEYSZ];
+ int fd;
+ int err;
#ifdef QOS_DISABLE_CRC
if (qs != NULL)
@@ -846,13 +872,15 @@ int flow_accept(qosspec_t * qs,
if (err < 0)
return err;
- err = flow__irm_result_des(&msg, &flow, &sk);
+ crypt.key = key;
+
+ err = flow__irm_result_des(&msg, &flow, &crypt);
if (err < 0)
return err;
- fd = flow_init(&flow, &sk);
+ fd = flow_init(&flow, &crypt);
- freebuf(sk);
+ crypt_secure_clear(key, SYMMKEYSZ);
if (qs != NULL)
*qs = flow.qs;
@@ -864,12 +892,13 @@ int flow_alloc(const char * dst,
qosspec_t * qs,
const struct timespec * timeo)
{
- struct flow_info flow;
- uint8_t buf[SOCK_BUF_SIZE];
- buffer_t msg = {SOCK_BUF_SIZE, buf};
- buffer_t sk; /* symmetric key */
- int fd;
- int err;
+ struct flow_info flow;
+ struct crypt_sk crypt;
+ uint8_t buf[SOCK_BUF_SIZE];
+ buffer_t msg = {SOCK_BUF_SIZE, buf};
+ uint8_t key[SYMMKEYSZ];
+ int fd;
+ int err;
#ifdef QOS_DISABLE_CRC
if (qs != NULL)
@@ -890,13 +919,15 @@ int flow_alloc(const char * dst,
return err;
}
- err = flow__irm_result_des(&msg, &flow, &sk);
+ crypt.key = key;
+
+ err = flow__irm_result_des(&msg, &flow, &crypt);
if (err < 0)
return err;
- fd = flow_init(&flow, &sk);
+ fd = flow_init(&flow, &crypt);
- freebuf(sk);
+ crypt_secure_clear(key, SYMMKEYSZ);
if (qs != NULL)
*qs = flow.qs;
@@ -907,11 +938,13 @@ int flow_alloc(const char * dst,
int flow_join(const char * dst,
const struct timespec * timeo)
{
- struct flow_info flow;
- uint8_t buf[SOCK_BUF_SIZE];
- buffer_t msg = {SOCK_BUF_SIZE, buf};
- int fd;
- int err;
+ struct flow_info flow;
+ struct crypt_sk crypt;
+ uint8_t buf[SOCK_BUF_SIZE];
+ buffer_t msg = {SOCK_BUF_SIZE, buf};
+ uint8_t key[SYMMKEYSZ];
+ int fd;
+ int err;
memset(&flow, 0, sizeof(flow));
@@ -925,11 +958,15 @@ int flow_join(const char * dst,
if (err < 0)
return err;
- err = flow__irm_result_des(&msg, &flow, NULL);
+ crypt.key = key;
+
+ err = flow__irm_result_des(&msg, &flow, &crypt);
if (err < 0)
return err;
- fd = flow_init(&flow, NULL);
+ fd = flow_init(&flow, &crypt);
+
+ crypt_secure_clear(key, SYMMKEYSZ);
return fd;
}
@@ -951,12 +988,12 @@ int flow_dealloc(int fd)
memset(&info, 0, sizeof(flow));
- flow = &ai.flows[fd];
+ flow = &proc.flows[fd];
- pthread_rwlock_rdlock(&ai.lock);
+ pthread_rwlock_rdlock(&proc.lock);
if (flow->info.id < 0) {
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return -ENOTALLOC;
}
@@ -965,21 +1002,21 @@ int flow_dealloc(int fd)
flow->rcv_timesout = true;
flow->rcv_timeo = tic;
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
flow_read(fd, buf, SOCK_BUF_SIZE);
- pthread_rwlock_rdlock(&ai.lock);
+ pthread_rwlock_rdlock(&proc.lock);
timeo.tv_sec = frcti_dealloc(flow->frcti);
while (timeo.tv_sec < 0) { /* keep the flow active for rtx */
ssize_t ret;
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
ret = flow_read(fd, pkt, PKT_BUF_LEN);
- pthread_rwlock_rdlock(&ai.lock);
+ pthread_rwlock_rdlock(&proc.lock);
timeo.tv_sec = frcti_dealloc(flow->frcti);
@@ -987,9 +1024,9 @@ int flow_dealloc(int fd)
timeo.tv_sec = -timeo.tv_sec;
}
- pthread_cleanup_push(__cleanup_rwlock_unlock, &ai.lock);
+ pthread_cleanup_push(__cleanup_rwlock_unlock, &proc.lock);
- shm_rbuff_fini(flow->tx_rb);
+ ssm_rbuff_fini(flow->tx_rb);
pthread_cleanup_pop(true);
@@ -1021,21 +1058,21 @@ int ipcp_flow_dealloc(int fd)
if (fd < 0 || fd >= SYS_MAX_FLOWS )
return -EINVAL;
- flow = &ai.flows[fd];
+ flow = &proc.flows[fd];
memset(&info, 0, sizeof(flow));
- pthread_rwlock_rdlock(&ai.lock);
+ pthread_rwlock_rdlock(&proc.lock);
if (flow->info.id < 0) {
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return -ENOTALLOC;
}
info.id = flow->info.id;
info.n_1_pid = flow->info.n_1_pid;
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
if (ipcp_flow_dealloc__irm_req_ser(&msg, &info) < 0)
return -ENOMEM;
@@ -1069,14 +1106,14 @@ int fccntl(int fd,
if (fd < 0 || fd >= SYS_MAX_FLOWS)
return -EBADF;
- flow = &ai.flows[fd];
+ flow = &proc.flows[fd];
va_start(l, cmd);
- pthread_rwlock_wrlock(&ai.lock);
+ pthread_rwlock_wrlock(&proc.lock);
if (flow->info.id < 0) {
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
va_end(l);
return -ENOTALLOC;
}
@@ -1124,16 +1161,16 @@ int fccntl(int fd,
break;
case FLOWGRXQLEN:
qlen = va_arg(l, size_t *);
- *qlen = shm_rbuff_queued(flow->rx_rb);
+ *qlen = ssm_rbuff_queued(flow->rx_rb);
break;
case FLOWGTXQLEN:
qlen = va_arg(l, size_t *);
- *qlen = shm_rbuff_queued(flow->tx_rb);
+ *qlen = ssm_rbuff_queued(flow->tx_rb);
break;
case FLOWSFLAGS:
flow->oflags = va_arg(l, uint32_t);
- rx_acl = shm_rbuff_get_acl(flow->rx_rb);
- tx_acl = shm_rbuff_get_acl(flow->rx_rb);
+ rx_acl = ssm_rbuff_get_acl(flow->rx_rb);
+ tx_acl = ssm_rbuff_get_acl(flow->rx_rb);
/*
* Making our own flow write only means making the
* the other side of the flow read only.
@@ -1146,19 +1183,19 @@ int fccntl(int fd,
if (flow->oflags & FLOWFDOWN) {
rx_acl |= ACL_FLOWDOWN;
tx_acl |= ACL_FLOWDOWN;
- shm_flow_set_notify(flow->set,
+ ssm_flow_set_notify(flow->set,
flow->info.id,
FLOW_DOWN);
} else {
rx_acl &= ~ACL_FLOWDOWN;
tx_acl &= ~ACL_FLOWDOWN;
- shm_flow_set_notify(flow->set,
+ ssm_flow_set_notify(flow->set,
flow->info.id,
FLOW_UP);
}
- shm_rbuff_set_acl(flow->rx_rb, rx_acl);
- shm_rbuff_set_acl(flow->tx_rb, tx_acl);
+ ssm_rbuff_set_acl(flow->rx_rb, rx_acl);
+ ssm_rbuff_set_acl(flow->tx_rb, tx_acl);
break;
case FLOWGFLAGS:
@@ -1182,53 +1219,56 @@ int fccntl(int fd,
*cflags = frcti_getflags(flow->frcti);
break;
default:
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
va_end(l);
return -ENOTSUP;
};
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
va_end(l);
return 0;
einval:
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
va_end(l);
return -EINVAL;
eperm:
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
va_end(l);
return -EPERM;
}
-static int chk_crc(struct shm_du_buff * sdb)
+static int chk_crc(struct ssm_pk_buff * spb)
{
uint32_t crc;
- uint8_t * head = shm_du_buff_head(sdb);
- uint8_t * tail = shm_du_buff_tail_release(sdb, CRCLEN);
+ uint8_t * head = ssm_pk_buff_head(spb);
+ uint8_t * tail = ssm_pk_buff_tail_release(spb, CRCLEN);
mem_hash(HASH_CRC32, &crc, head, tail - head);
return !(crc == *((uint32_t *) tail));
}
-static int add_crc(struct shm_du_buff * sdb)
+static int add_crc(struct ssm_pk_buff * spb)
{
- uint8_t * head = shm_du_buff_head(sdb);
- uint8_t * tail = shm_du_buff_tail_alloc(sdb, CRCLEN);
+ uint8_t * head;
+ uint8_t * tail;
+
+ tail = ssm_pk_buff_tail_alloc(spb, CRCLEN);
if (tail == NULL)
- return -1;
+ return -ENOMEM;
+ head = ssm_pk_buff_head(spb);
mem_hash(HASH_CRC32, tail, head, tail - head);
return 0;
}
-static int flow_tx_sdb(struct flow * flow,
- struct shm_du_buff * sdb,
+static int flow_tx_spb(struct flow * flow,
+ struct ssm_pk_buff * spb,
bool block,
struct timespec * abstime)
{
@@ -1238,46 +1278,46 @@ static int flow_tx_sdb(struct flow * flow,
clock_gettime(PTHREAD_COND_CLOCK, &now);
- pthread_rwlock_wrlock(&ai.lock);
+ pthread_rwlock_wrlock(&proc.lock);
flow->snd_act = now;
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
- idx = shm_du_buff_get_idx(sdb);
+ idx = ssm_pk_buff_get_idx(spb);
- pthread_rwlock_rdlock(&ai.lock);
+ pthread_rwlock_rdlock(&proc.lock);
- if (shm_du_buff_len(sdb) > 0) {
- if (frcti_snd(flow->frcti, sdb) < 0)
+ if (ssm_pk_buff_len(spb) > 0) {
+ if (frcti_snd(flow->frcti, spb) < 0)
goto enomem;
- if (sdb_encrypt(flow, sdb) < 0)
+ if (spb_encrypt(flow, spb) < 0)
goto enomem;
- if (flow->info.qs.ber == 0 && add_crc(sdb) != 0)
+ if (flow->info.qs.ber == 0 && add_crc(spb) != 0)
goto enomem;
}
- pthread_cleanup_push(__cleanup_rwlock_unlock, &ai.lock);
+ pthread_cleanup_push(__cleanup_rwlock_unlock, &proc.lock);
if (!block)
- ret = shm_rbuff_write(flow->tx_rb, idx);
+ ret = ssm_rbuff_write(flow->tx_rb, idx);
else
- ret = shm_rbuff_write_b(flow->tx_rb, idx, abstime);
+ ret = ssm_rbuff_write_b(flow->tx_rb, idx, abstime);
if (ret < 0)
- shm_rdrbuff_remove(ai.rdrb, idx);
+ ssm_pool_remove(proc.pool, idx);
else
- shm_flow_set_notify(flow->set, flow->info.id, FLOW_PKT);
+ ssm_flow_set_notify(flow->set, flow->info.id, FLOW_PKT);
pthread_cleanup_pop(true);
return 0;
enomem:
- pthread_rwlock_unlock(&ai.lock);
- shm_rdrbuff_remove(ai.rdrb, idx);
+ pthread_rwlock_unlock(&proc.lock);
+ ssm_pool_remove(proc.pool, idx);
return -ENOMEM;
}
@@ -1291,7 +1331,7 @@ ssize_t flow_write(int fd,
int flags;
struct timespec abs;
struct timespec * abstime = NULL;
- struct shm_du_buff * sdb;
+ struct ssm_pk_buff * spb;
uint8_t * ptr;
if (buf == NULL && count != 0)
@@ -1300,14 +1340,14 @@ ssize_t flow_write(int fd,
if (fd < 0 || fd >= PROG_MAX_FLOWS)
return -EBADF;
- flow = &ai.flows[fd];
+ flow = &proc.flows[fd];
clock_gettime(PTHREAD_COND_CLOCK, &abs);
- pthread_rwlock_wrlock(&ai.lock);
+ pthread_rwlock_wrlock(&proc.lock);
if (flow->info.id < 0) {
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return -ENOTALLOC;
}
@@ -1318,7 +1358,7 @@ ssize_t flow_write(int fd,
flags = flow->oflags;
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
if ((flags & FLOWFACCMODE) == FLOWFRDONLY)
return -EPERM;
@@ -1326,12 +1366,12 @@ ssize_t flow_write(int fd,
if (flags & FLOWFWNOBLOCK) {
if (!frcti_is_window_open(flow->frcti))
return -EAGAIN;
- idx = shm_rdrbuff_alloc(ai.rdrb, count, &ptr, &sdb);
+ idx = ssm_pool_alloc(proc.pool, count, &ptr, &spb);
} else {
ret = frcti_window_wait(flow->frcti, abstime);
if (ret < 0)
return ret;
- idx = shm_rdrbuff_alloc_b(ai.rdrb, count, &ptr, &sdb, abstime);
+ idx = ssm_pool_alloc_b(proc.pool, count, &ptr, &spb, abstime);
}
if (idx < 0)
@@ -1340,51 +1380,51 @@ ssize_t flow_write(int fd,
if (count > 0)
memcpy(ptr, buf, count);
- ret = flow_tx_sdb(flow, sdb, !(flags & FLOWFWNOBLOCK), abstime);
+ ret = flow_tx_spb(flow, spb, !(flags & FLOWFWNOBLOCK), abstime);
return ret < 0 ? (ssize_t) ret : (ssize_t) count;
}
static bool invalid_pkt(struct flow * flow,
- struct shm_du_buff * sdb)
+ struct ssm_pk_buff * spb)
{
- if (shm_du_buff_len(sdb) == 0)
+ if (spb == NULL || ssm_pk_buff_len(spb) == 0)
return true;
- if (flow->info.qs.ber == 0 && chk_crc(sdb) != 0)
+ if (flow->info.qs.ber == 0 && chk_crc(spb) != 0)
return true;
- if (sdb_decrypt(flow, sdb) < 0)
+ if (spb_decrypt(flow, spb) < 0)
return true;
return false;
}
-static ssize_t flow_rx_sdb(struct flow * flow,
- struct shm_du_buff ** sdb,
+static ssize_t flow_rx_spb(struct flow * flow,
+ struct ssm_pk_buff ** spb,
bool block,
struct timespec * abstime)
{
ssize_t idx;
struct timespec now;
- idx = block ? shm_rbuff_read_b(flow->rx_rb, abstime) :
- shm_rbuff_read(flow->rx_rb);
+ idx = block ? ssm_rbuff_read_b(flow->rx_rb, abstime) :
+ ssm_rbuff_read(flow->rx_rb);
if (idx < 0)
return idx;
clock_gettime(PTHREAD_COND_CLOCK, &now);
- pthread_rwlock_wrlock(&ai.lock);
+ pthread_rwlock_wrlock(&proc.lock);
flow->rcv_act = now;
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
- *sdb = shm_rdrbuff_get(ai.rdrb, idx);
+ *spb = ssm_pool_get(proc.pool, idx);
- if (invalid_pkt(flow, *sdb)) {
- shm_rdrbuff_remove(ai.rdrb, idx);
+ if (invalid_pkt(flow, *spb)) {
+ ssm_pool_remove(proc.pool, idx);
return -EAGAIN;
}
@@ -1398,7 +1438,7 @@ ssize_t flow_read(int fd,
ssize_t idx;
ssize_t n;
uint8_t * packet;
- struct shm_du_buff * sdb;
+ struct ssm_pk_buff * spb;
struct timespec abs;
struct timespec now;
struct timespec * abstime = NULL;
@@ -1409,19 +1449,19 @@ ssize_t flow_read(int fd,
if (fd < 0 || fd >= PROG_MAX_FLOWS)
return -EBADF;
- flow = &ai.flows[fd];
+ flow = &proc.flows[fd];
clock_gettime(PTHREAD_COND_CLOCK, &now);
- pthread_rwlock_rdlock(&ai.lock);
+ pthread_rwlock_rdlock(&proc.lock);
if (flow->info.id < 0) {
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return -ENOTALLOC;
}
if (flow->part_idx == DONE_PART) {
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
flow->part_idx = NO_PART;
return 0;
}
@@ -1437,61 +1477,61 @@ ssize_t flow_read(int fd,
idx = flow->part_idx;
if (idx < 0) {
while ((idx = frcti_queued_pdu(flow->frcti)) < 0) {
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
- idx = flow_rx_sdb(flow, &sdb, block, abstime);
+ idx = flow_rx_spb(flow, &spb, block, abstime);
if (idx < 0) {
if (block && idx != -EAGAIN)
return idx;
if (!block)
return idx;
- pthread_rwlock_rdlock(&ai.lock);
+ pthread_rwlock_rdlock(&proc.lock);
continue;
}
- pthread_rwlock_rdlock(&ai.lock);
+ pthread_rwlock_rdlock(&proc.lock);
- frcti_rcv(flow->frcti, sdb);
+ frcti_rcv(flow->frcti, spb);
}
}
- sdb = shm_rdrbuff_get(ai.rdrb, idx);
+ spb = ssm_pool_get(proc.pool, idx);
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
- packet = shm_du_buff_head(sdb);
+ packet = ssm_pk_buff_head(spb);
- n = shm_du_buff_len(sdb);
+ n = ssm_pk_buff_len(spb);
assert(n >= 0);
if (n <= (ssize_t) count) {
memcpy(buf, packet, n);
- ipcp_sdb_release(sdb);
+ ipcp_spb_release(spb);
- pthread_rwlock_wrlock(&ai.lock);
+ pthread_rwlock_wrlock(&proc.lock);
flow->part_idx = (partrd && n == (ssize_t) count) ?
DONE_PART : NO_PART;
flow->rcv_act = now;
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return n;
} else {
if (partrd) {
memcpy(buf, packet, count);
- shm_du_buff_head_release(sdb, n);
- pthread_rwlock_wrlock(&ai.lock);
+ ssm_pk_buff_head_release(spb, n);
+ pthread_rwlock_wrlock(&proc.lock);
flow->part_idx = idx;
flow->rcv_act = now;
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return count;
} else {
- ipcp_sdb_release(sdb);
+ ipcp_spb_release(spb);
return -EMSGSIZE;
}
}
@@ -1507,20 +1547,20 @@ struct flow_set * fset_create(void)
if (set == NULL)
goto fail_malloc;
- assert(ai.fqueues);
+ assert(proc.fqueues);
- pthread_rwlock_wrlock(&ai.lock);
+ pthread_rwlock_wrlock(&proc.lock);
- set->idx = bmp_allocate(ai.fqueues);
- if (!bmp_is_id_valid(ai.fqueues, set->idx))
+ set->idx = bmp_allocate(proc.fqueues);
+ if (!bmp_is_id_valid(proc.fqueues, set->idx))
goto fail_bmp_alloc;
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return set;
fail_bmp_alloc:
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
free(set);
fail_malloc:
return NULL;
@@ -1533,11 +1573,11 @@ void fset_destroy(struct flow_set * set)
fset_zero(set);
- pthread_rwlock_wrlock(&ai.lock);
+ pthread_rwlock_wrlock(&proc.lock);
- bmp_release(ai.fqueues, set->idx);
+ bmp_release(proc.fqueues, set->idx);
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
free(set);
}
@@ -1548,7 +1588,7 @@ struct fqueue * fqueue_create(void)
if (fq == NULL)
return NULL;
- memset(fq->fqueue, -1, SHM_BUFFER_SIZE * sizeof(*fq->fqueue));
+ memset(fq->fqueue, -1, SSM_RBUFF_SIZE * sizeof(*fq->fqueue));
fq->fqsize = 0;
fq->next = 0;
@@ -1565,7 +1605,7 @@ void fset_zero(struct flow_set * set)
if (set == NULL)
return;
- shm_flow_set_zero(ai.fqset, set->idx);
+ ssm_flow_set_zero(proc.fqset, set->idx);
}
int fset_add(struct flow_set * set,
@@ -1577,9 +1617,9 @@ int fset_add(struct flow_set * set,
if (set == NULL || fd < 0 || fd >= SYS_MAX_FLOWS)
return -EINVAL;
- flow = &ai.flows[fd];
+ flow = &proc.flows[fd];
- pthread_rwlock_rdlock(&ai.lock);
+ pthread_rwlock_rdlock(&proc.lock);
if (flow->info.id < 0) {
ret = -EINVAL;
@@ -1587,21 +1627,21 @@ int fset_add(struct flow_set * set,
}
if (flow->frcti != NULL)
- shm_flow_set_del(ai.fqset, 0, ai.flows[fd].info.id);
+ ssm_flow_set_del(proc.fqset, 0, flow->info.id);
- ret = shm_flow_set_add(ai.fqset, set->idx, ai.flows[fd].info.id);
+ ret = ssm_flow_set_add(proc.fqset, set->idx, flow->info.id);
if (ret < 0)
goto fail;
- if (shm_rbuff_queued(ai.flows[fd].rx_rb))
- shm_flow_set_notify(ai.fqset, ai.flows[fd].info.id, FLOW_PKT);
+ if (ssm_rbuff_queued(flow->rx_rb))
+ ssm_flow_set_notify(proc.fqset, flow->info.id, FLOW_PKT);
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return ret;
fail:
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return ret;
}
@@ -1613,37 +1653,40 @@ void fset_del(struct flow_set * set,
if (set == NULL || fd < 0 || fd >= SYS_MAX_FLOWS)
return;
- flow = &ai.flows[fd];
+ flow = &proc.flows[fd];
- pthread_rwlock_rdlock(&ai.lock);
+ pthread_rwlock_rdlock(&proc.lock);
if (flow->info.id >= 0)
- shm_flow_set_del(ai.fqset, set->idx, flow->info.id);
+ ssm_flow_set_del(proc.fqset, set->idx, flow->info.id);
if (flow->frcti != NULL)
- shm_flow_set_add(ai.fqset, 0, ai.flows[fd].info.id);
+ ssm_flow_set_add(proc.fqset, 0, proc.flows[fd].info.id);
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
}
bool fset_has(const struct flow_set * set,
int fd)
{
- bool ret;
+ struct flow * flow;
+ bool ret;
if (set == NULL || fd < 0 || fd >= SYS_MAX_FLOWS)
return false;
- pthread_rwlock_rdlock(&ai.lock);
+ flow = &proc.flows[fd];
+
+ pthread_rwlock_rdlock(&proc.lock);
- if (ai.flows[fd].info.id < 0) {
- pthread_rwlock_unlock(&ai.lock);
+ if (flow->info.id < 0) {
+ pthread_rwlock_unlock(&proc.lock);
return false;
}
- ret = (shm_flow_set_has(ai.fqset, set->idx, ai.flows[fd].info.id) == 1);
+ ret = (ssm_flow_set_has(proc.fqset, set->idx, flow->info.id) == 1);
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return ret;
}
@@ -1651,7 +1694,7 @@ bool fset_has(const struct flow_set * set,
/* Filter fqueue events for non-data packets */
static int fqueue_filter(struct fqueue * fq)
{
- struct shm_du_buff * sdb;
+ struct ssm_pk_buff * spb;
int fd;
ssize_t idx;
struct frcti * frcti;
@@ -1660,44 +1703,44 @@ static int fqueue_filter(struct fqueue * fq)
if (fq->fqueue[fq->next].event != FLOW_PKT)
return 1;
- pthread_rwlock_rdlock(&ai.lock);
+ pthread_rwlock_rdlock(&proc.lock);
- fd = ai.id_to_fd[fq->fqueue[fq->next].flow_id].fd;
+ fd = proc.id_to_fd[fq->fqueue[fq->next].flow_id].fd;
if (fd < 0) {
++fq->next;
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
continue;
}
- frcti = ai.flows[fd].frcti;
+ frcti = proc.flows[fd].frcti;
if (frcti == NULL) {
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return 1;
}
if (__frcti_pdu_ready(frcti) >= 0) {
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return 1;
}
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
- idx = flow_rx_sdb(&ai.flows[fd], &sdb, false, NULL);
+ idx = flow_rx_spb(&proc.flows[fd], &spb, false, NULL);
if (idx < 0)
return 0;
- pthread_rwlock_rdlock(&ai.lock);
+ pthread_rwlock_rdlock(&proc.lock);
- sdb = shm_rdrbuff_get(ai.rdrb, idx);
+ spb = ssm_pool_get(proc.pool, idx);
- __frcti_rcv(frcti, sdb);
+ __frcti_rcv(frcti, spb);
if (__frcti_pdu_ready(frcti) >= 0) {
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return 1;
}
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
++fq->next;
}
@@ -1719,15 +1762,15 @@ int fqueue_next(struct fqueue * fq)
if (fq->next != 0 && fqueue_filter(fq) == 0)
return -EPERM;
- pthread_rwlock_rdlock(&ai.lock);
+ pthread_rwlock_rdlock(&proc.lock);
e = fq->fqueue + fq->next;
- fd = ai.id_to_fd[e->flow_id].fd;
+ fd = proc.id_to_fd[e->flow_id].fd;
++fq->next;
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return fd;
}
@@ -1765,7 +1808,7 @@ ssize_t fevent(struct flow_set * set,
}
while (ret == 0) {
- ret = shm_flow_set_wait(ai.fqset, set->idx, fq->fqueue, t);
+ ret = ssm_flow_set_wait(proc.fqset, set->idx, fq->fqueue, t);
if (ret == -ETIMEDOUT)
return -ETIMEDOUT;
@@ -1785,7 +1828,8 @@ ssize_t fevent(struct flow_set * set,
int np1_flow_alloc(pid_t n_pid,
int flow_id)
{
- struct flow_info flow;
+ struct flow_info flow;
+ struct crypt_sk crypt = { .nid = NID_undef, .key = NULL };
memset(&flow, 0, sizeof(flow));
@@ -1795,7 +1839,7 @@ int np1_flow_alloc(pid_t n_pid,
flow.mpl = 0;
flow.n_1_pid = n_pid; /* This "flow" is upside-down! */
- return flow_init(&flow, NULL);
+ return flow_init(&flow, &crypt);
}
int np1_flow_dealloc(int flow_id,
@@ -1811,11 +1855,11 @@ int np1_flow_dealloc(int flow_id,
sleep(timeo);
- pthread_rwlock_rdlock(&ai.lock);
+ pthread_rwlock_rdlock(&proc.lock);
- fd = ai.id_to_fd[flow_id].fd;
+ fd = proc.id_to_fd[flow_id].fd;
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return fd;
}
@@ -1828,11 +1872,11 @@ int np1_flow_resp(int flow_id,
if (resp == 0 && flow_wait_assign(flow_id) != FLOW_ALLOCATED)
return -1;
- pthread_rwlock_rdlock(&ai.lock);
+ pthread_rwlock_rdlock(&proc.lock);
- fd = ai.id_to_fd[flow_id].fd;
+ fd = proc.id_to_fd[flow_id].fd;
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return fd;
}
@@ -1859,9 +1903,11 @@ int ipcp_flow_req_arr(const buffer_t * dst,
const buffer_t * data)
{
struct flow_info flow;
- uint8_t buf[SOCK_BUF_SIZE];
- buffer_t msg = {SOCK_BUF_SIZE, buf};
- int err;
+ uint8_t buf[SOCK_BUF_SIZE];
+ buffer_t msg = {SOCK_BUF_SIZE, buf};
+ struct crypt_sk crypt;
+ uint8_t key[SYMMKEYSZ];
+ int err;
memset(&flow, 0, sizeof(flow));
@@ -1878,17 +1924,23 @@ int ipcp_flow_req_arr(const buffer_t * dst,
if (err < 0)
return err;
- err = flow__irm_result_des(&msg, &flow, NULL);
+ crypt.key = key;
+
+ err = flow__irm_result_des(&msg, &flow, &crypt);
if (err < 0)
return err;
+ assert(crypt.nid == NID_undef); /* np1 flows are not encrypted */
+
/* inverted for np1_flow */
flow.n_1_pid = flow.n_pid;
flow.n_pid = getpid();
flow.mpl = 0;
flow.qs = qos_np1;
- return flow_init(&flow, NULL);
+ crypt.nid = NID_undef;
+
+ return flow_init(&flow, &crypt);
}
int ipcp_flow_alloc_reply(int fd,
@@ -1903,11 +1955,11 @@ int ipcp_flow_alloc_reply(int fd,
assert(fd >= 0 && fd < SYS_MAX_FLOWS);
- pthread_rwlock_rdlock(&ai.lock);
+ pthread_rwlock_rdlock(&proc.lock);
- flow.id = ai.flows[fd].info.id;
+ flow.id = proc.flows[fd].info.id;
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
flow.mpl = mpl;
@@ -1922,169 +1974,213 @@ int ipcp_flow_alloc_reply(int fd,
}
int ipcp_flow_read(int fd,
- struct shm_du_buff ** sdb)
+ struct ssm_pk_buff ** spb)
{
struct flow * flow;
ssize_t idx = -1;
assert(fd >= 0 && fd < SYS_MAX_FLOWS);
- assert(sdb);
+ assert(spb);
- flow = &ai.flows[fd];
+ flow = &proc.flows[fd];
- pthread_rwlock_rdlock(&ai.lock);
+ pthread_rwlock_rdlock(&proc.lock);
assert(flow->info.id >= 0);
while (frcti_queued_pdu(flow->frcti) < 0) {
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
- idx = flow_rx_sdb(flow, sdb, false, NULL);
+ idx = flow_rx_spb(flow, spb, false, NULL);
if (idx < 0)
return idx;
- pthread_rwlock_rdlock(&ai.lock);
+ pthread_rwlock_rdlock(&proc.lock);
- frcti_rcv(flow->frcti, *sdb);
+ frcti_rcv(flow->frcti, *spb);
}
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return 0;
}
int ipcp_flow_write(int fd,
- struct shm_du_buff * sdb)
+ struct ssm_pk_buff * spb)
{
struct flow * flow;
int ret;
assert(fd >= 0 && fd < SYS_MAX_FLOWS);
- assert(sdb);
+ assert(spb);
- flow = &ai.flows[fd];
+ flow = &proc.flows[fd];
- pthread_rwlock_wrlock(&ai.lock);
+ pthread_rwlock_wrlock(&proc.lock);
if (flow->info.id < 0) {
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return -ENOTALLOC;
}
if ((flow->oflags & FLOWFACCMODE) == FLOWFRDONLY) {
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return -EPERM;
}
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
- ret = flow_tx_sdb(flow, sdb, true, NULL);
+ ret = flow_tx_spb(flow, spb, true, NULL);
return ret;
}
+static int pool_copy_spb(struct ssm_pool * src_pool,
+ ssize_t src_idx,
+ struct ssm_pool * dst_pool,
+ struct ssm_pk_buff ** dst_spb)
+{
+ struct ssm_pk_buff * src;
+ uint8_t * ptr;
+ size_t len;
+
+ src = ssm_pool_get(src_pool, src_idx);
+ len = ssm_pk_buff_len(src);
+
+ if (ssm_pool_alloc(dst_pool, len, &ptr, dst_spb) < 0) {
+ ssm_pool_remove(src_pool, src_idx);
+ return -ENOMEM;
+ }
+
+ memcpy(ptr, ssm_pk_buff_head(src), len);
+ ssm_pool_remove(src_pool, src_idx);
+
+ return 0;
+}
+
int np1_flow_read(int fd,
- struct shm_du_buff ** sdb)
+ struct ssm_pk_buff ** spb,
+ struct ssm_pool * pool)
{
- struct flow * flow;
- ssize_t idx = -1;
+ struct flow * flow;
+ ssize_t idx = -1;
assert(fd >= 0 && fd < SYS_MAX_FLOWS);
- assert(sdb);
+ assert(spb);
- flow = &ai.flows[fd];
+ flow = &proc.flows[fd];
assert(flow->info.id >= 0);
- pthread_rwlock_rdlock(&ai.lock);
+ pthread_rwlock_rdlock(&proc.lock);
- idx = shm_rbuff_read(flow->rx_rb);
+ idx = ssm_rbuff_read(flow->rx_rb);
if (idx < 0) {
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return idx;
}
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
- *sdb = shm_rdrbuff_get(ai.rdrb, idx);
+ if (pool == NULL) {
+ *spb = ssm_pool_get(proc.pool, idx);
+ } else {
+ /* Cross-pool copy: PUP -> GSPP */
+ if (pool_copy_spb(pool, idx, proc.pool, spb) < 0)
+ return -ENOMEM;
+ }
return 0;
}
int np1_flow_write(int fd,
- struct shm_du_buff * sdb)
+ struct ssm_pk_buff * spb,
+ struct ssm_pool * pool)
{
- struct flow * flow;
- int ret;
- ssize_t idx;
+ struct flow * flow;
+ struct ssm_pk_buff * dst;
+ int ret;
+ ssize_t idx;
assert(fd >= 0 && fd < SYS_MAX_FLOWS);
- assert(sdb);
+ assert(spb);
- flow = &ai.flows[fd];
+ flow = &proc.flows[fd];
- pthread_rwlock_rdlock(&ai.lock);
+ pthread_rwlock_rdlock(&proc.lock);
if (flow->info.id < 0) {
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return -ENOTALLOC;
}
if ((flow->oflags & FLOWFACCMODE) == FLOWFRDONLY) {
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return -EPERM;
}
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
- idx = shm_du_buff_get_idx(sdb);
+ idx = ssm_pk_buff_get_idx(spb);
- ret = shm_rbuff_write_b(flow->tx_rb, idx, NULL);
- if (ret < 0)
- shm_rdrbuff_remove(ai.rdrb, idx);
- else
- shm_flow_set_notify(flow->set, flow->info.id, FLOW_PKT);
+ if (pool == NULL) {
+ ret = ssm_rbuff_write_b(flow->tx_rb, idx, NULL);
+ if (ret < 0)
+ ssm_pool_remove(proc.pool, idx);
+ else
+ ssm_flow_set_notify(flow->set, flow->info.id, FLOW_PKT);
+ } else {
+ /* Cross-pool copy: GSPP -> PUP */
+ if (pool_copy_spb(proc.pool, idx, pool, &dst) < 0)
+ return -ENOMEM;
+ idx = ssm_pk_buff_get_idx(dst);
+ ret = ssm_rbuff_write_b(flow->tx_rb, idx, NULL);
+ if (ret < 0)
+ ssm_pool_remove(pool, idx);
+ else
+ ssm_flow_set_notify(flow->set, flow->info.id, FLOW_PKT);
+ }
return ret;
}
-int ipcp_sdb_reserve(struct shm_du_buff ** sdb,
+int ipcp_spb_reserve(struct ssm_pk_buff ** spb,
size_t len)
{
- return shm_rdrbuff_alloc_b(ai.rdrb, len, NULL, sdb, NULL) < 0 ? -1 : 0;
+ return ssm_pool_alloc_b(proc.pool, len, NULL, spb, NULL) < 0 ? -1 : 0;
}
-void ipcp_sdb_release(struct shm_du_buff * sdb)
+void ipcp_spb_release(struct ssm_pk_buff * spb)
{
- shm_rdrbuff_remove(ai.rdrb, shm_du_buff_get_idx(sdb));
+ ssm_pool_remove(proc.pool, ssm_pk_buff_get_idx(spb));
}
int ipcp_flow_fini(int fd)
{
- struct shm_rbuff * rx_rb;
+ struct ssm_rbuff * rx_rb;
assert(fd >= 0 && fd < SYS_MAX_FLOWS);
- pthread_rwlock_rdlock(&ai.lock);
+ pthread_rwlock_rdlock(&proc.lock);
- if (ai.flows[fd].info.id < 0) {
- pthread_rwlock_unlock(&ai.lock);
+ if (proc.flows[fd].info.id < 0) {
+ pthread_rwlock_unlock(&proc.lock);
return -1;
}
- shm_rbuff_set_acl(ai.flows[fd].rx_rb, ACL_FLOWDOWN);
- shm_rbuff_set_acl(ai.flows[fd].tx_rb, ACL_FLOWDOWN);
+ ssm_rbuff_set_acl(proc.flows[fd].rx_rb, ACL_FLOWDOWN);
+ ssm_rbuff_set_acl(proc.flows[fd].tx_rb, ACL_FLOWDOWN);
- shm_flow_set_notify(ai.flows[fd].set,
- ai.flows[fd].info.id,
+ ssm_flow_set_notify(proc.flows[fd].set,
+ proc.flows[fd].info.id,
FLOW_DEALLOC);
- rx_rb = ai.flows[fd].rx_rb;
+ rx_rb = proc.flows[fd].rx_rb;
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
if (rx_rb != NULL)
- shm_rbuff_fini(rx_rb);
+ ssm_rbuff_fini(rx_rb);
return 0;
}
@@ -2095,13 +2191,13 @@ int ipcp_flow_get_qoscube(int fd,
assert(fd >= 0 && fd < SYS_MAX_FLOWS);
assert(cube);
- pthread_rwlock_rdlock(&ai.lock);
+ pthread_rwlock_rdlock(&proc.lock);
- assert(ai.flows[fd].info.id >= 0);
+ assert(proc.flows[fd].info.id >= 0);
- *cube = qos_spec_to_cube(ai.flows[fd].info.qs);
+ *cube = qos_spec_to_cube(proc.flows[fd].info.qs);
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return 0;
}
@@ -2110,56 +2206,76 @@ size_t ipcp_flow_queued(int fd)
{
size_t q;
- pthread_rwlock_rdlock(&ai.lock);
+ pthread_rwlock_rdlock(&proc.lock);
- assert(ai.flows[fd].info.id >= 0);
+ assert(proc.flows[fd].info.id >= 0);
- q = shm_rbuff_queued(ai.flows[fd].tx_rb);
+ q = ssm_rbuff_queued(proc.flows[fd].tx_rb);
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return q;
}
-ssize_t local_flow_read(int fd)
+int local_flow_transfer(int src_fd,
+ int dst_fd,
+ struct ssm_pool * src_pool,
+ struct ssm_pool * dst_pool)
{
- ssize_t ret;
-
- assert(fd >= 0);
-
- pthread_rwlock_rdlock(&ai.lock);
-
- ret = shm_rbuff_read(ai.flows[fd].rx_rb);
-
- pthread_rwlock_unlock(&ai.lock);
+ struct flow * src_flow;
+ struct flow * dst_flow;
+ struct ssm_pk_buff * dst_spb;
+ struct ssm_pool * sp;
+ struct ssm_pool * dp;
+ ssize_t idx;
+ int ret;
- return ret;
-}
+ assert(src_fd >= 0);
+ assert(dst_fd >= 0);
-int local_flow_write(int fd,
- size_t idx)
-{
- struct flow * flow;
- int ret;
+ src_flow = &proc.flows[src_fd];
+ dst_flow = &proc.flows[dst_fd];
- assert(fd >= 0);
+ sp = src_pool == NULL ? proc.pool : src_pool;
+ dp = dst_pool == NULL ? proc.pool : dst_pool;
- flow = &ai.flows[fd];
+ pthread_rwlock_rdlock(&proc.lock);
- pthread_rwlock_rdlock(&ai.lock);
+ idx = ssm_rbuff_read(src_flow->rx_rb);
+ if (idx < 0) {
+ pthread_rwlock_unlock(&proc.lock);
+ return idx;
+ }
- if (flow->info.id < 0) {
- pthread_rwlock_unlock(&ai.lock);
+ if (dst_flow->info.id < 0) {
+ pthread_rwlock_unlock(&proc.lock);
+ ssm_pool_remove(sp, idx);
return -ENOTALLOC;
}
- ret = shm_rbuff_write_b(flow->tx_rb, idx, NULL);
- if (ret == 0)
- shm_flow_set_notify(flow->set, flow->info.id, FLOW_PKT);
- else
- shm_rdrbuff_remove(ai.rdrb, idx);
+ pthread_rwlock_unlock(&proc.lock);
- pthread_rwlock_unlock(&ai.lock);
+ if (sp == dp) {
+ /* Same pool: zero-copy */
+ ret = ssm_rbuff_write_b(dst_flow->tx_rb, idx, NULL);
+ if (ret < 0)
+ ssm_pool_remove(sp, idx);
+ else
+ ssm_flow_set_notify(dst_flow->set,
+ dst_flow->info.id, FLOW_PKT);
+ } else {
+ /* Different pools: single copy */
+ if (pool_copy_spb(sp, idx, dp, &dst_spb) < 0)
+ return -ENOMEM;
+
+ idx = ssm_pk_buff_get_idx(dst_spb);
+ ret = ssm_rbuff_write_b(dst_flow->tx_rb, idx, NULL);
+ if (ret < 0)
+ ssm_pool_remove(dp, idx);
+ else
+ ssm_flow_set_notify(dst_flow->set,
+ dst_flow->info.id, FLOW_PKT);
+ }
return ret;
}
diff --git a/src/lib/frct.c b/src/lib/frct.c
index 08c5ea80..fad2cf69 100644
--- a/src/lib/frct.c
+++ b/src/lib/frct.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Flow and Retransmission Control
*
@@ -118,11 +118,11 @@ static int frct_rib_read(const char * path,
fd = atoi(path);
- flow = &ai.flows[fd];
+ flow = &proc.flows[fd];
clock_gettime(PTHREAD_COND_CLOCK, &now);
- pthread_rwlock_rdlock(&ai.lock);
+ pthread_rwlock_rdlock(&proc.lock);
frcti = flow->frcti;
@@ -176,7 +176,7 @@ static int frct_rib_read(const char * path,
pthread_rwlock_unlock(&flow->frcti->lock);
- pthread_rwlock_unlock(&ai.lock);
+ pthread_rwlock_unlock(&proc.lock);
return strlen(buf);
}
@@ -237,21 +237,21 @@ static void __send_frct_pkt(int fd,
uint32_t ackno,
uint32_t rwe)
{
- struct shm_du_buff * sdb;
+ struct ssm_pk_buff * spb;
struct frct_pci * pci;
ssize_t idx;
struct flow * f;
/* Raw calls needed to bypass frcti. */
#ifdef RXM_BLOCKING
- idx = shm_rdrbuff_alloc_b(ai.rdrb, sizeof(*pci), NULL, &sdb, NULL);
+ idx = ssm_pool_alloc_b(proc.pool, sizeof(*pci), NULL, &spb, NULL);
#else
- idx = shm_rdrbuff_alloc(ai.rdrb, sizeof(*pci), NULL, &sdb);
+ idx = ssm_pool_alloc(proc.pool, sizeof(*pci), NULL, &spb);
#endif
if (idx < 0)
return;
- pci = (struct frct_pci *) shm_du_buff_head(sdb);
+ pci = (struct frct_pci *) ssm_pk_buff_head(spb);
memset(pci, 0, sizeof(*pci));
*((uint32_t *) pci) = hton32(rwe);
@@ -259,24 +259,24 @@ static void __send_frct_pkt(int fd,
pci->flags = flags;
pci->ackno = hton32(ackno);
- f = &ai.flows[fd];
+ f = &proc.flows[fd];
- if (sdb_encrypt(f, sdb) < 0)
+ if (spb_encrypt(f, spb) < 0)
goto fail;
#ifdef RXM_BLOCKING
- if (shm_rbuff_write_b(f->tx_rb, idx, NULL))
+ if (ssm_rbuff_write_b(f->tx_rb, idx, NULL))
#else
- if (shm_rbuff_write(f->tx_rb, idx))
+ if (ssm_rbuff_write(f->tx_rb, idx))
#endif
goto fail;
- shm_flow_set_notify(f->set, f->info.id, FLOW_PKT);
+ ssm_flow_set_notify(f->set, f->info.id, FLOW_PKT);
return;
fail:
- ipcp_sdb_release(sdb);
+ ipcp_spb_release(spb);
return;
}
@@ -398,7 +398,7 @@ static struct frcti * frcti_create(int fd,
frcti->n_out = 0;
frcti->n_rqo = 0;
#endif
- if (ai.flows[fd].info.qs.loss == 0) {
+ if (proc.flows[fd].info.qs.loss == 0) {
frcti->snd_cr.cflags |= FRCTFRTX | FRCTFLINGER;
frcti->rcv_cr.cflags |= FRCTFRTX;
}
@@ -479,11 +479,11 @@ static void frcti_setflags(struct frcti * frcti,
#define frcti_queued_pdu(frcti) \
(frcti == NULL ? idx : __frcti_queued_pdu(frcti))
-#define frcti_snd(frcti, sdb) \
- (frcti == NULL ? 0 : __frcti_snd(frcti, sdb))
+#define frcti_snd(frcti, spb) \
+ (frcti == NULL ? 0 : __frcti_snd(frcti, spb))
-#define frcti_rcv(frcti, sdb) \
- (frcti == NULL ? 0 : __frcti_rcv(frcti, sdb))
+#define frcti_rcv(frcti, spb) \
+ (frcti == NULL ? 0 : __frcti_rcv(frcti, spb))
#define frcti_dealloc(frcti) \
(frcti == NULL ? 0 : __frcti_dealloc(frcti))
@@ -683,7 +683,7 @@ static time_t __frcti_dealloc(struct frcti * frcti)
}
static int __frcti_snd(struct frcti * frcti,
- struct shm_du_buff * sdb)
+ struct ssm_pk_buff * spb)
{
struct frct_pci * pci;
struct timespec now;
@@ -693,14 +693,14 @@ static int __frcti_snd(struct frcti * frcti,
bool rtx;
assert(frcti);
- assert(shm_du_buff_len(sdb) != 0);
+ assert(ssm_pk_buff_len(spb) != 0);
snd_cr = &frcti->snd_cr;
rcv_cr = &frcti->rcv_cr;
timerwheel_move();
- pci = (struct frct_pci *) shm_du_buff_head_alloc(sdb, FRCT_PCILEN);
+ pci = (struct frct_pci *) ssm_pk_buff_head_alloc(spb, FRCT_PCILEN);
if (pci == NULL)
return -ENOMEM;
@@ -759,7 +759,7 @@ static int __frcti_snd(struct frcti * frcti,
pthread_rwlock_unlock(&frcti->lock);
if (rtx)
- timerwheel_rxm(frcti, seqno, sdb);
+ timerwheel_rxm(frcti, seqno, spb);
return 0;
}
@@ -793,7 +793,7 @@ static void rtt_estimator(struct frcti * frcti,
/* Always queues the next application packet on the RQ. */
static void __frcti_rcv(struct frcti * frcti,
- struct shm_du_buff * sdb)
+ struct ssm_pk_buff * spb)
{
ssize_t idx;
size_t pos;
@@ -813,9 +813,9 @@ static void __frcti_rcv(struct frcti * frcti,
clock_gettime(PTHREAD_COND_CLOCK, &now);
- pci = (struct frct_pci *) shm_du_buff_head_release(sdb, FRCT_PCILEN);
+ pci = (struct frct_pci *) ssm_pk_buff_head_release(spb, FRCT_PCILEN);
- idx = shm_du_buff_get_idx(sdb);
+ idx = ssm_pk_buff_get_idx(spb);
seqno = ntoh32(pci->seqno);
pos = seqno & (RQ_SIZE - 1);
@@ -841,7 +841,7 @@ static void __frcti_rcv(struct frcti * frcti,
__send_frct_pkt(fd, FRCT_FC, 0, rwe);
- shm_rdrbuff_remove(ai.rdrb, idx);
+ ssm_pool_remove(proc.pool, idx);
return;
}
@@ -928,7 +928,7 @@ static void __frcti_rcv(struct frcti * frcti,
drop_packet:
pthread_rwlock_unlock(&frcti->lock);
- shm_rdrbuff_remove(ai.rdrb, idx);
+ ssm_pool_remove(proc.pool, idx);
send_frct_pkt(frcti);
return;
}
diff --git a/src/lib/hash.c b/src/lib/hash.c
index b465f894..7adee968 100644
--- a/src/lib/hash.c
+++ b/src/lib/hash.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Hashing
*
diff --git a/src/lib/irm.c b/src/lib/irm.c
index 8333d0d3..594014f7 100644
--- a/src/lib/irm.c
+++ b/src/lib/irm.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* The API to instruct the IRM
*
diff --git a/src/lib/list.c b/src/lib/list.c
deleted file mode 100644
index 62b2eb27..00000000
--- a/src/lib/list.c
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Ouroboros - Copyright (C) 2016 - 2024
- *
- * Simple doubly linked list implementation.
- *
- * Dimitri Staessens <dimitri@ouroboros.rocks>
- * Sander Vrijders <sander@ouroboros.rocks>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License
- * version 2.1 as published by the Free Software Foundation.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., http://www.fsf.org/about/contact/.
- */
-
-#include <ouroboros/list.h>
-
-#include <stddef.h>
-
-void list_head_init(struct list_head * h)
-{
- h->nxt = h;
- h->prv = h;
-}
-
-static void add_list(struct list_head * n,
- struct list_head * prv,
- struct list_head * nxt)
-{
- nxt->prv = n;
- n->nxt = nxt;
- n->prv = prv;
- prv->nxt = n;
-}
-
-static void del_list(struct list_head * prv,
- struct list_head * nxt)
-{
- nxt->prv = prv;
- prv->nxt = nxt;
-}
-
-void list_add(struct list_head * n,
- struct list_head * h)
-{
- add_list(n, h, h->nxt);
-}
-
-void list_add_tail(struct list_head * n,
- struct list_head * h)
-{
- add_list(n, h->prv, h);
-}
-
-void list_del(struct list_head * e)
-{
- del_list(e->prv, e->nxt);
- e->nxt = e->prv = e;
-}
-
-bool list_is_empty(const struct list_head * h)
-{
- return h->nxt == h;
-}
-
-void list_move(struct list_head * n,
- struct list_head * h)
-{
- del_list(n->prv, n->nxt);
- add_list(n, h, h->nxt);
-}
diff --git a/src/lib/lockfile.c b/src/lib/lockfile.c
index cf6d3c94..0c18dfc1 100644
--- a/src/lib/lockfile.c
+++ b/src/lib/lockfile.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Lockfile for Ouroboros
*
diff --git a/src/lib/logs.c b/src/lib/logs.c
index d90bcd63..af03de10 100644
--- a/src/lib/logs.c
+++ b/src/lib/logs.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Logging facilities
*
diff --git a/src/lib/md5.c b/src/lib/md5.c
index ad0dd4d7..2412b909 100644
--- a/src/lib/md5.c
+++ b/src/lib/md5.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* MD5 algorithm
*
diff --git a/src/lib/notifier.c b/src/lib/notifier.c
index 4fccd371..10bdb28d 100644
--- a/src/lib/notifier.c
+++ b/src/lib/notifier.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Notifier event system using callbacks
*
diff --git a/src/lib/pb/cep.proto b/src/lib/pb/cep.proto
index d31cf4f7..14a85cfa 100644
--- a/src/lib/pb/cep.proto
+++ b/src/lib/pb/cep.proto
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Message for Connection Information in OCEP
*
diff --git a/src/lib/pb/enroll.proto b/src/lib/pb/enroll.proto
index 60e964c6..37226b4d 100644
--- a/src/lib/pb/enroll.proto
+++ b/src/lib/pb/enroll.proto
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Enrollment protocol
*
diff --git a/src/lib/pb/ipcp.proto b/src/lib/pb/ipcp.proto
index c2c7f48b..9dc402f5 100644
--- a/src/lib/pb/ipcp.proto
+++ b/src/lib/pb/ipcp.proto
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Messages sent to IPCPds
*
@@ -56,4 +56,5 @@ message ipcp_msg {
optional uint32 timeo_sec = 12;
optional sint32 mpl = 13;
optional int32 result = 14;
+ optional uint32 uid = 15; /* 0 = GSPP, >0 = PUP uid */
}
diff --git a/src/lib/pb/ipcp_config.proto b/src/lib/pb/ipcp_config.proto
index 1308c6d1..eac4da37 100644
--- a/src/lib/pb/ipcp_config.proto
+++ b/src/lib/pb/ipcp_config.proto
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Layer configuration message
*
diff --git a/src/lib/pb/irm.proto b/src/lib/pb/irm.proto
index 75f5f350..9ed0a29b 100644
--- a/src/lib/pb/irm.proto
+++ b/src/lib/pb/irm.proto
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Messages sent to IRMd
*
@@ -91,8 +91,9 @@ message irm_msg {
optional sint32 mpl = 20;
optional string comp = 21;
optional bytes pk = 22; /* piggyback */
- optional bytes symmkey = 23;
- optional uint32 timeo_sec = 24;
- optional uint32 timeo_nsec = 25;
- optional sint32 result = 26;
+ optional uint32 timeo_sec = 23;
+ optional uint32 timeo_nsec = 24;
+ optional sint32 result = 25;
+ optional bytes sym_key = 26; /* symmetric encryption key */
+ optional sint32 cipher_nid = 27; /* cipher NID */
}
diff --git a/src/lib/pb/model.proto b/src/lib/pb/model.proto
index 7b06e434..f1382f3d 100644
--- a/src/lib/pb/model.proto
+++ b/src/lib/pb/model.proto
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Model description messages
*
@@ -34,12 +34,13 @@ message qosspec_msg {
}
message flow_info_msg {
- required uint32 id = 1;
- required uint32 n_pid = 2;
- required uint32 n_1_pid = 3;
- required uint32 mpl = 4;
- required uint32 state = 5;
- required qosspec_msg qos = 6;
+ required uint32 id = 1;
+ required uint32 n_pid = 2;
+ required uint32 n_1_pid = 3;
+ required uint32 mpl = 4;
+ required uint32 state = 5;
+ required qosspec_msg qos = 6;
+ required uint32 uid = 7;
}
message name_info_msg {
diff --git a/src/lib/protobuf.c b/src/lib/protobuf.c
index 6df4e810..d419a9f1 100644
--- a/src/lib/protobuf.c
+++ b/src/lib/protobuf.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Protobuf syntax conversion
*
@@ -23,6 +23,8 @@
#define _DEFAULT_SOURCE
#include <ouroboros/protobuf.h>
+#include <ouroboros/crypt.h>
+#include <ouroboros/proc.h>
#include <stdlib.h>
#include <string.h>
@@ -73,12 +75,13 @@ flow_info_msg_t * flow_info_s_to_msg(const struct flow_info * s)
flow_info_msg__init(msg);
- msg->id = s->id;
- msg->n_pid = s->n_pid;
- msg->n_1_pid = s->n_1_pid;
- msg->mpl = s->mpl;
- msg->state = s->state;
- msg->qos = qos_spec_s_to_msg(&s->qs);
+ msg->id = s->id;
+ msg->n_pid = s->n_pid;
+ msg->n_1_pid = s->n_1_pid;
+ msg->mpl = s->mpl;
+ msg->state = s->state;
+ msg->uid = s->uid;
+ msg->qos = qos_spec_s_to_msg(&s->qs);
if (msg->qos == NULL)
goto fail_msg;
@@ -96,11 +99,14 @@ struct flow_info flow_info_msg_to_s(const flow_info_msg_t * msg)
assert(msg != NULL);
+ memset(&s, 0, sizeof(s));
+
s.id = msg->id;
s.n_pid = msg->n_pid;
s.n_1_pid = msg->n_1_pid;
s.mpl = msg->mpl;
s.state = msg->state;
+ s.uid = msg->uid;
s.qs = qos_spec_msg_to_s(msg->qos);
return s;
@@ -544,7 +550,7 @@ struct udp4_config udp4_config_msg_to_s(const udp4_config_msg_t * msg)
return s;
}
-#define IN6_LEN sizeof(struct in6_addr)
+#define IN6_LEN (size_t) sizeof(struct in6_addr)
udp6_config_msg_t * udp6_config_s_to_msg(const struct udp6_config * s)
{
udp6_config_msg_t * msg;
diff --git a/src/lib/qoscube.c b/src/lib/qoscube.c
index 267b3a87..1eaa0d7c 100644
--- a/src/lib/qoscube.c
+++ b/src/lib/qoscube.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Quality of Service cube
*
diff --git a/src/lib/random.c b/src/lib/random.c
index 2dc5f02f..96315132 100644
--- a/src/lib/random.c
+++ b/src/lib/random.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Pseudo random generator
*
diff --git a/src/lib/rib.c b/src/lib/rib.c
index 97a20f47..a8d535c9 100644
--- a/src/lib/rib.c
+++ b/src/lib/rib.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* RIB export using FUSE
*
diff --git a/src/lib/serdes-irm.c b/src/lib/serdes-irm.c
index 3aea0617..65f2c02d 100644
--- a/src/lib/serdes-irm.c
+++ b/src/lib/serdes-irm.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Ouroboros IRM Protocol - serialization/deserialization
*
@@ -24,6 +24,7 @@
#include "config.h"
+#include <ouroboros/crypt.h>
#include <ouroboros/errno.h>
#include <ouroboros/serdes-irm.h>
#include <ouroboros/protobuf.h>
@@ -135,14 +136,11 @@ int flow_join__irm_req_ser(buffer_t * buf,
int flow__irm_result_des(buffer_t * buf,
struct flow_info * flow,
- buffer_t * sk)
+ struct crypt_sk * sk)
{
irm_msg_t * msg;
int err;
- if (sk != NULL)
- sk->data = NULL;
-
msg = irm_msg__unpack(NULL, buf->len, buf->data);
if (msg == NULL) {
err = -EIRMD;
@@ -166,13 +164,15 @@ int flow__irm_result_des(buffer_t * buf,
*flow = flow_info_msg_to_s(msg->flow_info);
- if (sk != NULL) {
- sk->len = msg->symmkey.len;
- sk->data = msg->symmkey.data;
+ if (msg->has_cipher_nid)
+ sk->nid = msg->cipher_nid;
+ else
+ sk->nid = NID_undef;
- msg->symmkey.data = NULL;
- msg->symmkey.len = 0;
- }
+ if (msg->sym_key.len == SYMMKEYSZ)
+ memcpy(sk->key, msg->sym_key.data, SYMMKEYSZ);
+ else
+ memset(sk->key, 0, SYMMKEYSZ);
irm_msg__free_unpacked(msg, NULL);
@@ -289,8 +289,8 @@ int ipcp_create_r__irm_req_ser(buffer_t * buf,
return -ENOMEM;
}
-int proc_announce__irm_req_ser(buffer_t * buf,
- const char * prog)
+int proc_announce__irm_req_ser(buffer_t * buf,
+ const struct proc_info * proc)
{
irm_msg_t * msg;
size_t len;
@@ -303,8 +303,8 @@ int proc_announce__irm_req_ser(buffer_t * buf,
msg->code = IRM_MSG_CODE__IRM_PROC_ANNOUNCE;
msg->has_pid = true;
- msg->pid = getpid();
- msg->prog = strdup(prog);
+ msg->pid = proc->pid;
+ msg->prog = strdup(proc->prog);
if (msg->prog == NULL)
goto fail_msg;
diff --git a/src/lib/serdes-oep.c b/src/lib/serdes-oep.c
index 8a836b3b..3d191494 100644
--- a/src/lib/serdes-oep.c
+++ b/src/lib/serdes-oep.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Ouroboros Enrollment Protocol - serialization/deserialization
*
diff --git a/src/lib/sha3.c b/src/lib/sha3.c
index b9d6b07f..f406124e 100644
--- a/src/lib/sha3.c
+++ b/src/lib/sha3.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* SHA3 algorithm
*
diff --git a/src/lib/shm_rbuff.c b/src/lib/shm_rbuff.c
deleted file mode 100644
index 22cff41c..00000000
--- a/src/lib/shm_rbuff.c
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * Ouroboros - Copyright (C) 2016 - 2024
- *
- * Ring buffer implementations for incoming packets
- *
- * Dimitri Staessens <dimitri@ouroboros.rocks>
- * Sander Vrijders <sander@ouroboros.rocks>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License
- * version 2.1 as published by the Free Software Foundation.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., http://www.fsf.org/about/contact/.
- */
-
-#define _POSIX_C_SOURCE 200809L
-
-#include "config.h"
-
-#include <ouroboros/shm_rbuff.h>
-#include <ouroboros/lockfile.h>
-#include <ouroboros/errno.h>
-#include <ouroboros/fccntl.h>
-#include <ouroboros/pthread.h>
-#include <ouroboros/time.h>
-
-#include <assert.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <stdbool.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
-
-#define FN_MAX_CHARS 255
-
-#define SHM_RB_FILE_SIZE ((SHM_RBUFF_SIZE) * sizeof(ssize_t) \
- + 3 * sizeof(size_t) \
- + sizeof(pthread_mutex_t) \
- + 2 * sizeof (pthread_cond_t))
-
-#define shm_rbuff_used(rb) ((*rb->head + (SHM_RBUFF_SIZE) - *rb->tail) \
- & ((SHM_RBUFF_SIZE) - 1))
-#define shm_rbuff_free(rb) (shm_rbuff_used(rb) + 1 < (SHM_RBUFF_SIZE))
-#define shm_rbuff_empty(rb) (*rb->head == *rb->tail)
-#define head_el_ptr(rb) (rb->shm_base + *rb->head)
-#define tail_el_ptr(rb) (rb->shm_base + *rb->tail)
-
-struct shm_rbuff {
- ssize_t * shm_base; /* start of entry */
- size_t * head; /* start of ringbuffer head */
- size_t * tail; /* start of ringbuffer tail */
- size_t * acl; /* access control */
- pthread_mutex_t * lock; /* lock all free space in shm */
- pthread_cond_t * add; /* packet arrived */
- pthread_cond_t * del; /* packet removed */
- pid_t pid; /* pid of the owner */
- int flow_id; /* flow_id of the flow */
-};
-
-#define MM_FLAGS (PROT_READ | PROT_WRITE)
-
-static struct shm_rbuff * rbuff_create(pid_t pid,
- int flow_id,
- int flags)
-{
- struct shm_rbuff * rb;
- int fd;
- ssize_t * shm_base;
- char fn[FN_MAX_CHARS];
-
- sprintf(fn, SHM_RBUFF_PREFIX "%d.%d", pid, flow_id);
-
- rb = malloc(sizeof(*rb));
- if (rb == NULL)
- goto fail_malloc;
-
- fd = shm_open(fn, flags, 0666);
- if (fd == -1)
- goto fail_open;
-
- if ((flags & O_CREAT) && ftruncate(fd, SHM_RB_FILE_SIZE) < 0)
- goto fail_truncate;
-
- shm_base = mmap(NULL, SHM_RB_FILE_SIZE, MM_FLAGS, MAP_SHARED, fd, 0);
- if (shm_base == MAP_FAILED)
- goto fail_truncate;
-
- close(fd);
-
- rb->shm_base = shm_base;
- rb->head = (size_t *) (rb->shm_base + (SHM_RBUFF_SIZE));
- rb->tail = rb->head + 1;
- rb->acl = rb->tail + 1;
- rb->lock = (pthread_mutex_t *) (rb->acl + 1);
- rb->add = (pthread_cond_t *) (rb->lock + 1);
- rb->del = rb->add + 1;
- rb->pid = pid;
- rb->flow_id = flow_id;
-
- return rb;
-
- fail_truncate:
- close(fd);
- if (flags & O_CREAT)
- shm_unlink(fn);
- fail_open:
- free(rb);
- fail_malloc:
- return NULL;
-}
-
-static void rbuff_destroy(struct shm_rbuff * rb)
-{
- munmap(rb->shm_base, SHM_RB_FILE_SIZE);
-
- free(rb);
-}
-
-struct shm_rbuff * shm_rbuff_create(pid_t pid,
- int flow_id)
-{
- struct shm_rbuff * rb;
- pthread_mutexattr_t mattr;
- pthread_condattr_t cattr;
- mode_t mask;
-
- mask = umask(0);
-
- rb = rbuff_create(pid, flow_id, O_CREAT | O_EXCL | O_RDWR);
-
- umask(mask);
-
- if (rb == NULL)
- goto fail_rb;
-
- if (pthread_mutexattr_init(&mattr))
- goto fail_mattr;
-
- pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED);
-#ifdef HAVE_ROBUST_MUTEX
- pthread_mutexattr_setrobust(&mattr, PTHREAD_MUTEX_ROBUST);
-#endif
- if (pthread_mutex_init(rb->lock, &mattr))
- goto fail_mutex;
-
- if (pthread_condattr_init(&cattr))
- goto fail_cattr;
-
- pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED);
-#ifndef __APPLE__
- pthread_condattr_setclock(&cattr, PTHREAD_COND_CLOCK);
-#endif
- if (pthread_cond_init(rb->add, &cattr))
- goto fail_add;
-
- if (pthread_cond_init(rb->del, &cattr))
- goto fail_del;
-
- *rb->acl = ACL_RDWR;
- *rb->head = 0;
- *rb->tail = 0;
-
- rb->pid = pid;
- rb->flow_id = flow_id;
-
- pthread_mutexattr_destroy(&mattr);
- pthread_condattr_destroy(&cattr);
-
- return rb;
-
- fail_del:
- pthread_cond_destroy(rb->add);
- fail_add:
- pthread_condattr_destroy(&cattr);
- fail_cattr:
- pthread_mutex_destroy(rb->lock);
- fail_mutex:
- pthread_mutexattr_destroy(&mattr);
- fail_mattr:
- shm_rbuff_destroy(rb);
- fail_rb:
- return NULL;
-}
-
-struct shm_rbuff * shm_rbuff_open(pid_t pid,
- int flow_id)
-{
- return rbuff_create(pid, flow_id, O_RDWR);
-}
-
-void shm_rbuff_close(struct shm_rbuff * rb)
-{
- assert(rb);
-
- rbuff_destroy(rb);
-}
-
-#if (defined(SHM_RBUFF_LOCKLESS) && \
- (defined(__GNUC__) || defined (__clang__)))
-#include "shm_rbuff_ll.c"
-#else
-#include "shm_rbuff_pthr.c"
-#endif
diff --git a/src/lib/shm_rbuff_ll.c b/src/lib/shm_rbuff_ll.c
deleted file mode 100644
index 46a5314e..00000000
--- a/src/lib/shm_rbuff_ll.c
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * Ouroboros - Copyright (C) 2016 - 2024
- *
- * Lockless ring buffer for incoming packets
- *
- * Dimitri Staessens <dimitri@ouroboros.rocks>
- * Sander Vrijders <sander@ouroboros.rocks>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License
- * version 2.1 as published by the Free Software Foundation.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., http://www.fsf.org/about/contact/.
- */
-
-#define RB_HEAD __sync_fetch_and_add(rb->head, 0)
-#define RB_TAIL __sync_fetch_and_add(rb->tail, 0)
-
-void shm_rbuff_destroy(struct shm_rbuff * rb)
-{
- char fn[FN_MAX_CHARS];
-
- assert(rb);
-
- sprintf(fn, SHM_RBUFF_PREFIX "%d.%d", rb->pid, rb->flow_id);
-
- __sync_bool_compare_and_swap(rb->acl, *rb->acl, ACL_FLOWDOWN);
-
- pthread_cond_broadcast(rb->del);
- pthread_cond_broadcast(rb->add);
-
- shm_rbuff_close(rb);
-
- shm_unlink(fn);
-}
-
-int shm_rbuff_write(struct shm_rbuff * rb,
- size_t idx)
-{
- size_t ohead;
- size_t nhead;
- bool was_empty = false;
-
- assert(rb);
- assert(idx < SHM_BUFFER_SIZE);
-
- if (__sync_fetch_and_add(rb->acl, 0) != ACL_RDWR) {
- if (__sync_fetch_and_add(rb->acl, 0) & ACL_FLOWDOWN)
- return -EFLOWDOWN;
- else if (__sync_fetch_and_add(rb->acl, 0) & ACL_RDONLY)
- return -ENOTALLOC;
- }
-
- if (!shm_rbuff_free(rb))
- return -EAGAIN;
-
- if (shm_rbuff_empty(rb))
- was_empty = true;
-
- nhead = RB_HEAD;
-
- *(rb->shm_base + nhead) = (ssize_t) idx;
-
- do {
- ohead = nhead;
- nhead = (ohead + 1) & ((SHM_RBUFF_SIZE) - 1);
- nhead = __sync_val_compare_and_swap(rb->head, ohead, nhead);
- } while (nhead != ohead);
-
- if (was_empty)
- pthread_cond_broadcast(rb->add);
-
- return 0;
-}
-
-/* FIXME: this is a copy of the pthr implementation */
-int shm_rbuff_write_b(struct shm_rbuff * rb,
- size_t idx,
- const struct timespec * abstime)
-{
- int ret = 0;
-
- assert(rb);
- assert(idx < SHM_BUFFER_SIZE);
-
-#ifndef HAVE_ROBUST_MUTEX
- pthread_mutex_lock(rb->lock);
-#else
- if (pthread_mutex_lock(rb->lock) == EOWNERDEAD)
- pthread_mutex_consistent(rb->lock);
-#endif
-
- if (*rb->acl != ACL_RDWR) {
- if (*rb->acl & ACL_FLOWDOWN)
- ret = -EFLOWDOWN;
- else if (*rb->acl & ACL_RDONLY)
- ret = -ENOTALLOC;
- goto err;
- }
-
- pthread_cleanup_push(__cleanup_mutex_unlock, rb->lock);
-
- while (!shm_rbuff_free(rb) && ret != -ETIMEDOUT) {
- ret = -__timedwait(rb->add, rb->lock, abstime);
-#ifdef HAVE_ROBUST_MUTEX
- if (ret == -EOWNERDEAD)
- pthread_mutex_consistent(rb->lock);
-#endif
- }
-
- if (shm_rbuff_empty(rb))
- pthread_cond_broadcast(rb->add);
-
- if (ret != -ETIMEDOUT) {
- *head_el_ptr(rb) = (ssize_t) idx;
- *rb->head = (*rb->head + 1) & ((SHM_RBUFF_SIZE) -1);
- }
-
- pthread_cleanup_pop(true);
-
- return ret;
- err:
- pthread_mutex_unlock(rb->lock);
- return ret;
-}
-
-ssize_t shm_rbuff_read(struct shm_rbuff * rb)
-{
- size_t otail;
- size_t ntail;
-
- assert(rb);
-
- if (shm_rbuff_empty(rb)) {
- if (_sync_fetch_and_add(rb->acl, 0) & ACL_FLOWDOWN)
- return -EFLOWDOWN;
-
- if (_sync_fetch_and_add(rb->acl, 0) & ACL_FLOWPEER)
- return -EFLOWPEER;
-
- return -EAGAIN;
- }
-
- ntail = RB_TAIL;
-
- do {
- otail = ntail;
- ntail = (otail + 1) & ((SHM_RBUFF_SIZE) - 1);
- ntail = __sync_val_compare_and_swap(rb->tail, otail, ntail);
- } while (ntail != otail);
-
- pthread_cond_broadcast(rb->del);
-
- return *(rb->shm_base + ntail);
-}
-
-ssize_t shm_rbuff_read_b(struct shm_rbuff * rb,
- const struct timespec * abstime)
-{
- ssize_t idx = -1;
-
- assert(rb);
-
- /* try a non-blocking read first */
- idx = shm_rbuff_read(rb);
- if (idx != -EAGAIN)
- return idx;
-
-#ifndef HAVE_ROBUST_MUTEX
- pthread_mutex_lock(rb->lock);
-#else
- if (pthread_mutex_lock(rb->lock) == EOWNERDEAD)
- pthread_mutex_consistent(rb->lock);
-#endif
- pthread_cleanup_push(__cleanup_mutex_unlock, rb->lock);
-
- while (shm_rbuff_empty(rb) && (idx != -ETIMEDOUT)) {
- idx = -__timedwait(rb->add, rb->lock, abstime);
-#ifdef HAVE_ROBUST_MUTEX
- if (idx == -EOWNERDEAD)
- pthread_mutex_consistent(rb->lock);
-#endif
- }
-
- if (idx != -ETIMEDOUT) {
- /* do a nonblocking read */
- idx = shm_rbuff_read(rb);
- assert(idx >= 0);
- }
-
- pthread_cleanup_pop(true);
-
- return idx;
-}
-
-void shm_rbuff_set_acl(struct shm_rbuff * rb,
- uint32_t flags)
-{
- assert(rb);
-
- __sync_bool_compare_and_swap(rb->acl, *rb->acl, flags);
-}
-
-uint32_t shm_rbuff_get_acl(struct shm_rbuff * rb)
-{
- assert(rb);
-
- return __sync_fetch_and_add(rb->acl, 0);
-}
-
-void shm_rbuff_fini(struct shm_rbuff * rb)
-{
- assert(rb);
-
- if (shm_rbuff_empty(rb))
- return;
-
-#ifndef HAVE_ROBUST_MUTEX
- pthread_mutex_lock(rb->lock);
-#else
- if (pthread_mutex_lock(rb->lock) == EOWNERDEAD)
- pthread_mutex_consistent(rb->lock);
-#endif
-
- pthread_cleanup_push(__cleanup_mutex_unlock, rb->lock);
-
- while (!shm_rbuff_empty(rb))
-#ifndef HAVE_ROBUST_MUTEX
- pthread_cond_wait(rb->del, rb->lock);
-#else
- if (pthread_cond_wait(rb->del, rb->lock) == EOWNERDEAD)
- pthread_mutex_consistent(rb->lock);
-#endif
- pthread_cleanup_pop(true);
-}
-
-size_t shm_rbuff_queued(struct shm_rbuff * rb)
-{
- assert(rb);
-
- return shm_rbuff_used(rb);
-}
diff --git a/src/lib/shm_rbuff_pthr.c b/src/lib/shm_rbuff_pthr.c
deleted file mode 100644
index b543fb07..00000000
--- a/src/lib/shm_rbuff_pthr.c
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- * Ouroboros - Copyright (C) 2016 - 2024
- *
- * Ring buffer for incoming packets
- *
- * Dimitri Staessens <dimitri@ouroboros.rocks>
- * Sander Vrijders <sander@ouroboros.rocks>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License
- * version 2.1 as published by the Free Software Foundation.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., http://www.fsf.org/about/contact/.
- */
-
-void shm_rbuff_destroy(struct shm_rbuff * rb)
-{
- char fn[FN_MAX_CHARS];
-
- assert(rb != NULL);
-
-#ifdef CONFIG_OUROBOROS_DEBUG
- pthread_mutex_lock(rb->lock);
-
- *rb->acl = *rb->acl & ACL_FLOWDOWN;
-
- pthread_cond_broadcast(rb->del);
- pthread_cond_broadcast(rb->add);
-
- pthread_mutex_unlock(rb->lock);
-#endif
- sprintf(fn, SHM_RBUFF_PREFIX "%d.%d", rb->pid, rb->flow_id);
-
- shm_rbuff_close(rb);
-
- shm_unlink(fn);
-}
-
-int shm_rbuff_write(struct shm_rbuff * rb,
- size_t idx)
-{
- int ret = 0;
-
- assert(rb != NULL);
- assert(idx < SHM_BUFFER_SIZE);
-
-#ifndef HAVE_ROBUST_MUTEX
- pthread_mutex_lock(rb->lock);
-#else
- if (pthread_mutex_lock(rb->lock) == EOWNERDEAD)
- pthread_mutex_consistent(rb->lock);
-#endif
-
- if (*rb->acl != ACL_RDWR) {
- if (*rb->acl & ACL_FLOWDOWN)
- ret = -EFLOWDOWN;
- else if (*rb->acl & ACL_RDONLY)
- ret = -ENOTALLOC;
- goto err;
- }
-
- if (!shm_rbuff_free(rb)) {
- ret = -EAGAIN;
- goto err;
- }
-
- if (shm_rbuff_empty(rb))
- pthread_cond_broadcast(rb->add);
-
- *head_el_ptr(rb) = (ssize_t) idx;
- *rb->head = (*rb->head + 1) & ((SHM_RBUFF_SIZE) - 1);
-
- pthread_mutex_unlock(rb->lock);
-
- return 0;
- err:
- pthread_mutex_unlock(rb->lock);
- return ret;
-}
-
-int shm_rbuff_write_b(struct shm_rbuff * rb,
- size_t idx,
- const struct timespec * abstime)
-{
- int ret = 0;
-
- assert(rb != NULL);
- assert(idx < SHM_BUFFER_SIZE);
-
-#ifndef HAVE_ROBUST_MUTEX
- pthread_mutex_lock(rb->lock);
-#else
- if (pthread_mutex_lock(rb->lock) == EOWNERDEAD)
- pthread_mutex_consistent(rb->lock);
-#endif
-
- if (*rb->acl != ACL_RDWR) {
- if (*rb->acl & ACL_FLOWDOWN)
- ret = -EFLOWDOWN;
- else if (*rb->acl & ACL_RDONLY)
- ret = -ENOTALLOC;
- goto err;
- }
-
- pthread_cleanup_push(__cleanup_mutex_unlock, rb->lock);
-
- while (!shm_rbuff_free(rb)
- && ret != -ETIMEDOUT
- && !(*rb->acl & ACL_FLOWDOWN)) {
- ret = -__timedwait(rb->del, rb->lock, abstime);
-#ifdef HAVE_ROBUST_MUTEX
- if (ret == -EOWNERDEAD)
- pthread_mutex_consistent(rb->lock);
-#endif
- }
-
- if (ret != -ETIMEDOUT) {
- if (shm_rbuff_empty(rb))
- pthread_cond_broadcast(rb->add);
- *head_el_ptr(rb) = (ssize_t) idx;
- *rb->head = (*rb->head + 1) & ((SHM_RBUFF_SIZE) - 1);
- }
-
- pthread_cleanup_pop(true);
-
- return ret;
- err:
- pthread_mutex_unlock(rb->lock);
- return ret;
-}
-
-static int check_rb_acl(struct shm_rbuff * rb)
-{
- assert(rb != NULL);
-
- if (*rb->acl & ACL_FLOWDOWN)
- return -EFLOWDOWN;
-
- if (*rb->acl & ACL_FLOWPEER)
- return -EFLOWPEER;
-
- return -EAGAIN;
-}
-
-ssize_t shm_rbuff_read(struct shm_rbuff * rb)
-{
- ssize_t ret = 0;
-
- assert(rb != NULL);
-
-#ifndef HAVE_ROBUST_MUTEX
- pthread_mutex_lock(rb->lock);
-#else
- if (pthread_mutex_lock(rb->lock) == EOWNERDEAD)
- pthread_mutex_consistent(rb->lock);
-#endif
-
- if (shm_rbuff_empty(rb)) {
- ret = check_rb_acl(rb);
- pthread_mutex_unlock(rb->lock);
- return ret;
- }
-
- ret = *tail_el_ptr(rb);
- *rb->tail = (*rb->tail + 1) & ((SHM_RBUFF_SIZE) - 1);
- pthread_cond_broadcast(rb->del);
-
- pthread_mutex_unlock(rb->lock);
-
- return ret;
-}
-
-ssize_t shm_rbuff_read_b(struct shm_rbuff * rb,
- const struct timespec * abstime)
-{
- ssize_t idx = -1;
-
- assert(rb != NULL);
-
-#ifndef HAVE_ROBUST_MUTEX
- pthread_mutex_lock(rb->lock);
-#else
- if (pthread_mutex_lock(rb->lock) == EOWNERDEAD)
- pthread_mutex_consistent(rb->lock);
-#endif
-
- if (shm_rbuff_empty(rb) && (*rb->acl & ACL_FLOWDOWN)) {
- pthread_mutex_unlock(rb->lock);
- return -EFLOWDOWN;
- }
-
- pthread_cleanup_push(__cleanup_mutex_unlock, rb->lock);
-
- while (shm_rbuff_empty(rb) &&
- idx != -ETIMEDOUT &&
- check_rb_acl(rb) == -EAGAIN) {
- idx = -__timedwait(rb->add, rb->lock, abstime);
-#ifdef HAVE_ROBUST_MUTEX
- if (idx == -EOWNERDEAD)
- pthread_mutex_consistent(rb->lock);
-#endif
- }
-
- if (!shm_rbuff_empty(rb)) {
- idx = *tail_el_ptr(rb);
- *rb->tail = (*rb->tail + 1) & ((SHM_RBUFF_SIZE) - 1);
- pthread_cond_broadcast(rb->del);
- } else if (idx != -ETIMEDOUT) {
- idx = check_rb_acl(rb);
- }
-
- pthread_cleanup_pop(true);
-
- assert(idx != -EAGAIN);
-
- return idx;
-}
-
-void shm_rbuff_set_acl(struct shm_rbuff * rb,
- uint32_t flags)
-{
- assert(rb != NULL);
-
-#ifndef HAVE_ROBUST_MUTEX
- pthread_mutex_lock(rb->lock);
-#else
- if (pthread_mutex_lock(rb->lock) == EOWNERDEAD)
- pthread_mutex_consistent(rb->lock);
-#endif
- *rb->acl = (size_t) flags;
-
- pthread_cond_broadcast(rb->del);
- pthread_cond_broadcast(rb->add);
-
- pthread_mutex_unlock(rb->lock);
-}
-
-uint32_t shm_rbuff_get_acl(struct shm_rbuff * rb)
-{
- uint32_t flags;
-
- assert(rb != NULL);
-
-#ifndef HAVE_ROBUST_MUTEX
- pthread_mutex_lock(rb->lock);
-#else
- if (pthread_mutex_lock(rb->lock) == EOWNERDEAD)
- pthread_mutex_consistent(rb->lock);
-#endif
- flags = (uint32_t) *rb->acl;
-
- pthread_mutex_unlock(rb->lock);
-
- return flags;
-}
-
-void shm_rbuff_fini(struct shm_rbuff * rb)
-{
- assert(rb != NULL);
-
-#ifndef HAVE_ROBUST_MUTEX
- pthread_mutex_lock(rb->lock);
-#else
- if (pthread_mutex_lock(rb->lock) == EOWNERDEAD)
- pthread_mutex_consistent(rb->lock);
-#endif
- pthread_cleanup_push(__cleanup_mutex_unlock, rb->lock);
-
- while (!shm_rbuff_empty(rb))
-#ifndef HAVE_ROBUST_MUTEX
- pthread_cond_wait(rb->del, rb->lock);
-#else
- if (pthread_cond_wait(rb->del, rb->lock) == EOWNERDEAD)
- pthread_mutex_consistent(rb->lock);
-#endif
- pthread_cleanup_pop(true);
-}
-
-size_t shm_rbuff_queued(struct shm_rbuff * rb)
-{
- size_t ret;
-
- assert(rb != NULL);
-
-#ifndef HAVE_ROBUST_MUTEX
- pthread_mutex_lock(rb->lock);
-#else
- if (pthread_mutex_lock(rb->lock) == EOWNERDEAD)
- pthread_mutex_consistent(rb->lock);
-#endif
-
- ret = shm_rbuff_used(rb);
-
- pthread_mutex_unlock(rb->lock);
-
- return ret;
-}
diff --git a/src/lib/shm_rdrbuff.c b/src/lib/shm_rdrbuff.c
deleted file mode 100644
index 7ad1bd2e..00000000
--- a/src/lib/shm_rdrbuff.c
+++ /dev/null
@@ -1,610 +0,0 @@
-/*
- * Ouroboros - Copyright (C) 2016 - 2024
- *
- * Random Deletion Ring Buffer for Data Units
- *
- * Dimitri Staessens <dimitri@ouroboros.rocks>
- * Sander Vrijders <sander@ouroboros.rocks>
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public License
- * version 2.1 as published by the Free Software Foundation.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., http://www.fsf.org/about/contact/.
- */
-
-#define _POSIX_C_SOURCE 200809L
-
-#include "config.h"
-
-#include <ouroboros/errno.h>
-#include <ouroboros/pthread.h>
-#include <ouroboros/shm_rdrbuff.h>
-
-#include <assert.h>
-#include <fcntl.h>
-#include <signal.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
-
-#define SHM_BLOCKS_SIZE ((SHM_BUFFER_SIZE) * SHM_RDRB_BLOCK_SIZE)
-#define SHM_FILE_SIZE (SHM_BLOCKS_SIZE + 2 * sizeof(size_t) \
- + sizeof(pthread_mutex_t) + 2 * sizeof(pthread_cond_t) \
- + sizeof(pid_t))
-#define DU_BUFF_OVERHEAD (DU_BUFF_HEADSPACE + DU_BUFF_TAILSPACE)
-
-#define get_head_ptr(rdrb) \
- idx_to_du_buff_ptr(rdrb, *rdrb->head)
-
-#define get_tail_ptr(rdrb) \
- idx_to_du_buff_ptr(rdrb, *rdrb->tail)
-
-#define idx_to_du_buff_ptr(rdrb, idx) \
- ((struct shm_du_buff *) (rdrb->shm_base + idx * SHM_RDRB_BLOCK_SIZE))
-
-#define shm_rdrb_used(rdrb) \
- (((*rdrb->head + (SHM_BUFFER_SIZE) - *rdrb->tail) + 1) \
- & ((SHM_BUFFER_SIZE) - 1))
-
-#define shm_rdrb_free(rdrb, i) \
- (shm_rdrb_used(rdrb) + i < (SHM_BUFFER_SIZE))
-
-#define shm_rdrb_empty(rdrb) \
- (*rdrb->tail == *rdrb->head)
-
-struct shm_du_buff {
- size_t size;
-#ifdef SHM_RDRB_MULTI_BLOCK
- size_t blocks;
-#endif
- size_t du_head;
- size_t du_tail;
- size_t refs;
- size_t idx;
-};
-
-struct shm_rdrbuff {
- uint8_t * shm_base; /* start of blocks */
- size_t * head; /* start of ringbuffer head */
- size_t * tail; /* start of ringbuffer tail */
- pthread_mutex_t * lock; /* lock all free space in shm */
- pthread_cond_t * healthy; /* flag when packet is read */
- pid_t * pid; /* pid of the irmd owner */
-};
-
-static void garbage_collect(struct shm_rdrbuff * rdrb)
-{
-#ifdef SHM_RDRB_MULTI_BLOCK
- struct shm_du_buff * sdb;
- while (!shm_rdrb_empty(rdrb) &&
- (sdb = get_tail_ptr(rdrb))->refs == 0)
- *rdrb->tail = (*rdrb->tail + sdb->blocks)
- & ((SHM_BUFFER_SIZE) - 1);
-#else
- while (!shm_rdrb_empty(rdrb) && get_tail_ptr(rdrb)->refs == 0)
- *rdrb->tail = (*rdrb->tail + 1) & ((SHM_BUFFER_SIZE) - 1);
-#endif
- pthread_cond_broadcast(rdrb->healthy);
-}
-
-#ifdef HAVE_ROBUST_MUTEX
-static void sanitize(struct shm_rdrbuff * rdrb)
-{
- --get_head_ptr(rdrb)->refs;
- garbage_collect(rdrb);
- pthread_mutex_consistent(rdrb->lock);
-}
-#endif
-
-static char * rdrb_filename(void)
-{
- char * str;
-
- str = malloc(strlen(SHM_RDRB_NAME) + 1);
- if (str == NULL)
- return NULL;
-
- sprintf(str, "%s", SHM_RDRB_NAME);
-
- return str;
-}
-
-void shm_rdrbuff_close(struct shm_rdrbuff * rdrb)
-{
- assert(rdrb);
-
- munmap(rdrb->shm_base, SHM_FILE_SIZE);
- free(rdrb);
-}
-
-void shm_rdrbuff_destroy(struct shm_rdrbuff * rdrb)
-{
- char * shm_rdrb_fn;
-
- assert(rdrb);
-
- if (getpid() != *rdrb->pid && kill(*rdrb->pid, 0) == 0) {
- free(rdrb);
- return;
- }
-
- shm_rdrbuff_close(rdrb);
-
- shm_rdrb_fn = rdrb_filename();
- if (shm_rdrb_fn == NULL)
- return;
-
- shm_unlink(shm_rdrb_fn);
- free(shm_rdrb_fn);
-}
-
-#define MM_FLAGS (PROT_READ | PROT_WRITE)
-
-static struct shm_rdrbuff * rdrb_create(int flags)
-{
- struct shm_rdrbuff * rdrb;
- int fd;
- uint8_t * shm_base;
- char * shm_rdrb_fn;
-
- shm_rdrb_fn = rdrb_filename();
- if (shm_rdrb_fn == NULL)
- goto fail_fn;
-
- rdrb = malloc(sizeof *rdrb);
- if (rdrb == NULL)
- goto fail_rdrb;
-
- fd = shm_open(shm_rdrb_fn, flags, 0666);
- if (fd == -1)
- goto fail_open;
-
- if ((flags & O_CREAT) && ftruncate(fd, SHM_FILE_SIZE) < 0)
- goto fail_truncate;
-
- shm_base = mmap(NULL, SHM_FILE_SIZE, MM_FLAGS, MAP_SHARED, fd, 0);
- if (shm_base == MAP_FAILED)
- goto fail_truncate;
-
- close(fd);
-
- rdrb->shm_base = shm_base;
- rdrb->head = (size_t *) ((uint8_t *) rdrb->shm_base + SHM_BLOCKS_SIZE);
- rdrb->tail = rdrb->head + 1;
- rdrb->lock = (pthread_mutex_t *) (rdrb->tail + 1);
- rdrb->healthy = (pthread_cond_t *) (rdrb->lock + 1);
- rdrb->pid = (pid_t *) (rdrb->healthy + 1);
-
- free(shm_rdrb_fn);
-
- return rdrb;
-
- fail_truncate:
- close(fd);
- if (flags & O_CREAT)
- shm_unlink(shm_rdrb_fn);
- fail_open:
- free(rdrb);
- fail_rdrb:
- free(shm_rdrb_fn);
- fail_fn:
- return NULL;
-}
-
-struct shm_rdrbuff * shm_rdrbuff_create(void)
-{
- struct shm_rdrbuff * rdrb;
- mode_t mask;
- pthread_mutexattr_t mattr;
- pthread_condattr_t cattr;
-
- mask = umask(0);
-
- rdrb = rdrb_create(O_CREAT | O_EXCL | O_RDWR);
-
- umask(mask);
-
- if (rdrb == NULL)
- goto fail_rdrb;
-
- if (pthread_mutexattr_init(&mattr))
- goto fail_mattr;
-
- pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED);
-#ifdef HAVE_ROBUST_MUTEX
- pthread_mutexattr_setrobust(&mattr, PTHREAD_MUTEX_ROBUST);
-#endif
- if (pthread_mutex_init(rdrb->lock, &mattr))
- goto fail_mutex;
-
- if (pthread_condattr_init(&cattr))
- goto fail_cattr;
-
- pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED);
-#ifndef __APPLE__
- pthread_condattr_setclock(&cattr, PTHREAD_COND_CLOCK);
-#endif
- if (pthread_cond_init(rdrb->healthy, &cattr))
- goto fail_healthy;
-
- *rdrb->head = 0;
- *rdrb->tail = 0;
-
- *rdrb->pid = getpid();
-
- pthread_mutexattr_destroy(&mattr);
- pthread_condattr_destroy(&cattr);
-
- return rdrb;
-
- fail_healthy:
- pthread_condattr_destroy(&cattr);
- fail_cattr:
- pthread_mutex_destroy(rdrb->lock);
- fail_mutex:
- pthread_mutexattr_destroy(&mattr);
- fail_mattr:
- shm_rdrbuff_destroy(rdrb);
- fail_rdrb:
- return NULL;
-}
-
-struct shm_rdrbuff * shm_rdrbuff_open(void)
-{
- return rdrb_create(O_RDWR);
-}
-
-void shm_rdrbuff_purge(void)
-{
- char * shm_rdrb_fn;
-
- shm_rdrb_fn = rdrb_filename();
- if (shm_rdrb_fn == NULL)
- return;
-
- shm_unlink(shm_rdrb_fn);
- free(shm_rdrb_fn);
-}
-
-ssize_t shm_rdrbuff_alloc(struct shm_rdrbuff * rdrb,
- size_t len,
- uint8_t ** ptr,
- struct shm_du_buff ** psdb)
-{
- struct shm_du_buff * sdb;
- size_t size = DU_BUFF_OVERHEAD + len;
-#ifdef SHM_RDRB_MULTI_BLOCK
- size_t blocks = 0;
- size_t padblocks = 0;
-#endif
- ssize_t sz = size + sizeof(*sdb);
-
- assert(rdrb);
- assert(psdb);
-
-#ifndef SHM_RDRB_MULTI_BLOCK
- if (sz > SHM_RDRB_BLOCK_SIZE)
- return -EMSGSIZE;
-#else
- while (sz > 0) {
- sz -= SHM_RDRB_BLOCK_SIZE;
- ++blocks;
- }
-#endif
-#ifndef HAVE_ROBUST_MUTEX
- pthread_mutex_lock(rdrb->lock);
-#else
- if (pthread_mutex_lock(rdrb->lock) == EOWNERDEAD)
- sanitize(rdrb);
-#endif
-#ifdef SHM_RDRB_MULTI_BLOCK
- if (blocks + *rdrb->head > (SHM_BUFFER_SIZE))
- padblocks = (SHM_BUFFER_SIZE) - *rdrb->head;
-
- if (!shm_rdrb_free(rdrb, blocks + padblocks)) {
-#else
- if (!shm_rdrb_free(rdrb, 1)) {
-#endif
- pthread_mutex_unlock(rdrb->lock);
- return -EAGAIN;
- }
-
-#ifdef SHM_RDRB_MULTI_BLOCK
- if (padblocks) {
- sdb = get_head_ptr(rdrb);
- sdb->size = 0;
- sdb->blocks = padblocks;
- sdb->refs = 0;
- sdb->du_head = 0;
- sdb->du_tail = 0;
- sdb->idx = *rdrb->head;
-
- *rdrb->head = 0;
- }
-#endif
- sdb = get_head_ptr(rdrb);
- sdb->refs = 1;
- sdb->idx = *rdrb->head;
-#ifdef SHM_RDRB_MULTI_BLOCK
- sdb->blocks = blocks;
-
- *rdrb->head = (*rdrb->head + blocks) & ((SHM_BUFFER_SIZE) - 1);
-#else
- *rdrb->head = (*rdrb->head + 1) & ((SHM_BUFFER_SIZE) - 1);
-#endif
- pthread_mutex_unlock(rdrb->lock);
-
- sdb->size = size;
- sdb->du_head = DU_BUFF_HEADSPACE;
- sdb->du_tail = sdb->du_head + len;
-
- *psdb = sdb;
- if (ptr != NULL)
- *ptr = (uint8_t *) (sdb + 1) + sdb->du_head;
-
- return sdb->idx;
-}
-
-ssize_t shm_rdrbuff_alloc_b(struct shm_rdrbuff * rdrb,
- size_t len,
- uint8_t ** ptr,
- struct shm_du_buff ** psdb,
- const struct timespec * abstime)
-{
- struct shm_du_buff * sdb;
- size_t size = DU_BUFF_OVERHEAD + len;
-#ifdef SHM_RDRB_MULTI_BLOCK
- size_t blocks = 0;
- size_t padblocks = 0;
-#endif
- ssize_t sz = size + sizeof(*sdb);
- int ret = 0;
-
- assert(rdrb);
- assert(psdb);
-
-#ifndef SHM_RDRB_MULTI_BLOCK
- if (sz > SHM_RDRB_BLOCK_SIZE)
- return -EMSGSIZE;
-#else
- while (sz > 0) {
- sz -= SHM_RDRB_BLOCK_SIZE;
- ++blocks;
- }
-#endif
-#ifndef HAVE_ROBUST_MUTEX
- pthread_mutex_lock(rdrb->lock);
-#else
- if (pthread_mutex_lock(rdrb->lock) == EOWNERDEAD)
- sanitize(rdrb);
-#endif
- pthread_cleanup_push(__cleanup_mutex_unlock, rdrb->lock);
-
-#ifdef SHM_RDRB_MULTI_BLOCK
- if (blocks + *rdrb->head > (SHM_BUFFER_SIZE))
- padblocks = (SHM_BUFFER_SIZE) - *rdrb->head;
-
- while (!shm_rdrb_free(rdrb, blocks + padblocks) && ret != ETIMEDOUT) {
-#else
- while (!shm_rdrb_free(rdrb, 1) && ret != ETIMEDOUT) {
-#endif
- ret = __timedwait(rdrb->healthy, rdrb->lock, abstime);
-#ifdef SHM_RDRB_MULTI_BLOCK
- if (blocks + *rdrb->head > (SHM_BUFFER_SIZE))
- padblocks = (SHM_BUFFER_SIZE) - *rdrb->head;
-#endif
- }
-
- if (ret != ETIMEDOUT) {
-#ifdef SHM_RDRB_MULTI_BLOCK
- if (padblocks) {
- sdb = get_head_ptr(rdrb);
- sdb->size = 0;
- sdb->blocks = padblocks;
- sdb->refs = 0;
- sdb->du_head = 0;
- sdb->du_tail = 0;
- sdb->idx = *rdrb->head;
-
- *rdrb->head = 0;
- }
-#endif
- sdb = get_head_ptr(rdrb);
- sdb->refs = 1;
- sdb->idx = *rdrb->head;
-#ifdef SHM_RDRB_MULTI_BLOCK
- sdb->blocks = blocks;
-
- *rdrb->head = (*rdrb->head + blocks) & ((SHM_BUFFER_SIZE) - 1);
-#else
- *rdrb->head = (*rdrb->head + 1) & ((SHM_BUFFER_SIZE) - 1);
-#endif
- }
-
- pthread_cleanup_pop(true);
-
- if (ret == ETIMEDOUT)
- return -ETIMEDOUT;
-
- sdb->size = size;
- sdb->du_head = DU_BUFF_HEADSPACE;
- sdb->du_tail = sdb->du_head + len;
-
- *psdb = sdb;
- if (ptr != NULL)
- *ptr = (uint8_t *) (sdb + 1) + sdb->du_head;
-
- return sdb->idx;
-}
-
-ssize_t shm_rdrbuff_read(uint8_t ** dst,
- struct shm_rdrbuff * rdrb,
- size_t idx)
-{
- struct shm_du_buff * sdb;
-
- assert(dst);
- assert(rdrb);
- assert(idx < (SHM_BUFFER_SIZE));
-
- sdb = idx_to_du_buff_ptr(rdrb, idx);
- *dst = ((uint8_t *) (sdb + 1)) + sdb->du_head;
-
- return (ssize_t) (sdb->du_tail - sdb->du_head);
-}
-
-struct shm_du_buff * shm_rdrbuff_get(struct shm_rdrbuff * rdrb,
- size_t idx)
-{
- assert(rdrb);
- assert(idx < (SHM_BUFFER_SIZE));
-
- return idx_to_du_buff_ptr(rdrb, idx);
-}
-
-int shm_rdrbuff_remove(struct shm_rdrbuff * rdrb,
- size_t idx)
-{
- struct shm_du_buff * sdb;
-
- assert(rdrb);
- assert(idx < (SHM_BUFFER_SIZE));
-
-#ifndef HAVE_ROBUST_MUTEX
- pthread_mutex_lock(rdrb->lock);
-#else
- if (pthread_mutex_lock(rdrb->lock) == EOWNERDEAD)
- sanitize(rdrb);
-#endif
- /* assert(!shm_rdrb_empty(rdrb)); */
-
- sdb = idx_to_du_buff_ptr(rdrb, idx);
-
- if (sdb->refs == 1) { /* only stack needs it, can be removed */
- sdb->refs = 0;
- if (idx == *rdrb->tail)
- garbage_collect(rdrb);
- }
-
- pthread_mutex_unlock(rdrb->lock);
-
- return 0;
-}
-
-size_t shm_du_buff_get_idx(struct shm_du_buff * sdb)
-{
- assert(sdb);
-
- return sdb->idx;
-}
-
-uint8_t * shm_du_buff_head(struct shm_du_buff * sdb)
-{
- assert(sdb);
-
- return (uint8_t *) (sdb + 1) + sdb->du_head;
-}
-
-uint8_t * shm_du_buff_tail(struct shm_du_buff * sdb)
-{
- assert(sdb);
-
- return (uint8_t *) (sdb + 1) + sdb->du_tail;
-}
-
-size_t shm_du_buff_len(struct shm_du_buff * sdb)
-{
- assert(sdb);
-
- return sdb->du_tail - sdb->du_head;
-}
-
-uint8_t * shm_du_buff_head_alloc(struct shm_du_buff * sdb,
- size_t size)
-{
- assert(sdb);
-
- if (sdb->du_head < size)
- return NULL;
-
- sdb->du_head -= size;
-
- return (uint8_t *) (sdb + 1) + sdb->du_head;
-}
-
-uint8_t * shm_du_buff_tail_alloc(struct shm_du_buff * sdb,
- size_t size)
-{
- uint8_t * buf;
-
- assert(sdb);
-
- if (sdb->du_tail + size >= sdb->size)
- return NULL;
-
- buf = (uint8_t *) (sdb + 1) + sdb->du_tail;
-
- sdb->du_tail += size;
-
- return buf;
-}
-
-uint8_t * shm_du_buff_head_release(struct shm_du_buff * sdb,
- size_t size)
-{
- uint8_t * buf;
-
- assert(sdb);
- assert(!(size > sdb->du_tail - sdb->du_head));
-
- buf = (uint8_t *) (sdb + 1) + sdb->du_head;
-
- sdb->du_head += size;
-
- return buf;
-}
-
-uint8_t * shm_du_buff_tail_release(struct shm_du_buff * sdb,
- size_t size)
-{
- assert(sdb);
- assert(!(size > sdb->du_tail - sdb->du_head));
-
- sdb->du_tail -= size;
-
- return (uint8_t *) (sdb + 1) + sdb->du_tail;
-}
-
-void shm_du_buff_truncate(struct shm_du_buff * sdb,
- size_t len)
-{
- assert(sdb);
- assert(len <= sdb->size);
-
- sdb->du_tail = sdb->du_head + len;
-}
-
-int shm_du_buff_wait_ack(struct shm_du_buff * sdb)
-{
- __sync_add_and_fetch(&sdb->refs, 1);
-
- return 0;
-}
-
-int shm_du_buff_ack(struct shm_du_buff * sdb)
-{
- __sync_sub_and_fetch(&sdb->refs, 1);
- return 0;
-}
diff --git a/src/lib/sockets.c b/src/lib/sockets.c
index 5dfbcb5c..46586911 100644
--- a/src/lib/sockets.c
+++ b/src/lib/sockets.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* The sockets layer to communicate between daemons
*
diff --git a/src/lib/shm_flow_set.c b/src/lib/ssm/flow_set.c
index 39913fd1..73d0db55 100644
--- a/src/lib/shm_flow_set.c
+++ b/src/lib/ssm/flow_set.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Management of flow_sets for fqueue
*
@@ -23,11 +23,12 @@
#define _POSIX_C_SOURCE 200809L
#include "config.h"
+#include "ssm.h"
#include <ouroboros/errno.h>
#include <ouroboros/lockfile.h>
#include <ouroboros/pthread.h>
-#include <ouroboros/shm_flow_set.h>
+#include <ouroboros/ssm_flow_set.h>
#include <ouroboros/time.h>
#include <assert.h>
@@ -54,17 +55,17 @@
#define FN_MAX_CHARS 255
#define FS_PROT (PROT_READ | PROT_WRITE)
-#define QUEUESIZE ((SHM_BUFFER_SIZE) * sizeof(struct flowevent))
+#define QUEUESIZE ((SSM_RBUFF_SIZE) * sizeof(struct flowevent))
-#define SHM_FSET_FILE_SIZE (SYS_MAX_FLOWS * sizeof(ssize_t) \
+#define SSM_FSET_FILE_SIZE (SYS_MAX_FLOWS * sizeof(ssize_t) \
+ PROG_MAX_FQUEUES * sizeof(size_t) \
+ PROG_MAX_FQUEUES * sizeof(pthread_cond_t) \
+ PROG_MAX_FQUEUES * QUEUESIZE \
+ sizeof(pthread_mutex_t))
-#define fqueue_ptr(fs, idx) (fs->fqueues + (SHM_BUFFER_SIZE) * idx)
+#define fqueue_ptr(fs, idx) (fs->fqueues + (SSM_RBUFF_SIZE) * idx)
-struct shm_flow_set {
+struct ssm_flow_set {
ssize_t * mtable;
size_t * heads;
pthread_cond_t * conds;
@@ -74,15 +75,15 @@ struct shm_flow_set {
pid_t pid;
};
-static struct shm_flow_set * flow_set_create(pid_t pid,
+static struct ssm_flow_set * flow_set_create(pid_t pid,
int oflags)
{
- struct shm_flow_set * set;
+ struct ssm_flow_set * set;
ssize_t * shm_base;
char fn[FN_MAX_CHARS];
int fd;
- sprintf(fn, SHM_FLOW_SET_PREFIX "%d", pid);
+ sprintf(fn, SSM_FLOW_SET_PREFIX "%d", pid);
set = malloc(sizeof(*set));
if (set == NULL)
@@ -92,10 +93,10 @@ static struct shm_flow_set * flow_set_create(pid_t pid,
if (fd == -1)
goto fail_shm_open;
- if ((oflags & O_CREAT) && ftruncate(fd, SHM_FSET_FILE_SIZE) < 0)
+ if ((oflags & O_CREAT) && ftruncate(fd, SSM_FSET_FILE_SIZE) < 0)
goto fail_truncate;
- shm_base = mmap(NULL, SHM_FSET_FILE_SIZE, FS_PROT, MAP_SHARED, fd, 0);
+ shm_base = mmap(NULL, SSM_FSET_FILE_SIZE, FS_PROT, MAP_SHARED, fd, 0);
if (shm_base == MAP_FAILED)
goto fail_mmap;
@@ -106,7 +107,7 @@ static struct shm_flow_set * flow_set_create(pid_t pid,
set->conds = (pthread_cond_t *)(set->heads + PROG_MAX_FQUEUES);
set->fqueues = (struct flowevent *) (set->conds + PROG_MAX_FQUEUES);
set->lock = (pthread_mutex_t *)
- (set->fqueues + PROG_MAX_FQUEUES * (SHM_BUFFER_SIZE));
+ (set->fqueues + PROG_MAX_FQUEUES * (SSM_RBUFF_SIZE));
return set;
@@ -121,9 +122,9 @@ static struct shm_flow_set * flow_set_create(pid_t pid,
return NULL;
}
-struct shm_flow_set * shm_flow_set_create(pid_t pid)
+struct ssm_flow_set * ssm_flow_set_create(pid_t pid)
{
- struct shm_flow_set * set;
+ struct ssm_flow_set * set;
pthread_mutexattr_t mattr;
pthread_condattr_t cattr;
mode_t mask;
@@ -184,38 +185,38 @@ struct shm_flow_set * shm_flow_set_create(pid_t pid)
fail_mattr_set:
pthread_mutexattr_destroy(&mattr);
fail_mutexattr_init:
- shm_flow_set_destroy(set);
+ ssm_flow_set_destroy(set);
fail_set:
return NULL;
}
-struct shm_flow_set * shm_flow_set_open(pid_t pid)
+struct ssm_flow_set * ssm_flow_set_open(pid_t pid)
{
return flow_set_create(pid, O_RDWR);
}
-void shm_flow_set_destroy(struct shm_flow_set * set)
+void ssm_flow_set_destroy(struct ssm_flow_set * set)
{
char fn[FN_MAX_CHARS];
assert(set);
- sprintf(fn, SHM_FLOW_SET_PREFIX "%d", set->pid);
+ sprintf(fn, SSM_FLOW_SET_PREFIX "%d", set->pid);
- shm_flow_set_close(set);
+ ssm_flow_set_close(set);
shm_unlink(fn);
}
-void shm_flow_set_close(struct shm_flow_set * set)
+void ssm_flow_set_close(struct ssm_flow_set * set)
{
assert(set);
- munmap(set->mtable, SHM_FSET_FILE_SIZE);
+ munmap(set->mtable, SSM_FSET_FILE_SIZE);
free(set);
}
-void shm_flow_set_zero(struct shm_flow_set * set,
+void ssm_flow_set_zero(struct ssm_flow_set * set,
size_t idx)
{
ssize_t i = 0;
@@ -235,7 +236,7 @@ void shm_flow_set_zero(struct shm_flow_set * set,
}
-int shm_flow_set_add(struct shm_flow_set * set,
+int ssm_flow_set_add(struct ssm_flow_set * set,
size_t idx,
int flow_id)
{
@@ -257,7 +258,7 @@ int shm_flow_set_add(struct shm_flow_set * set,
return 0;
}
-void shm_flow_set_del(struct shm_flow_set * set,
+void ssm_flow_set_del(struct ssm_flow_set * set,
size_t idx,
int flow_id)
{
@@ -273,7 +274,7 @@ void shm_flow_set_del(struct shm_flow_set * set,
pthread_mutex_unlock(set->lock);
}
-int shm_flow_set_has(struct shm_flow_set * set,
+int ssm_flow_set_has(struct ssm_flow_set * set,
size_t idx,
int flow_id)
{
@@ -293,7 +294,7 @@ int shm_flow_set_has(struct shm_flow_set * set,
return ret;
}
-void shm_flow_set_notify(struct shm_flow_set * set,
+void ssm_flow_set_notify(struct ssm_flow_set * set,
int flow_id,
int event)
{
@@ -323,7 +324,7 @@ void shm_flow_set_notify(struct shm_flow_set * set,
}
-ssize_t shm_flow_set_wait(const struct shm_flow_set * set,
+ssize_t ssm_flow_set_wait(const struct ssm_flow_set * set,
size_t idx,
struct flowevent * fqueue,
const struct timespec * abstime)
diff --git a/src/lib/ssm/pool.c b/src/lib/ssm/pool.c
new file mode 100644
index 00000000..5c98b515
--- /dev/null
+++ b/src/lib/ssm/pool.c
@@ -0,0 +1,935 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * Secure Shared Memory Infrastructure (SSMI) Packet Buffer
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#define _POSIX_C_SOURCE 200809L
+
+#include "config.h"
+
+#include <ouroboros/errno.h>
+#include <ouroboros/pthread.h>
+#include <ouroboros/ssm_pool.h>
+
+#include "ssm.h"
+
+#include <assert.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+/* Global Shared Packet Pool (GSPP) configuration */
+static const struct ssm_size_class_cfg ssm_gspp_cfg[SSM_POOL_MAX_CLASSES] = {
+ { (1 << 8), SSM_GSPP_256_BLOCKS },
+ { (1 << 9), SSM_GSPP_512_BLOCKS },
+ { (1 << 10), SSM_GSPP_1K_BLOCKS },
+ { (1 << 11), SSM_GSPP_2K_BLOCKS },
+ { (1 << 12), SSM_GSPP_4K_BLOCKS },
+ { (1 << 14), SSM_GSPP_16K_BLOCKS },
+ { (1 << 16), SSM_GSPP_64K_BLOCKS },
+ { (1 << 18), SSM_GSPP_256K_BLOCKS },
+ { (1 << 20), SSM_GSPP_1M_BLOCKS },
+};
+
+/* Per-User Pool (PUP) configuration */
+static const struct ssm_size_class_cfg ssm_pup_cfg[SSM_POOL_MAX_CLASSES] = {
+ { (1 << 8), SSM_PUP_256_BLOCKS },
+ { (1 << 9), SSM_PUP_512_BLOCKS },
+ { (1 << 10), SSM_PUP_1K_BLOCKS },
+ { (1 << 11), SSM_PUP_2K_BLOCKS },
+ { (1 << 12), SSM_PUP_4K_BLOCKS },
+ { (1 << 14), SSM_PUP_16K_BLOCKS },
+ { (1 << 16), SSM_PUP_64K_BLOCKS },
+ { (1 << 18), SSM_PUP_256K_BLOCKS },
+ { (1 << 20), SSM_PUP_1M_BLOCKS },
+};
+
+#define PTR_TO_OFFSET(pool_base, ptr) \
+ ((uintptr_t)(ptr) - (uintptr_t)(pool_base))
+
+#define OFFSET_TO_PTR(pool_base, offset) \
+ ((offset == 0) ? NULL : (void *)((uintptr_t)(pool_base) + offset))
+
+#define GET_SHARD_FOR_PID(pid) ((int)((pid) % SSM_POOL_SHARDS))
+
+#define LOAD_RELAXED(ptr) \
+ (__atomic_load_n(ptr, __ATOMIC_RELAXED))
+
+#define LOAD_ACQUIRE(ptr) \
+ (__atomic_load_n(ptr, __ATOMIC_ACQUIRE))
+
+#define STORE_RELEASE(ptr, val) \
+ (__atomic_store_n(ptr, val, __ATOMIC_RELEASE))
+
+#define LOAD(ptr) \
+ (__atomic_load_n(ptr, __ATOMIC_SEQ_CST))
+
+#define STORE(ptr, val) \
+ (__atomic_store_n(ptr, val, __ATOMIC_SEQ_CST))
+
+#define FETCH_ADD(ptr, val) \
+ (__atomic_fetch_add(ptr, val, __ATOMIC_SEQ_CST))
+
+#define FETCH_SUB(ptr, val) \
+ (__atomic_fetch_sub(ptr, val, __ATOMIC_SEQ_CST))
+
+#define SSM_FILE_SIZE (SSM_POOL_TOTAL_SIZE + sizeof(struct _ssm_pool_hdr))
+#define SSM_GSPP_FILE_SIZE (SSM_GSPP_TOTAL_SIZE + sizeof(struct _ssm_pool_hdr))
+#define SSM_PUP_FILE_SIZE (SSM_PUP_TOTAL_SIZE + sizeof(struct _ssm_pool_hdr))
+
+#define IS_GSPP(uid) ((uid) == SSM_GSPP_UID)
+#define GET_POOL_TOTAL_SIZE(uid) (IS_GSPP(uid) ? SSM_GSPP_TOTAL_SIZE \
+ : SSM_PUP_TOTAL_SIZE)
+#define GET_POOL_FILE_SIZE(uid) (IS_GSPP(uid) ? SSM_GSPP_FILE_SIZE \
+ : SSM_PUP_FILE_SIZE)
+#define GET_POOL_CFG(uid) (IS_GSPP(uid) ? ssm_gspp_cfg : ssm_pup_cfg)
+
+struct ssm_pool {
+ uint8_t * shm_base; /* start of blocks */
+ struct _ssm_pool_hdr * hdr; /* shared memory header */
+ void * pool_base; /* base of the memory pool */
+ uid_t uid; /* user owner (0 = GSPP) */
+ size_t total_size; /* total data size */
+};
+
+static __inline__
+struct ssm_pk_buff * list_remove_head(struct _ssm_list_head * head,
+ void * base)
+{
+ uint32_t off;
+ uint32_t next_off;
+ struct ssm_pk_buff * blk;
+
+ assert(head != NULL);
+ assert(base != NULL);
+
+ off = LOAD(&head->head_offset);
+ if (off == 0)
+ return NULL;
+
+ /* Validate offset is within pool bounds */
+ if (off >= SSM_POOL_TOTAL_SIZE)
+ return NULL;
+
+ blk = OFFSET_TO_PTR(base, off);
+ next_off = LOAD(&blk->next_offset);
+
+
+
+ STORE(&head->head_offset, next_off);
+ STORE(&head->count, LOAD(&head->count) - 1);
+
+ return blk;
+}
+static __inline__ void list_add_head(struct _ssm_list_head * head,
+ struct ssm_pk_buff * blk,
+ void * base)
+{
+ uint32_t off;
+ uint32_t old;
+
+ assert(head != NULL);
+ assert(blk != NULL);
+ assert(base != NULL);
+
+ off = (uint32_t) PTR_TO_OFFSET(base, blk);
+ old = LOAD(&head->head_offset);
+
+ STORE(&blk->next_offset, old);
+ STORE(&head->head_offset, off);
+ STORE(&head->count, LOAD(&head->count) + 1);
+}
+
+static __inline__ int select_size_class(struct ssm_pool * pool,
+ size_t len)
+{
+ size_t sz;
+ int i;
+
+ assert(pool != NULL);
+
+ /* Total space needed: header + headspace + data + tailspace */
+ sz = sizeof(struct ssm_pk_buff) + SSM_PK_BUFF_HEADSPACE + len
+ + SSM_PK_BUFF_TAILSPACE;
+
+ for (i = 0; i < SSM_POOL_MAX_CLASSES; i++) {
+ struct _ssm_size_class * sc;
+
+ sc = &pool->hdr->size_classes[i];
+ if (sc->object_size > 0 && sz <= sc->object_size)
+ return i;
+ }
+
+ return -1;
+}
+
+static __inline__ int find_size_class_for_offset(struct ssm_pool * pool,
+ size_t offset)
+{
+ int c;
+
+ assert(pool != NULL);
+
+ for (c = 0; c < SSM_POOL_MAX_CLASSES; c++) {
+ struct _ssm_size_class * sc = &pool->hdr->size_classes[c];
+
+ if (sc->object_size == 0)
+ continue;
+
+ if (offset >= sc->pool_start &&
+ offset < sc->pool_start + sc->pool_size)
+ return c;
+ }
+
+ return -1;
+}
+
+static void init_size_classes(struct ssm_pool * pool)
+{
+ const struct ssm_size_class_cfg * cfg;
+ struct _ssm_size_class * sc;
+ struct _ssm_shard * shard;
+ pthread_mutexattr_t mattr;
+ pthread_condattr_t cattr;
+ uint8_t * region;
+ size_t offset;
+ int c; /* class iterator */
+ int s; /* shard iterator */
+ size_t i;
+
+ assert(pool != NULL);
+
+ /* Check if already initialized */
+ if (LOAD(&pool->hdr->initialized) != 0)
+ return;
+
+ cfg = GET_POOL_CFG(pool->uid);
+
+ pthread_mutexattr_init(&mattr);
+ pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED);
+#ifdef HAVE_ROBUST_MUTEX
+ pthread_mutexattr_setrobust(&mattr, PTHREAD_MUTEX_ROBUST);
+#endif
+ pthread_mutexattr_setprotocol(&mattr, PTHREAD_PRIO_INHERIT);
+
+ pthread_condattr_init(&cattr);
+ pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED);
+#ifndef __APPLE__
+ pthread_condattr_setclock(&cattr, PTHREAD_COND_CLOCK);
+#endif
+ offset = 0;
+
+ for (c = 0; c < SSM_POOL_MAX_CLASSES; c++) {
+ if (cfg[c].blocks == 0)
+ continue;
+
+ sc = &pool->hdr->size_classes[c];
+
+ sc->object_size = cfg[c].size;
+ sc->pool_start = offset;
+ sc->pool_size = cfg[c].size * cfg[c].blocks;
+ sc->object_count = cfg[c].blocks;
+
+ /* Initialize all shards */
+ for (s = 0; s < SSM_POOL_SHARDS; s++) {
+ shard = &sc->shards[s];
+
+ STORE(&shard->free_list.head_offset, 0);
+ STORE(&shard->free_list.count, 0);
+ STORE(&shard->free_count, 0);
+
+ pthread_mutex_init(&shard->mtx, &mattr);
+ pthread_cond_init(&shard->cond, &cattr);
+ }
+
+ /* Lazy distribution: put all blocks in shard 0 initially */
+ region = pool->shm_base + offset;
+
+ for (i = 0; i < sc->object_count; ++i) {
+ struct ssm_pk_buff * blk;
+
+ blk = (struct ssm_pk_buff *)
+ (region + i * sc->object_size);
+
+ STORE(&blk->refcount, 0);
+ blk->allocator_pid = 0;
+ STORE(&blk->next_offset, 0);
+
+ list_add_head(&sc->shards[0].free_list, blk,
+ pool->pool_base);
+ FETCH_ADD(&sc->shards[0].free_count, 1);
+ }
+
+ offset += sc->pool_size;
+ }
+
+ /* Mark as initialized - acts as memory barrier */
+ STORE(&pool->hdr->initialized, 1);
+
+ pthread_mutexattr_destroy(&mattr);
+ pthread_condattr_destroy(&cattr);
+}
+
+/*
+ * Reclaim all blocks allocated by a specific pid in a size class.
+ * Called with shard mutex held.
+ */
+static size_t reclaim_pid_from_sc(struct _ssm_size_class * sc,
+ struct _ssm_shard * shard,
+ void * pool_base,
+ pid_t pid)
+{
+ uint8_t * region;
+ size_t i;
+ size_t recovered = 0;
+ struct ssm_pk_buff * blk;
+
+ region = (uint8_t *) pool_base + sc->pool_start;
+
+ for (i = 0; i < sc->object_count; ++i) {
+ blk = (struct ssm_pk_buff *)(region + i * sc->object_size);
+
+ if (blk->allocator_pid == pid && LOAD(&blk->refcount) > 0) {
+ STORE(&blk->refcount, 0);
+ blk->allocator_pid = 0;
+ list_add_head(&shard->free_list, blk, pool_base);
+ FETCH_ADD(&shard->free_count, 1);
+ recovered++;
+ }
+ }
+
+ return recovered;
+}
+
+void ssm_pool_reclaim_orphans(struct ssm_pool * pool,
+ pid_t pid)
+{
+ size_t sc_idx;
+
+ if (pool == NULL || pid <= 0)
+ return;
+
+ for (sc_idx = 0; sc_idx < SSM_POOL_MAX_CLASSES; sc_idx++) {
+ struct _ssm_size_class * sc;
+ struct _ssm_shard * shard;
+
+ sc = &pool->hdr->size_classes[sc_idx];
+ if (sc->object_count == 0)
+ continue;
+
+ /* Reclaim to shard 0 for simplicity */
+ shard = &sc->shards[0];
+ robust_mutex_lock(&shard->mtx);
+ reclaim_pid_from_sc(sc, shard, pool->pool_base, pid);
+ pthread_mutex_unlock(&shard->mtx);
+ }
+}
+
+static __inline__
+struct ssm_pk_buff * try_alloc_from_shard(struct _ssm_shard * shard,
+ void * base)
+{
+ struct ssm_pk_buff * blk;
+
+ robust_mutex_lock(&shard->mtx);
+
+ if (LOAD(&shard->free_count) > 0) {
+ blk = list_remove_head(&shard->free_list, base);
+ if (blk != NULL) {
+ FETCH_SUB(&shard->free_count, 1);
+ return blk; /* Caller must unlock */
+ }
+ FETCH_SUB(&shard->free_count, 1);
+ }
+
+ pthread_mutex_unlock(&shard->mtx);
+ return NULL;
+}
+
+static __inline__ ssize_t init_block(struct ssm_pool * pool,
+ struct _ssm_size_class * sc,
+ struct _ssm_shard * shard,
+ struct ssm_pk_buff * blk,
+ size_t len,
+ uint8_t ** ptr,
+ struct ssm_pk_buff ** spb)
+{
+ STORE(&blk->refcount, 1);
+ blk->allocator_pid = getpid();
+ blk->size = (uint32_t) (sc->object_size -
+ sizeof(struct ssm_pk_buff));
+ blk->pk_head = SSM_PK_BUFF_HEADSPACE;
+ blk->pk_tail = blk->pk_head + (uint32_t) len;
+ blk->off = (uint32_t) PTR_TO_OFFSET(pool->pool_base, blk);
+
+ pthread_mutex_unlock(&shard->mtx);
+
+ *spb = blk;
+ if (ptr != NULL)
+ *ptr = blk->data + blk->pk_head;
+
+ return blk->off;
+}
+
+/* Non-blocking allocation from size class */
+static ssize_t alloc_from_sc(struct ssm_pool * pool,
+ int idx,
+ size_t len,
+ uint8_t ** ptr,
+ struct ssm_pk_buff ** spb)
+{
+ struct _ssm_size_class * sc;
+ struct ssm_pk_buff * blk;
+ int local;
+ int s;
+
+ assert(pool != NULL);
+ assert(idx >= 0 && idx < SSM_POOL_MAX_CLASSES);
+ assert(spb != NULL);
+
+ sc = &pool->hdr->size_classes[idx];
+ local = GET_SHARD_FOR_PID(getpid());
+
+ for (s = 0; s < SSM_POOL_SHARDS; s++) {
+ struct _ssm_shard * shard;
+ int idx;
+
+ idx = (local + s) % SSM_POOL_SHARDS;
+ shard = &sc->shards[idx];
+
+ blk = try_alloc_from_shard(shard, pool->pool_base);
+ if (blk != NULL)
+ return init_block(pool, sc, shard, blk, len, ptr, spb);
+ }
+
+ return -EAGAIN;
+}
+
+/* Blocking allocation from size class */
+static ssize_t alloc_from_sc_b(struct ssm_pool * pool,
+ int idx,
+ size_t len,
+ uint8_t ** ptr,
+ struct ssm_pk_buff ** spb,
+ const struct timespec * abstime)
+{
+ struct _ssm_size_class * sc;
+ struct _ssm_shard * shard;
+ struct ssm_pk_buff * blk = NULL;
+ int local;
+ int s;
+ int ret = 0;
+
+ assert(pool != NULL);
+ assert(idx >= 0 && idx < SSM_POOL_MAX_CLASSES);
+ assert(spb != NULL);
+
+ sc = &pool->hdr->size_classes[idx];
+ local = GET_SHARD_FOR_PID(getpid());
+
+ while (blk == NULL && ret != ETIMEDOUT) {
+ /* Try non-blocking allocation from any shard */
+ for (s = 0; s < SSM_POOL_SHARDS && blk == NULL; s++) {
+ shard = &sc->shards[(local + s) % SSM_POOL_SHARDS];
+ blk = try_alloc_from_shard(shard, pool->pool_base);
+ }
+
+ if (blk != NULL)
+ break;
+
+ /* Nothing available, wait for signal */
+ shard = &sc->shards[local];
+ robust_mutex_lock(&shard->mtx);
+ ret = robust_wait(&shard->cond, &shard->mtx, abstime);
+ pthread_mutex_unlock(&shard->mtx);
+ }
+
+ if (ret == ETIMEDOUT)
+ return -ETIMEDOUT;
+
+ return init_block(pool, sc, shard, blk, len, ptr, spb);
+}
+
+/* Generate pool filename: uid=0 for GSPP, uid>0 for PUP */
+static char * pool_filename(uid_t uid)
+{
+ char base[64];
+
+ if (IS_GSPP(uid))
+ snprintf(base, sizeof(base), "%s", SSM_GSPP_NAME);
+ else
+ snprintf(base, sizeof(base), SSM_PUP_NAME_FMT, (int) uid);
+
+ return strdup(base);
+}
+
+void ssm_pool_close(struct ssm_pool * pool)
+{
+ size_t file_size;
+
+ assert(pool != NULL);
+
+ file_size = GET_POOL_FILE_SIZE(pool->uid);
+
+ munmap(pool->shm_base, file_size);
+ free(pool);
+}
+
+void ssm_pool_destroy(struct ssm_pool * pool)
+{
+ char * fn;
+
+ assert(pool != NULL);
+
+ if (getpid() != pool->hdr->pid && kill(pool->hdr->pid, 0) == 0) {
+ ssm_pool_close(pool);
+ return;
+ }
+
+ fn = pool_filename(pool->uid);
+ if (fn == NULL) {
+ ssm_pool_close(pool);
+ return;
+ }
+
+ ssm_pool_close(pool);
+
+ shm_unlink(fn);
+ free(fn);
+}
+
+#define MM_FLAGS (PROT_READ | PROT_WRITE)
+static struct ssm_pool * __pool_create(const char * name,
+ int flags,
+ uid_t uid,
+ gid_t gid,
+ mode_t mode)
+{
+ struct ssm_pool * pool;
+ int fd;
+ uint8_t * shm_base;
+ size_t file_size;
+ size_t total_size;
+
+ file_size = GET_POOL_FILE_SIZE(uid);
+ total_size = GET_POOL_TOTAL_SIZE(uid);
+
+ pool = malloc(sizeof(*pool));
+ if (pool == NULL)
+ goto fail_pool;
+
+ fd = shm_open(name, flags, mode);
+ if (fd == -1)
+ goto fail_open;
+
+ if (flags & O_CREAT) {
+ if (ftruncate(fd, (off_t) file_size) < 0)
+ goto fail_truncate;
+ if (uid != geteuid() && fchown(fd, uid, gid) < 0)
+ goto fail_truncate;
+ }
+
+ shm_base = mmap(NULL, file_size, MM_FLAGS, MAP_SHARED, fd, 0);
+ if (shm_base == MAP_FAILED)
+ goto fail_truncate;
+
+ pool->shm_base = shm_base;
+ pool->pool_base = shm_base;
+ pool->hdr = (struct _ssm_pool_hdr *) (shm_base + total_size);
+ pool->uid = uid;
+ pool->total_size = total_size;
+
+ if (flags & O_CREAT)
+ pool->hdr->mapped_addr = shm_base;
+
+ close(fd);
+
+ return pool;
+
+ fail_truncate:
+ close(fd);
+ if (flags & O_CREAT)
+ shm_unlink(name);
+ fail_open:
+ free(pool);
+ fail_pool:
+ return NULL;
+}
+
+struct ssm_pool * ssm_pool_create(uid_t uid,
+ gid_t gid)
+{
+ struct ssm_pool * pool;
+ char * fn;
+ mode_t mask;
+ mode_t mode;
+ pthread_mutexattr_t mattr;
+ pthread_condattr_t cattr;
+
+ fn = pool_filename(uid);
+ if (fn == NULL)
+ goto fail_fn;
+
+ mode = IS_GSPP(uid) ? 0660 : 0600;
+ mask = umask(0);
+
+ pool = __pool_create(fn, O_CREAT | O_EXCL | O_RDWR, uid, gid, mode);
+
+ umask(mask);
+
+ if (pool == NULL)
+ goto fail_pool;
+
+ if (pthread_mutexattr_init(&mattr))
+ goto fail_mattr;
+
+ pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED);
+#ifdef HAVE_ROBUST_MUTEX
+ pthread_mutexattr_setrobust(&mattr, PTHREAD_MUTEX_ROBUST);
+#endif
+ if (pthread_mutex_init(&pool->hdr->mtx, &mattr))
+ goto fail_mutex;
+
+ if (pthread_condattr_init(&cattr))
+ goto fail_cattr;
+
+ pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED);
+#ifndef __APPLE__
+ pthread_condattr_setclock(&cattr, PTHREAD_COND_CLOCK);
+#endif
+ if (pthread_cond_init(&pool->hdr->healthy, &cattr))
+ goto fail_healthy;
+
+ pool->hdr->pid = getpid();
+ STORE(&pool->hdr->initialized, 0);
+
+ init_size_classes(pool);
+
+ pthread_mutexattr_destroy(&mattr);
+ pthread_condattr_destroy(&cattr);
+ free(fn);
+
+ return pool;
+
+ fail_healthy:
+ pthread_condattr_destroy(&cattr);
+ fail_cattr:
+ pthread_mutex_destroy(&pool->hdr->mtx);
+ fail_mutex:
+ pthread_mutexattr_destroy(&mattr);
+ fail_mattr:
+ ssm_pool_close(pool);
+ shm_unlink(fn);
+ fail_pool:
+ free(fn);
+ fail_fn:
+ return NULL;
+}
+
+struct ssm_pool * ssm_pool_open(uid_t uid)
+{
+ struct ssm_pool * pool;
+ char * fn;
+
+ fn = pool_filename(uid);
+ if (fn == NULL)
+ return NULL;
+
+ pool = __pool_create(fn, O_RDWR, uid, 0, 0);
+ if (pool != NULL)
+ init_size_classes(pool);
+
+ free(fn);
+
+ return pool;
+}
+
+void ssm_pool_gspp_purge(void)
+{
+ char * fn;
+
+ fn = pool_filename(SSM_GSPP_UID);
+ if (fn == NULL)
+ return;
+
+ shm_unlink(fn);
+ free(fn);
+}
+
+int ssm_pool_mlock(struct ssm_pool * pool)
+{
+ size_t file_size;
+
+ assert(pool != NULL);
+
+ file_size = GET_POOL_FILE_SIZE(pool->uid);
+
+ return mlock(pool->shm_base, file_size);
+}
+
+ssize_t ssm_pool_alloc(struct ssm_pool * pool,
+ size_t count,
+ uint8_t ** ptr,
+ struct ssm_pk_buff ** spb)
+{
+ int idx;
+
+ assert(pool != NULL);
+ assert(spb != NULL);
+
+ idx = select_size_class(pool, count);
+ if (idx >= 0)
+ return alloc_from_sc(pool, idx, count, ptr, spb);
+
+ return -EMSGSIZE;
+}
+
+ssize_t ssm_pool_alloc_b(struct ssm_pool * pool,
+ size_t count,
+ uint8_t ** ptr,
+ struct ssm_pk_buff ** spb,
+ const struct timespec * abstime)
+{
+ int idx;
+
+ assert(pool != NULL);
+ assert(spb != NULL);
+
+ idx = select_size_class(pool, count);
+ if (idx >= 0)
+ return alloc_from_sc_b(pool, idx, count, ptr, spb, abstime);
+
+ return -EMSGSIZE;
+}
+
+ssize_t ssm_pool_read(uint8_t ** dst,
+ struct ssm_pool * pool,
+ size_t off)
+{
+ struct ssm_pk_buff * blk;
+
+ assert(dst != NULL);
+ assert(pool != NULL);
+
+ blk = OFFSET_TO_PTR(pool->pool_base, off);
+ if (blk == NULL)
+ return -EINVAL;
+
+ *dst = blk->data + blk->pk_head;
+
+ return (ssize_t) (blk->pk_tail - blk->pk_head);
+}
+
+struct ssm_pk_buff * ssm_pool_get(struct ssm_pool * pool,
+ size_t off)
+{
+ struct ssm_pk_buff * blk;
+
+ assert(pool != NULL);
+
+ if (off == 0 || off >= pool->total_size)
+ return NULL;
+
+ blk = OFFSET_TO_PTR(pool->pool_base, off);
+ if (blk == NULL)
+ return NULL;
+
+ if (LOAD(&blk->refcount) == 0)
+ return NULL;
+
+ return blk;
+}
+
+int ssm_pool_remove(struct ssm_pool * pool,
+ size_t off)
+{
+ struct ssm_pk_buff * blk;
+ struct _ssm_size_class * sc;
+ struct _ssm_shard * shard;
+ int sc_idx;
+ int shard_idx;
+ uint16_t old_ref;
+
+ assert(pool != NULL);
+
+ if (off == 0 || off >= pool->total_size)
+ return -EINVAL;
+
+ blk = OFFSET_TO_PTR(pool->pool_base, off);
+ if (blk == NULL)
+ return -EINVAL;
+
+ sc_idx = find_size_class_for_offset(pool, off);
+ if (sc_idx < 0)
+ return -EINVAL;
+
+ sc = &pool->hdr->size_classes[sc_idx];
+
+ /* Free to allocator's shard (lazy distribution in action) */
+ shard_idx = GET_SHARD_FOR_PID(blk->allocator_pid);
+ shard = &sc->shards[shard_idx];
+
+ robust_mutex_lock(&shard->mtx);
+
+ old_ref = FETCH_SUB(&blk->refcount, 1);
+ if (old_ref > 1) {
+ /* Still referenced */
+ pthread_mutex_unlock(&shard->mtx);
+ return 0;
+ }
+
+ blk->allocator_pid = 0;
+#ifdef CONFIG_OUROBOROS_DEBUG
+ if (old_ref == 0) {
+ /* Underflow - double free attempt */
+ pthread_mutex_unlock(&shard->mtx);
+ abort();
+ }
+
+ /* Poison fields to detect use-after-free */
+ blk->pk_head = 0xDEAD;
+ blk->pk_tail = 0xBEEF;
+#endif
+ list_add_head(&shard->free_list, blk, pool->pool_base);
+ FETCH_ADD(&shard->free_count, 1);
+
+ pthread_cond_signal(&shard->cond);
+
+ pthread_mutex_unlock(&shard->mtx);
+
+ return 0;
+}
+
+size_t ssm_pk_buff_get_idx(struct ssm_pk_buff * spb)
+{
+ assert(spb != NULL);
+
+ return spb->off;
+}
+
+uint8_t * ssm_pk_buff_head(struct ssm_pk_buff * spb)
+{
+ assert(spb != NULL);
+
+ return spb->data + spb->pk_head;
+}
+
+uint8_t * ssm_pk_buff_tail(struct ssm_pk_buff * spb)
+{
+ assert(spb != NULL);
+
+ return spb->data + spb->pk_tail;
+}
+
+size_t ssm_pk_buff_len(struct ssm_pk_buff * spb)
+{
+ assert(spb != NULL);
+
+ return spb->pk_tail - spb->pk_head;
+}
+
+uint8_t * ssm_pk_buff_head_alloc(struct ssm_pk_buff * spb,
+ size_t size)
+{
+ assert(spb != NULL);
+
+ if (spb->pk_head < size)
+ return NULL;
+
+ spb->pk_head -= size;
+
+ return spb->data + spb->pk_head;
+}
+
+uint8_t * ssm_pk_buff_tail_alloc(struct ssm_pk_buff * spb,
+ size_t size)
+{
+ uint8_t * buf;
+
+ assert(spb != NULL);
+
+ if (spb->pk_tail + size >= spb->size)
+ return NULL;
+
+ buf = spb->data + spb->pk_tail;
+
+ spb->pk_tail += size;
+
+ return buf;
+}
+
+uint8_t * ssm_pk_buff_head_release(struct ssm_pk_buff * spb,
+ size_t size)
+{
+ uint8_t * buf;
+
+ assert(spb != NULL);
+ assert(!(size > spb->pk_tail - spb->pk_head));
+
+ buf = spb->data + spb->pk_head;
+
+ spb->pk_head += size;
+
+ return buf;
+}
+
+uint8_t * ssm_pk_buff_tail_release(struct ssm_pk_buff * spb,
+ size_t size)
+{
+ assert(spb != NULL);
+ assert(!(size > spb->pk_tail - spb->pk_head));
+
+ spb->pk_tail -= size;
+
+ return spb->data + spb->pk_tail;
+}
+
+void ssm_pk_buff_truncate(struct ssm_pk_buff * spb,
+ size_t len)
+{
+ assert(spb != NULL);
+ assert(len <= spb->size);
+
+ spb->pk_tail = spb->pk_head + len;
+}
+
+int ssm_pk_buff_wait_ack(struct ssm_pk_buff * spb)
+{
+ assert(spb != NULL);
+
+ FETCH_ADD(&spb->refcount, 1);
+
+ return 0;
+}
+
+int ssm_pk_buff_ack(struct ssm_pk_buff * spb)
+{
+ assert(spb != NULL);
+
+ FETCH_SUB(&spb->refcount, 1);
+
+ return 0;
+}
diff --git a/src/lib/ssm/rbuff.c b/src/lib/ssm/rbuff.c
new file mode 100644
index 00000000..e4558c31
--- /dev/null
+++ b/src/lib/ssm/rbuff.c
@@ -0,0 +1,449 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * Ring buffer implementations for incoming packets
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#define _POSIX_C_SOURCE 200809L
+
+#include "config.h"
+#include "ssm.h"
+
+#include <ouroboros/ssm_rbuff.h>
+#include <ouroboros/lockfile.h>
+#include <ouroboros/errno.h>
+#include <ouroboros/fccntl.h>
+#include <ouroboros/pthread.h>
+#include <ouroboros/time.h>
+
+#include <assert.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+
+#define FN_MAX_CHARS 255
+
+#define SSM_RBUFF_FILESIZE ((SSM_RBUFF_SIZE) * sizeof(ssize_t) \
+ + 3 * sizeof(size_t) \
+ + sizeof(pthread_mutex_t) \
+ + 2 * sizeof(pthread_cond_t))
+
+#define MODB(x) ((x) & (SSM_RBUFF_SIZE - 1))
+
+#define LOAD_RELAXED(ptr) (__atomic_load_n(ptr, __ATOMIC_RELAXED))
+#define LOAD_ACQUIRE(ptr) (__atomic_load_n(ptr, __ATOMIC_ACQUIRE))
+#define STORE_RELEASE(ptr, val) \
+ (__atomic_store_n(ptr, val, __ATOMIC_RELEASE))
+
+#define HEAD(rb) (rb->shm_base[LOAD_RELAXED(rb->head)])
+#define TAIL(rb) (rb->shm_base[LOAD_RELAXED(rb->tail)])
+#define HEAD_IDX(rb) (LOAD_ACQUIRE(rb->head))
+#define TAIL_IDX(rb) (LOAD_ACQUIRE(rb->tail))
+#define ADVANCE_HEAD(rb) \
+ (STORE_RELEASE(rb->head, MODB(LOAD_RELAXED(rb->head) + 1)))
+#define ADVANCE_TAIL(rb) \
+ (STORE_RELEASE(rb->tail, MODB(LOAD_RELAXED(rb->tail) + 1)))
+#define QUEUED(rb) (MODB(HEAD_IDX(rb) - TAIL_IDX(rb)))
+#define IS_FULL(rb) (QUEUED(rb) == (SSM_RBUFF_SIZE - 1))
+#define IS_EMPTY(rb) (HEAD_IDX(rb) == TAIL_IDX(rb))
+
+struct ssm_rbuff {
+ ssize_t * shm_base; /* start of shared memory */
+ size_t * head; /* start of ringbuffer */
+ size_t * tail;
+ size_t * acl; /* access control */
+ pthread_mutex_t * mtx; /* lock for cond vars only */
+ pthread_cond_t * add; /* signal when new data */
+ pthread_cond_t * del; /* signal when data removed */
+ pid_t pid; /* pid of the owner */
+ int flow_id; /* flow_id of the flow */
+};
+
+#define MM_FLAGS (PROT_READ | PROT_WRITE)
+
+static struct ssm_rbuff * rbuff_create(pid_t pid,
+ int flow_id,
+ int flags)
+{
+ struct ssm_rbuff * rb;
+ int fd;
+ ssize_t * shm_base;
+ char fn[FN_MAX_CHARS];
+
+ sprintf(fn, SSM_RBUFF_PREFIX "%d.%d", pid, flow_id);
+
+ rb = malloc(sizeof(*rb));
+ if (rb == NULL)
+ goto fail_malloc;
+
+ fd = shm_open(fn, flags, 0666);
+ if (fd == -1)
+ goto fail_open;
+
+ if ((flags & O_CREAT) && ftruncate(fd, SSM_RBUFF_FILESIZE) < 0)
+ goto fail_truncate;
+
+ shm_base = mmap(NULL, SSM_RBUFF_FILESIZE, MM_FLAGS, MAP_SHARED, fd, 0);
+
+ close(fd);
+
+ rb->shm_base = shm_base;
+ rb->head = (size_t *) (rb->shm_base + (SSM_RBUFF_SIZE));
+ rb->tail = (size_t *) (rb->head + 1);
+ rb->acl = (size_t *) (rb->tail + 1);
+ rb->mtx = (pthread_mutex_t *) (rb->acl + 1);
+ rb->add = (pthread_cond_t *) (rb->mtx + 1);
+ rb->del = rb->add + 1;
+ rb->pid = pid;
+ rb->flow_id = flow_id;
+
+ return rb;
+
+ fail_truncate:
+ close(fd);
+ if (flags & O_CREAT)
+ shm_unlink(fn);
+ fail_open:
+ free(rb);
+ fail_malloc:
+ return NULL;
+}
+
+static void rbuff_destroy(struct ssm_rbuff * rb)
+{
+ munmap(rb->shm_base, SSM_RBUFF_FILESIZE);
+
+ free(rb);
+}
+
+struct ssm_rbuff * ssm_rbuff_create(pid_t pid,
+ int flow_id)
+{
+ struct ssm_rbuff * rb;
+ pthread_mutexattr_t mattr;
+ pthread_condattr_t cattr;
+ mode_t mask;
+
+ mask = umask(0);
+
+ rb = rbuff_create(pid, flow_id, O_CREAT | O_EXCL | O_RDWR);
+
+ umask(mask);
+
+ if (rb == NULL)
+ goto fail_rb;
+
+ if (pthread_mutexattr_init(&mattr))
+ goto fail_mattr;
+
+ pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED);
+#ifdef HAVE_ROBUST_MUTEX
+ pthread_mutexattr_setrobust(&mattr, PTHREAD_MUTEX_ROBUST);
+#endif
+ if (pthread_mutex_init(rb->mtx, &mattr))
+ goto fail_mutex;
+
+ if (pthread_condattr_init(&cattr))
+ goto fail_cattr;
+
+ pthread_condattr_setpshared(&cattr, PTHREAD_PROCESS_SHARED);
+#ifndef __APPLE__
+ pthread_condattr_setclock(&cattr, PTHREAD_COND_CLOCK);
+#endif
+ if (pthread_cond_init(rb->add, &cattr))
+ goto fail_add;
+
+ if (pthread_cond_init(rb->del, &cattr))
+ goto fail_del;
+
+ *rb->acl = ACL_RDWR;
+ *rb->head = 0;
+ *rb->tail = 0;
+
+ rb->pid = pid;
+ rb->flow_id = flow_id;
+
+ pthread_mutexattr_destroy(&mattr);
+ pthread_condattr_destroy(&cattr);
+
+ return rb;
+
+ fail_del:
+ pthread_cond_destroy(rb->add);
+ fail_add:
+ pthread_condattr_destroy(&cattr);
+ fail_cattr:
+ pthread_mutex_destroy(rb->mtx);
+ fail_mutex:
+ pthread_mutexattr_destroy(&mattr);
+ fail_mattr:
+ ssm_rbuff_destroy(rb);
+ fail_rb:
+ return NULL;
+}
+
+void ssm_rbuff_destroy(struct ssm_rbuff * rb)
+{
+ char fn[FN_MAX_CHARS];
+
+ assert(rb != NULL);
+
+ sprintf(fn, SSM_RBUFF_PREFIX "%d.%d", rb->pid, rb->flow_id);
+
+ ssm_rbuff_close(rb);
+
+ shm_unlink(fn);
+}
+
+struct ssm_rbuff * ssm_rbuff_open(pid_t pid,
+ int flow_id)
+{
+ return rbuff_create(pid, flow_id, O_RDWR);
+}
+
+void ssm_rbuff_close(struct ssm_rbuff * rb)
+{
+ assert(rb);
+
+ rbuff_destroy(rb);
+}
+
+int ssm_rbuff_write(struct ssm_rbuff * rb,
+ size_t idx)
+{
+ size_t acl;
+ bool was_empty;
+ int ret = 0;
+
+ assert(rb != NULL);
+
+ acl = __atomic_load_n(rb->acl, __ATOMIC_SEQ_CST);
+ if (acl != ACL_RDWR) {
+ if (acl & ACL_FLOWDOWN) {
+ ret = -EFLOWDOWN;
+ goto fail_acl;
+ }
+ if (acl & ACL_RDONLY) {
+ ret = -ENOTALLOC;
+ goto fail_acl;
+ }
+ }
+
+ robust_mutex_lock(rb->mtx);
+
+ if (IS_FULL(rb)) {
+ ret = -EAGAIN;
+ goto fail_mutex;
+ }
+
+ was_empty = IS_EMPTY(rb);
+
+ HEAD(rb) = (ssize_t) idx;
+ ADVANCE_HEAD(rb);
+
+ if (was_empty)
+ pthread_cond_broadcast(rb->add);
+
+ pthread_mutex_unlock(rb->mtx);
+
+ return 0;
+
+ fail_mutex:
+ pthread_mutex_unlock(rb->mtx);
+ fail_acl:
+ return ret;
+}
+
+int ssm_rbuff_write_b(struct ssm_rbuff * rb,
+ size_t idx,
+ const struct timespec * abstime)
+{
+ size_t acl;
+ int ret = 0;
+ bool was_empty;
+
+ assert(rb != NULL);
+
+ acl = __atomic_load_n(rb->acl, __ATOMIC_SEQ_CST);
+ if (acl != ACL_RDWR) {
+ if (acl & ACL_FLOWDOWN) {
+ ret = -EFLOWDOWN;
+ goto fail_acl;
+ }
+ if (acl & ACL_RDONLY) {
+ ret = -ENOTALLOC;
+ goto fail_acl;
+ }
+ }
+
+ robust_mutex_lock(rb->mtx);
+
+ pthread_cleanup_push(__cleanup_mutex_unlock, rb->mtx);
+
+ while (IS_FULL(rb) && ret != -ETIMEDOUT) {
+ acl = __atomic_load_n(rb->acl, __ATOMIC_SEQ_CST);
+ if (acl & ACL_FLOWDOWN) {
+ ret = -EFLOWDOWN;
+ break;
+ }
+ ret = -robust_wait(rb->del, rb->mtx, abstime);
+ }
+
+ pthread_cleanup_pop(false);
+
+ if (ret != -ETIMEDOUT && ret != -EFLOWDOWN) {
+ was_empty = IS_EMPTY(rb);
+ HEAD(rb) = (ssize_t) idx;
+ ADVANCE_HEAD(rb);
+ if (was_empty)
+ pthread_cond_broadcast(rb->add);
+ }
+
+ pthread_mutex_unlock(rb->mtx);
+
+ fail_acl:
+ return ret;
+}
+
+static int check_rb_acl(struct ssm_rbuff * rb)
+{
+ size_t acl;
+
+ assert(rb != NULL);
+
+ acl = __atomic_load_n(rb->acl, __ATOMIC_SEQ_CST);
+
+ if (acl & ACL_FLOWDOWN)
+ return -EFLOWDOWN;
+
+ if (acl & ACL_FLOWPEER)
+ return -EFLOWPEER;
+
+ return -EAGAIN;
+}
+
+ssize_t ssm_rbuff_read(struct ssm_rbuff * rb)
+{
+ ssize_t ret;
+
+ assert(rb != NULL);
+
+ if (IS_EMPTY(rb))
+ return check_rb_acl(rb);
+
+ robust_mutex_lock(rb->mtx);
+
+ ret = TAIL(rb);
+ ADVANCE_TAIL(rb);
+
+ pthread_cond_broadcast(rb->del);
+
+ pthread_mutex_unlock(rb->mtx);
+
+ return ret;
+}
+
+ssize_t ssm_rbuff_read_b(struct ssm_rbuff * rb,
+ const struct timespec * abstime)
+{
+ ssize_t idx = -1;
+ size_t acl;
+
+ assert(rb != NULL);
+
+ acl = __atomic_load_n(rb->acl, __ATOMIC_SEQ_CST);
+ if (IS_EMPTY(rb) && (acl & ACL_FLOWDOWN))
+ return -EFLOWDOWN;
+
+ robust_mutex_lock(rb->mtx);
+
+ pthread_cleanup_push(__cleanup_mutex_unlock, rb->mtx);
+
+ while (IS_EMPTY(rb) &&
+ idx != -ETIMEDOUT &&
+ check_rb_acl(rb) == -EAGAIN) {
+ idx = -robust_wait(rb->add, rb->mtx, abstime);
+ }
+
+ pthread_cleanup_pop(false);
+
+ if (!IS_EMPTY(rb)) {
+ idx = TAIL(rb);
+ ADVANCE_TAIL(rb);
+ pthread_cond_broadcast(rb->del);
+ } else if (idx != -ETIMEDOUT) {
+ idx = check_rb_acl(rb);
+ }
+
+ pthread_mutex_unlock(rb->mtx);
+
+ assert(idx != -EAGAIN);
+
+ return idx;
+}
+
+void ssm_rbuff_set_acl(struct ssm_rbuff * rb,
+ uint32_t flags)
+{
+ assert(rb != NULL);
+
+ __atomic_store_n(rb->acl, (size_t) flags, __ATOMIC_SEQ_CST);
+}
+
+uint32_t ssm_rbuff_get_acl(struct ssm_rbuff * rb)
+{
+ assert(rb != NULL);
+
+ return (uint32_t) __atomic_load_n(rb->acl, __ATOMIC_SEQ_CST);
+}
+
+void ssm_rbuff_fini(struct ssm_rbuff * rb)
+{
+ assert(rb != NULL);
+
+ robust_mutex_lock(rb->mtx);
+
+ pthread_cleanup_push(__cleanup_mutex_unlock, rb->mtx);
+
+ while (!IS_EMPTY(rb))
+ robust_wait(rb->del, rb->mtx, NULL);
+
+ pthread_cleanup_pop(true);
+}
+
+size_t ssm_rbuff_queued(struct ssm_rbuff * rb)
+{
+ assert(rb != NULL);
+
+ return QUEUED(rb);
+}
+
+int ssm_rbuff_mlock(struct ssm_rbuff * rb)
+{
+ assert(rb != NULL);
+
+ return mlock(rb->shm_base, SSM_RBUFF_FILESIZE);
+}
diff --git a/src/lib/ssm/ssm.h.in b/src/lib/ssm/ssm.h.in
new file mode 100644
index 00000000..b9246c8b
--- /dev/null
+++ b/src/lib/ssm/ssm.h.in
@@ -0,0 +1,171 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * Secure Shared Memory configuration
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * version 2.1 as published by the Free Software Foundation.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#ifndef OUROBOROS_LIB_SSM_H
+#define OUROBOROS_LIB_SSM_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdatomic.h>
+#include <sys/types.h>
+
+/* Pool naming configuration */
+#define SSM_PREFIX "@SSM_PREFIX@"
+#define SSM_GSPP_NAME "@SSM_GSPP_NAME@"
+#define SSM_PUP_NAME_FMT "@SSM_PUP_NAME_FMT@"
+#define SSM_GSPP_UID 0
+
+/* Legacy SSM constants */
+#define SSM_RBUFF_PREFIX "@SSM_RBUFF_PREFIX@"
+#define SSM_FLOW_SET_PREFIX "@SSM_FLOW_SET_PREFIX@"
+#define SSM_POOL_NAME "@SSM_POOL_NAME@"
+#define SSM_POOL_BLOCKS @SSM_POOL_BLOCKS@
+#define SSM_RBUFF_SIZE @SSM_RBUFF_SIZE@
+
+/* Packet buffer space reservation */
+#define SSM_PK_BUFF_HEADSPACE @SSM_PK_BUFF_HEADSPACE@
+#define SSM_PK_BUFF_TAILSPACE @SSM_PK_BUFF_TAILSPACE@
+
+/* Global Shared Packet Pool (GSPP) - for privileged processes */
+#define SSM_GSPP_256_BLOCKS @SSM_GSPP_256_BLOCKS@
+#define SSM_GSPP_512_BLOCKS @SSM_GSPP_512_BLOCKS@
+#define SSM_GSPP_1K_BLOCKS @SSM_GSPP_1K_BLOCKS@
+#define SSM_GSPP_2K_BLOCKS @SSM_GSPP_2K_BLOCKS@
+#define SSM_GSPP_4K_BLOCKS @SSM_GSPP_4K_BLOCKS@
+#define SSM_GSPP_16K_BLOCKS @SSM_GSPP_16K_BLOCKS@
+#define SSM_GSPP_64K_BLOCKS @SSM_GSPP_64K_BLOCKS@
+#define SSM_GSPP_256K_BLOCKS @SSM_GSPP_256K_BLOCKS@
+#define SSM_GSPP_1M_BLOCKS @SSM_GSPP_1M_BLOCKS@
+#define SSM_GSPP_TOTAL_SIZE @SSM_GSPP_TOTAL_SIZE@
+
+/* Per-User Pool (PUP) - for unprivileged applications */
+#define SSM_PUP_256_BLOCKS @SSM_PUP_256_BLOCKS@
+#define SSM_PUP_512_BLOCKS @SSM_PUP_512_BLOCKS@
+#define SSM_PUP_1K_BLOCKS @SSM_PUP_1K_BLOCKS@
+#define SSM_PUP_2K_BLOCKS @SSM_PUP_2K_BLOCKS@
+#define SSM_PUP_4K_BLOCKS @SSM_PUP_4K_BLOCKS@
+#define SSM_PUP_16K_BLOCKS @SSM_PUP_16K_BLOCKS@
+#define SSM_PUP_64K_BLOCKS @SSM_PUP_64K_BLOCKS@
+#define SSM_PUP_256K_BLOCKS @SSM_PUP_256K_BLOCKS@
+#define SSM_PUP_1M_BLOCKS @SSM_PUP_1M_BLOCKS@
+#define SSM_PUP_TOTAL_SIZE @SSM_PUP_TOTAL_SIZE@
+
+/* Legacy pool blocks (same as GSPP for compatibility) */
+#define SSM_POOL_256_BLOCKS @SSM_POOL_256_BLOCKS@
+#define SSM_POOL_512_BLOCKS @SSM_POOL_512_BLOCKS@
+#define SSM_POOL_1K_BLOCKS @SSM_POOL_1K_BLOCKS@
+#define SSM_POOL_2K_BLOCKS @SSM_POOL_2K_BLOCKS@
+#define SSM_POOL_4K_BLOCKS @SSM_POOL_4K_BLOCKS@
+#define SSM_POOL_16K_BLOCKS @SSM_POOL_16K_BLOCKS@
+#define SSM_POOL_64K_BLOCKS @SSM_POOL_64K_BLOCKS@
+#define SSM_POOL_256K_BLOCKS @SSM_POOL_256K_BLOCKS@
+#define SSM_POOL_1M_BLOCKS @SSM_POOL_1M_BLOCKS@
+#define SSM_POOL_TOTAL_SIZE @SSM_POOL_TOTAL_SIZE@
+
+/* Size class configuration */
+#define SSM_POOL_MAX_CLASSES 9
+#define SSM_POOL_SHARDS @SSM_POOL_SHARDS@
+
+/* Internal structures - exposed for testing */
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <errno.h>
+#include <pthread.h>
+
+#include <ouroboros/pthread.h>
+
+static __inline__ void robust_mutex_lock(pthread_mutex_t * mtx)
+{
+#ifndef HAVE_ROBUST_MUTEX
+ pthread_mutex_lock(mtx);
+#else
+ if (pthread_mutex_lock(mtx) == EOWNERDEAD)
+ pthread_mutex_consistent(mtx);
+#endif
+}
+
+static __inline__ int robust_wait(pthread_cond_t * cond,
+ pthread_mutex_t * mtx,
+ const struct timespec * abstime)
+{
+ int ret = __timedwait(cond, mtx, abstime);
+#ifdef HAVE_ROBUST_MUTEX
+ if (ret == EOWNERDEAD)
+ pthread_mutex_consistent(mtx);
+#endif
+ return ret;
+}
+
+/* Packet buffer structure used by pool, rbuff, and tests */
+struct ssm_pk_buff {
+ uint32_t next_offset; /* List linkage (pool < 4GB) */
+ uint16_t refcount; /* Reference count (app + rtx) */
+ pid_t allocator_pid; /* For orphan detection */
+ uint32_t size; /* Block size (max 1MB) */
+ uint32_t pk_head; /* Head offset into data */
+ uint32_t pk_tail; /* Tail offset into data */
+ uint32_t off; /* Block offset in pool */
+ uint8_t data[]; /* Packet data */
+};
+
+/* Size class configuration table */
+struct ssm_size_class_cfg {
+ size_t size;
+ size_t blocks;
+};
+
+struct _ssm_list_head {
+ uint32_t head_offset;
+ uint32_t count;
+};
+
+struct _ssm_shard {
+ pthread_mutex_t mtx;
+ pthread_cond_t cond;
+ struct _ssm_list_head free_list;
+ size_t free_count;
+};
+
+struct _ssm_size_class {
+ struct _ssm_shard shards[SSM_POOL_SHARDS];
+ size_t object_size;
+ size_t pool_start;
+ size_t pool_size;
+ size_t object_count;
+};
+
+struct _ssm_pool_hdr {
+ pthread_mutex_t mtx;
+ pthread_cond_t healthy;
+ pid_t pid;
+ uint32_t initialized;
+ void * mapped_addr;
+ struct _ssm_size_class size_classes[SSM_POOL_MAX_CLASSES];
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* OUROBOROS_LIB_SSM_H */
diff --git a/src/lib/ssm/tests/CMakeLists.txt b/src/lib/ssm/tests/CMakeLists.txt
new file mode 100644
index 00000000..d622d41c
--- /dev/null
+++ b/src/lib/ssm/tests/CMakeLists.txt
@@ -0,0 +1,21 @@
+get_filename_component(PARENT_PATH ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY)
+get_filename_component(PARENT_DIR ${PARENT_PATH} NAME)
+
+compute_test_prefix()
+
+create_test_sourcelist(${PARENT_DIR}_tests test_suite.c
+ # Add new tests here
+ pool_test.c
+ pool_sharding_test.c
+ rbuff_test.c
+ flow_set_test.c
+ )
+
+add_executable(${PARENT_DIR}_test ${${PARENT_DIR}_tests})
+
+disable_test_logging_for_target(${PARENT_DIR}_test)
+target_link_libraries(${PARENT_DIR}_test ouroboros-common)
+
+add_dependencies(build_tests ${PARENT_DIR}_test)
+
+ouroboros_register_tests(TARGET ${PARENT_DIR}_test TESTS ${${PARENT_DIR}_tests})
diff --git a/src/lib/ssm/tests/flow_set_test.c b/src/lib/ssm/tests/flow_set_test.c
new file mode 100644
index 00000000..1bb97b2f
--- /dev/null
+++ b/src/lib/ssm/tests/flow_set_test.c
@@ -0,0 +1,255 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * Test of the SSM flow set
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#if defined(__linux__) || defined(__CYGWIN__)
+#define _DEFAULT_SOURCE
+#else
+#define _POSIX_C_SOURCE 200112L
+#endif
+
+#include "config.h"
+#include "ssm.h"
+
+#include <test/test.h>
+#include <ouroboros/ssm_flow_set.h>
+#include <ouroboros/errno.h>
+#include <ouroboros/time.h>
+
+#include <stdio.h>
+#include <unistd.h>
+#include <pthread.h>
+
+static int test_ssm_flow_set_create_destroy(void)
+{
+ struct ssm_flow_set * set;
+ pid_t pid;
+
+ TEST_START();
+
+ pid = getpid();
+
+ set = ssm_flow_set_create(pid);
+ if (set == NULL) {
+ printf("Failed to create flow set.\n");
+ goto fail;
+ }
+
+ ssm_flow_set_destroy(set);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_ssm_flow_set_add_del_has(void)
+{
+ struct ssm_flow_set * set;
+ pid_t pid;
+ size_t idx = 0;
+ int flow_id = 42;
+
+ TEST_START();
+
+ pid = getpid();
+
+ set = ssm_flow_set_create(pid);
+ if (set == NULL) {
+ printf("Failed to create flow set.\n");
+ goto fail;
+ }
+
+ if (ssm_flow_set_has(set, idx, flow_id)) {
+ printf("Flow should not be in set initially.\n");
+ goto fail_destroy;
+ }
+
+ if (ssm_flow_set_add(set, idx, flow_id) < 0) {
+ printf("Failed to add flow to set.\n");
+ goto fail_destroy;
+ }
+
+ if (!ssm_flow_set_has(set, idx, flow_id)) {
+ printf("Flow should be in set after add.\n");
+ goto fail_destroy;
+ }
+
+ /* Adding same flow again should fail */
+ if (ssm_flow_set_add(set, idx, flow_id) != -EPERM) {
+ printf("Should not be able to add flow twice.\n");
+ goto fail_destroy;
+ }
+
+ ssm_flow_set_del(set, idx, flow_id);
+
+ if (ssm_flow_set_has(set, idx, flow_id)) {
+ printf("Flow should not be in set after delete.\n");
+ goto fail_destroy;
+ }
+
+ ssm_flow_set_destroy(set);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+fail_destroy:
+ ssm_flow_set_destroy(set);
+fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_ssm_flow_set_zero(void)
+{
+ struct ssm_flow_set * set;
+ pid_t pid;
+ size_t idx = 0;
+ int flow_id1 = 10;
+ int flow_id2 = 20;
+
+ TEST_START();
+
+ pid = getpid();
+
+ set = ssm_flow_set_create(pid);
+ if (set == NULL) {
+ printf("Failed to create flow set.\n");
+ goto fail;
+ }
+
+ if (ssm_flow_set_add(set, idx, flow_id1) < 0) {
+ printf("Failed to add flow1 to set.\n");
+ goto fail_destroy;
+ }
+
+ if (ssm_flow_set_add(set, idx, flow_id2) < 0) {
+ printf("Failed to add flow2 to set.\n");
+ goto fail_destroy;
+ }
+
+ ssm_flow_set_zero(set, idx);
+
+ if (ssm_flow_set_has(set, idx, flow_id1)) {
+ printf("Flow1 should not be in set after zero.\n");
+ goto fail_destroy;
+ }
+
+ if (ssm_flow_set_has(set, idx, flow_id2)) {
+ printf("Flow2 should not be in set after zero.\n");
+ goto fail_destroy;
+ }
+
+ ssm_flow_set_destroy(set);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+fail_destroy:
+ ssm_flow_set_destroy(set);
+fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_ssm_flow_set_notify_wait(void)
+{
+ struct ssm_flow_set * set;
+ pid_t pid;
+ size_t idx = 0;
+ int flow_id = 100;
+ struct flowevent events[SSM_RBUFF_SIZE];
+ struct timespec timeout;
+ ssize_t ret;
+
+ TEST_START();
+
+ pid = getpid();
+
+ set = ssm_flow_set_create(pid);
+ if (set == NULL) {
+ printf("Failed to create flow set.\n");
+ goto fail;
+ }
+
+ if (ssm_flow_set_add(set, idx, flow_id) < 0) {
+ printf("Failed to add flow to set.\n");
+ goto fail_destroy;
+ }
+
+ /* Test immediate timeout when no events */
+ clock_gettime(PTHREAD_COND_CLOCK, &timeout);
+ ret = ssm_flow_set_wait(set, idx, events, &timeout);
+ if (ret != -ETIMEDOUT) {
+ printf("Wait should timeout immediately when no events.\n");
+ goto fail_destroy;
+ }
+
+ /* Notify an event */
+ ssm_flow_set_notify(set, flow_id, FLOW_PKT);
+
+ /* Should be able to read the event immediately */
+ clock_gettime(PTHREAD_COND_CLOCK, &timeout);
+ ts_add(&timeout, &timeout, &((struct timespec) {1, 0}));
+
+ ret = ssm_flow_set_wait(set, idx, events, &timeout);
+ if (ret != 1) {
+ printf("Wait should return 1 event, got %zd.\n", ret);
+ goto fail_destroy;
+ }
+
+ if (events[0].flow_id != flow_id) {
+ printf("Event flow_id mismatch: expected %d, got %d.\n",
+ flow_id, events[0].flow_id);
+ goto fail_destroy;
+ }
+
+ if (events[0].event != FLOW_PKT) {
+ printf("Event type mismatch: expected %d, got %d.\n",
+ FLOW_PKT, events[0].event);
+ goto fail_destroy;
+ }
+
+ ssm_flow_set_destroy(set);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+fail_destroy:
+ ssm_flow_set_destroy(set);
+fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+int flow_set_test(int argc,
+ char ** argv)
+{
+ int ret = 0;
+
+ (void) argc;
+ (void) argv;
+
+ ret |= test_ssm_flow_set_create_destroy();
+ ret |= test_ssm_flow_set_add_del_has();
+ ret |= test_ssm_flow_set_zero();
+ ret |= test_ssm_flow_set_notify_wait();
+
+ return ret;
+}
diff --git a/src/lib/ssm/tests/pool_sharding_test.c b/src/lib/ssm/tests/pool_sharding_test.c
new file mode 100644
index 00000000..c53105e3
--- /dev/null
+++ b/src/lib/ssm/tests/pool_sharding_test.c
@@ -0,0 +1,501 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * Test of the SSM pool sharding with fallback
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#if defined(__linux__) || defined(__CYGWIN__)
+#define _DEFAULT_SOURCE
+#else
+#define _POSIX_C_SOURCE 200112L
+#endif
+
+#include "config.h"
+#include "ssm.h"
+
+#include <test/test.h>
+#include <ouroboros/ssm_pool.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+#include <signal.h>
+
+#define TEST_SIZE 256
+
+/* Helper to get pool header for inspection */
+static struct _ssm_pool_hdr * get_pool_hdr(struct ssm_pool * pool)
+{
+ /* ssm_pool is opaque, but we know its layout:
+ * uint8_t * shm_base
+ * struct _ssm_pool_hdr * hdr
+ * void * pool_base
+ */
+ struct _ssm_pool_hdr ** hdr_ptr =
+ (struct _ssm_pool_hdr **)((uint8_t *)pool + sizeof(void *));
+
+ return *hdr_ptr;
+}
+
+static int test_lazy_distribution(void)
+{
+ struct ssm_pool * pool;
+ struct _ssm_pool_hdr * hdr;
+ struct _ssm_size_class * sc;
+ int i;
+ int sc_idx;
+
+ TEST_START();
+
+ pool = ssm_pool_create(getuid(), getgid());
+ if (pool == NULL) {
+ printf("Failed to create pool.\n");
+ goto fail;
+ }
+
+ hdr = get_pool_hdr(pool);
+ if (hdr == NULL) {
+ printf("Failed to get pool header.\n");
+ goto fail_pool;
+ }
+
+ /* Find the first size class with blocks */
+ sc_idx = -1;
+ for (i = 0; i < SSM_POOL_MAX_CLASSES; i++) {
+ if (hdr->size_classes[i].object_count > 0) {
+ sc_idx = i;
+ break;
+ }
+ }
+
+ if (sc_idx < 0) {
+ printf("No size classes configured.\n");
+ for (i = 0; i < SSM_POOL_MAX_CLASSES; i++) {
+ printf(" Class %d: count=%zu\n", i,
+ hdr->size_classes[i].object_count);
+ }
+ goto fail_pool;
+ }
+
+ sc = &hdr->size_classes[sc_idx];
+
+ /* Verify all blocks start in shard 0 */
+ if (sc->shards[0].free_count == 0) {
+ printf("Shard 0 should have all blocks initially.\n");
+ goto fail_pool;
+ }
+
+ /* Verify other shards are empty */
+ for (i = 1; i < SSM_POOL_SHARDS; i++) {
+ if (sc->shards[i].free_count != 0) {
+ printf("Shard %d should be empty, has %zu.\n",
+ i, sc->shards[i].free_count);
+ goto fail_pool;
+ }
+ }
+
+ ssm_pool_destroy(pool);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_pool:
+ ssm_pool_destroy(pool);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_shard_migration(void)
+{
+ struct ssm_pool * pool;
+ struct _ssm_pool_hdr * hdr;
+ struct _ssm_size_class * sc;
+ struct ssm_pk_buff * spb;
+ uint8_t * ptr;
+ ssize_t off;
+ int shard_idx;
+ int sc_idx;
+ int i;
+
+ TEST_START();
+
+ pool = ssm_pool_create(getuid(), getgid());
+ if (pool == NULL) {
+ printf("Failed to create pool.\n");
+ goto fail;
+ }
+
+ hdr = get_pool_hdr(pool);
+
+ /* Find the first size class with blocks */
+ sc_idx = -1;
+ for (i = 0; i < SSM_POOL_MAX_CLASSES; i++) {
+ if (hdr->size_classes[i].object_count > 0) {
+ sc_idx = i;
+ break;
+ }
+ }
+
+ if (sc_idx < 0) {
+ printf("No size classes configured.\n");
+ goto fail;
+ }
+
+ sc = &hdr->size_classes[sc_idx];
+
+ /* Allocate from this process */
+ off = ssm_pool_alloc(pool, TEST_SIZE, &ptr, &spb);
+ if (off < 0) {
+ printf("Allocation failed: %zd.\n", off);
+ goto fail_pool;
+ }
+
+ /* Free it - should go to this process's shard */
+ shard_idx = getpid() % SSM_POOL_SHARDS;
+ if (ssm_pool_remove(pool, off) != 0) {
+ printf("Remove failed.\n");
+ goto fail_pool;
+ }
+
+ /* Verify block migrated away from shard 0 or in allocator's shard */
+ if (sc->shards[shard_idx].free_count == 0 &&
+ sc->shards[0].free_count == 0) {
+ printf("Block should have been freed to a shard.\n");
+ goto fail_pool;
+ }
+
+ ssm_pool_destroy(pool);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_pool:
+ ssm_pool_destroy(pool);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_fallback_stealing(void)
+{
+ struct ssm_pool * pool;
+ struct _ssm_pool_hdr * hdr;
+ struct _ssm_size_class * sc;
+ struct ssm_pk_buff ** spbs;
+ uint8_t ** ptrs;
+ size_t total_blocks;
+ size_t total_free;
+ size_t i;
+ int sc_idx;
+ int c;
+
+ TEST_START();
+
+ pool = ssm_pool_create(getuid(), getgid());
+ if (pool == NULL) {
+ printf("Failed to create pool.\n");
+ goto fail;
+ }
+
+ hdr = get_pool_hdr(pool);
+
+ /* Find the first size class with blocks */
+ sc_idx = -1;
+ for (c = 0; c < SSM_POOL_MAX_CLASSES; c++) {
+ if (hdr->size_classes[c].object_count > 0) {
+ sc_idx = c;
+ break;
+ }
+ }
+
+ if (sc_idx < 0) {
+ printf("No size classes configured.\n");
+ goto fail;
+ }
+
+ sc = &hdr->size_classes[sc_idx];
+ total_blocks = sc->object_count;
+
+ spbs = malloc(total_blocks * sizeof(struct ssm_pk_buff *));
+ ptrs = malloc(total_blocks * sizeof(uint8_t *));
+ if (spbs == NULL || ptrs == NULL) {
+ printf("Failed to allocate test arrays.\n");
+ free(spbs);
+ free(ptrs);
+ goto fail_pool;
+ }
+
+ /* Allocate half the blocks from single process */
+ for (i = 0; i < total_blocks / 2; i++) {
+ ssize_t off = ssm_pool_alloc(pool, TEST_SIZE,
+ &ptrs[i], &spbs[i]);
+ if (off < 0) {
+ printf("Allocation %zu failed: %zd.\n", i, off);
+ free(spbs);
+ free(ptrs);
+ goto fail_pool;
+ }
+ }
+
+ /* Free them all - they go to local_shard */
+ for (i = 0; i < total_blocks / 2; i++) {
+ size_t off = ssm_pk_buff_get_idx(spbs[i]);
+ if (ssm_pool_remove(pool, off) != 0) {
+ printf("Remove %zu failed.\n", i);
+ free(spbs);
+ free(ptrs);
+ goto fail_pool;
+ }
+ }
+
+ /* Freed blocks should be in shards (all blocks free again) */
+ total_free = 0;
+ for (i = 0; i < SSM_POOL_SHARDS; i++) {
+ total_free += sc->shards[i].free_count;
+ }
+
+ if (total_free != total_blocks) {
+ printf("Expected %zu free blocks total, got %zu.\n",
+ total_blocks, total_free);
+ free(spbs);
+ free(ptrs);
+ goto fail_pool;
+ }
+
+ /* Allocate again - should succeed by taking from shards */
+ for (i = 0; i < total_blocks / 2; i++) {
+ ssize_t off = ssm_pool_alloc(pool, TEST_SIZE,
+ &ptrs[i], &spbs[i]);
+ if (off < 0) {
+ printf("Fallback alloc %zu failed: %zd.\n", i, off);
+ free(spbs);
+ free(ptrs);
+ goto fail_pool;
+ }
+ }
+
+ /* Now all allocated blocks are in use again */
+ /* Cleanup - free all allocated blocks */
+ for (i = 0; i < total_blocks / 2; i++) {
+ size_t off = ssm_pk_buff_get_idx(spbs[i]);
+ ssm_pool_remove(pool, off);
+ }
+
+ free(spbs);
+ free(ptrs);
+ ssm_pool_destroy(pool);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_pool:
+ ssm_pool_destroy(pool);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_multiprocess_sharding(void)
+{
+ struct ssm_pool * pool;
+ struct _ssm_pool_hdr * hdr;
+ struct _ssm_size_class * sc;
+ pid_t children[SSM_POOL_SHARDS];
+ int i;
+ int status;
+
+ TEST_START();
+
+ for (i = 0; i < SSM_POOL_SHARDS; i++)
+ children[i] = -1;
+
+ pool = ssm_pool_create(getuid(), getgid());
+ if (pool == NULL) {
+ printf("Failed to create pool.\n");
+ goto fail;
+ }
+
+ /* Fork processes to test different shards */
+ for (i = 0; i < SSM_POOL_SHARDS; i++) {
+ children[i] = fork();
+ if (children[i] == -1) {
+ printf("Fork %d failed.\n", i);
+ goto fail_children;
+ }
+
+ if (children[i] == 0) {
+ /* Child process */
+ struct ssm_pool * child_pool;
+ struct ssm_pk_buff * spb;
+ uint8_t * ptr;
+ ssize_t off;
+ int my_shard;
+
+ child_pool = ssm_pool_open(getuid());
+ if (child_pool == NULL)
+ exit(EXIT_FAILURE);
+
+ my_shard = getpid() % SSM_POOL_SHARDS;
+ (void) my_shard; /* Reserved for future use */
+
+ /* Each child allocates and frees a block */
+ off = ssm_pool_alloc(child_pool, TEST_SIZE,
+ &ptr, &spb);
+ if (off < 0) {
+ ssm_pool_close(child_pool);
+ exit(EXIT_FAILURE);
+ }
+
+ /* Small delay to ensure allocation visible */
+ usleep(10000);
+
+ if (ssm_pool_remove(child_pool, off) != 0) {
+ ssm_pool_close(child_pool);
+ exit(EXIT_FAILURE);
+ }
+
+ ssm_pool_close(child_pool);
+ exit(EXIT_SUCCESS);
+ }
+ }
+
+ /* Wait for all children */
+ for (i = 0; i < SSM_POOL_SHARDS; i++) {
+ if (waitpid(children[i], &status, 0) == -1) {
+ printf("Waitpid %d failed.\n", i);
+ goto fail_children;
+ }
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+ printf("Child %d failed.\n", i);
+ goto fail_pool;
+ }
+ }
+
+ /* Verify blocks distributed across shards */
+ hdr = get_pool_hdr(pool);
+
+ /* Find the first size class with blocks */
+ sc = NULL;
+ for (i = 0; i < SSM_POOL_MAX_CLASSES; i++) {
+ if (hdr->size_classes[i].object_count > 0) {
+ sc = &hdr->size_classes[i];
+ break;
+ }
+ }
+
+ if (sc == NULL) {
+ printf("No size classes configured.\n");
+ goto fail_pool;
+ }
+
+ /* After children allocate and free, blocks should be in shards
+ * (though exact distribution depends on PID values)
+ */
+ for (i = 0; i < SSM_POOL_SHARDS; i++) {
+ /* At least some shards should have blocks */
+ if (sc->shards[i].free_count > 0) {
+ break;
+ }
+ }
+
+ ssm_pool_destroy(pool);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_children:
+ /* Kill any remaining children */
+ for (i = 0; i < SSM_POOL_SHARDS; i++) {
+ if (children[i] > 0)
+ kill(children[i], SIGKILL);
+ }
+ fail_pool:
+ ssm_pool_destroy(pool);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_exhaustion_with_fallback(void)
+{
+ struct ssm_pool * pool;
+ struct ssm_pk_buff * spb;
+ uint8_t * ptr;
+ ssize_t off;
+
+ TEST_START();
+
+ pool = ssm_pool_create(getuid(), getgid());
+ if (pool == NULL) {
+ printf("Failed to create pool.\n");
+ goto fail;
+ }
+
+ /* Allocate until exhausted across all shards */
+ while (true) {
+ off = ssm_pool_alloc(pool, TEST_SIZE, &ptr, &spb);
+ if (off < 0) {
+ if (off == -EAGAIN)
+ break;
+ printf("Unexpected error: %zd.\n", off);
+ goto fail_pool;
+ }
+ }
+
+ /* Should fail with -EAGAIN when truly exhausted */
+ off = ssm_pool_alloc(pool, TEST_SIZE, &ptr, &spb);
+ if (off != -EAGAIN) {
+ printf("Expected -EAGAIN, got %zd.\n", off);
+ goto fail_pool;
+ }
+
+ ssm_pool_destroy(pool);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_pool:
+ ssm_pool_destroy(pool);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+int pool_sharding_test(int argc,
+ char ** argv)
+{
+ int ret = 0;
+
+ (void) argc;
+ (void) argv;
+
+ ret |= test_lazy_distribution();
+ ret |= test_shard_migration();
+ ret |= test_fallback_stealing();
+ ret |= test_multiprocess_sharding();
+ ret |= test_exhaustion_with_fallback();
+
+ return ret;
+}
diff --git a/src/lib/ssm/tests/pool_test.c b/src/lib/ssm/tests/pool_test.c
new file mode 100644
index 00000000..3fc19cd5
--- /dev/null
+++ b/src/lib/ssm/tests/pool_test.c
@@ -0,0 +1,1060 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * Test of the Secure Shared Memory (SSM) system
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#define _POSIX_C_SOURCE 200809L
+
+#include "config.h"
+#include "ssm.h"
+
+#include <test/test.h>
+#include <ouroboros/ssm_pool.h>
+#include <ouroboros/ssm_rbuff.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <stdatomic.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <time.h>
+
+#define POOL_256 256
+#define POOL_512 512
+#define POOL_1K 1024
+#define POOL_2K 2048
+#define POOL_4K 4096
+#define POOL_16K 16384
+#define POOL_64K 65536
+#define POOL_256K 262144
+#define POOL_1M 1048576
+#define POOL_2M (2 * 1024 * 1024)
+
+static int test_ssm_pool_basic_allocation(void)
+{
+ struct ssm_pool * pool;
+ uint8_t * ptr;
+ struct ssm_pk_buff * spb;
+ ssize_t ret;
+
+ TEST_START();
+
+ pool = ssm_pool_create(getuid(), getgid());
+ if (pool == NULL) {
+ printf("Failed to create pool.\n");
+ goto fail_create;
+ }
+ ret = ssm_pool_alloc(pool, POOL_256, &ptr, &spb);
+ if (ret < 0) {
+ printf("Alloc failed: %zd.\n", ret);
+ goto fail_alloc;
+ }
+
+ if (spb == NULL) {
+ printf("Spb is NULL.\n");
+ goto fail_alloc;
+ }
+
+ if (ptr == NULL) {
+ printf("Ptr is NULL.\n");
+ goto fail_alloc;
+ }
+
+ if (ssm_pk_buff_len(spb) != POOL_256) {
+ printf("Bad length: %zu.\n", ssm_pk_buff_len(spb));
+ goto fail_alloc;
+ }
+
+ ret = ssm_pool_remove(pool, ret);
+ if (ret != 0) {
+ printf("Remove failed: %zd.\n", ret);
+ goto fail_alloc;
+ }
+
+ ssm_pool_destroy(pool);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_alloc:
+ ssm_pool_destroy(pool);
+ fail_create:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_ssm_pool_multiple_allocations(void)
+{
+ struct ssm_pool * pool;
+ uint8_t * ptr1;
+ uint8_t * ptr2;
+ uint8_t * ptr3;
+ struct ssm_pk_buff * spb1;
+ struct ssm_pk_buff * spb2;
+ struct ssm_pk_buff * spb3;
+ ssize_t ret1;
+ ssize_t ret2;
+ ssize_t ret3;
+
+ TEST_START();
+
+ pool = ssm_pool_create(getuid(), getgid());
+ if (pool == NULL) {
+ printf("Failed to create pool.\n");
+ goto fail_create;
+ }
+
+ ret1 = ssm_pool_alloc(pool, POOL_256, &ptr1, &spb1);
+ ret2 = ssm_pool_alloc(pool, POOL_256, &ptr2, &spb2);
+ ret3 = ssm_pool_alloc(pool, POOL_256, &ptr3, &spb3);
+ if (ret1 < 0 || ret2 < 0 || ret3 < 0) {
+ printf("Allocs failed: %zd, %zd, %zd.\n", ret1, ret2, ret3);
+ goto fail_alloc;
+ }
+
+ if (spb1 == NULL) {
+ printf("Spb1 is NULL.\n");
+ goto fail_alloc;
+ }
+
+ if (ptr1 == NULL) {
+ printf("Ptr1 is NULL.\n");
+ goto fail_alloc;
+ }
+
+ if (spb2 == NULL) {
+ printf("Spb2 is NULL.\n");
+ goto fail_alloc;
+ }
+
+ if (ptr2 == NULL) {
+ printf("Ptr2 is NULL.\n");
+ goto fail_alloc;
+ }
+
+ if (spb3 == NULL) {
+ printf("Spb3 is NULL.\n");
+ goto fail_alloc;
+ }
+
+ if (ptr3 == NULL) {
+ printf("Ptr3 is NULL.\n");
+ goto fail_alloc;
+ }
+
+ if (ssm_pk_buff_len(spb1) != POOL_256) {
+ printf("Bad length spb1: %zu.\n", ssm_pk_buff_len(spb1));
+ goto fail_alloc;
+ }
+
+ if (ssm_pk_buff_len(spb2) != POOL_256) {
+ printf("Bad length spb2: %zu.\n", ssm_pk_buff_len(spb2));
+ goto fail_alloc;
+ }
+
+ if (ssm_pk_buff_len(spb3) != POOL_256) {
+ printf("Bad length spb3: %zu.\n", ssm_pk_buff_len(spb3));
+ goto fail_alloc;
+ }
+
+ if (ssm_pool_remove(pool, ret2) != 0) {
+ printf("Remove ret2 failed.\n");
+ goto fail_alloc;
+ }
+
+ if (ssm_pool_remove(pool, ret1) != 0) {
+ printf("Remove ret1 failed.\n");
+ goto fail_alloc;
+ }
+
+ if (ssm_pool_remove(pool, ret3) != 0) {
+ printf("Remove ret3 failed.\n");
+ goto fail_alloc;
+ }
+
+ ssm_pool_destroy(pool);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_alloc:
+ ssm_pool_destroy(pool);
+ fail_create:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_ssm_pool_no_fallback_for_large(void)
+{
+ struct ssm_pool * pool;
+ uint8_t * ptr;
+ struct ssm_pk_buff * spb;
+ ssize_t ret;
+
+ TEST_START();
+
+ pool = ssm_pool_create(getuid(), getgid());
+ if (pool == NULL) {
+ printf("Failed to create pool.\n");
+ goto fail_create;
+ }
+
+ ret = ssm_pool_alloc(pool, POOL_2M, &ptr, &spb);
+ if (ret >= 0) {
+ printf("Oversized alloc succeeded: %zd.\n", ret);
+ goto fail_alloc;
+ }
+
+ if (ret != -EMSGSIZE) {
+ printf("Wrong error: %zd.\n", ret);
+ goto fail_alloc;
+ }
+
+ ssm_pool_destroy(pool);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_alloc:
+ ssm_pool_destroy(pool);
+ fail_create:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_ssm_pool_blocking_vs_nonblocking(void)
+{
+ struct ssm_pool * pool;
+ uint8_t * ptr;
+ struct ssm_pk_buff * spb;
+ ssize_t ret;
+
+ TEST_START();
+
+ pool = ssm_pool_create(getuid(), getgid());
+ if (pool == NULL) {
+ printf("Failed to create pool.\n");
+ goto fail_create;
+ }
+
+ ret = ssm_pool_alloc(pool, POOL_2M, &ptr, &spb);
+ if (ret != -EMSGSIZE) {
+ printf("Nonblocking oversized: %zd.\n", ret);
+ goto fail_alloc;
+ }
+
+ ret = ssm_pool_alloc_b(pool, POOL_2M, &ptr, &spb, NULL);
+ if (ret != -EMSGSIZE) {
+ printf("Blocking oversized: %zd.\n", ret);
+ goto fail_alloc;
+ }
+
+ ret = ssm_pool_alloc(pool, POOL_256, &ptr, &spb);
+ if (ret < 0) {
+ printf("Valid alloc failed: %zd.\n", ret);
+ goto fail_alloc;
+ }
+
+ ssm_pool_remove(pool, ret);
+ ssm_pool_destroy(pool);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_alloc:
+ ssm_pool_destroy(pool);
+ fail_create:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_ssm_pool_stress_test(void)
+{
+ struct ssm_pool * pool;
+ uint8_t * ptr;
+ struct ssm_pk_buff * spb;
+ ssize_t * indices = NULL;
+ ssize_t ret;
+ size_t count = 0;
+ size_t i;
+
+ TEST_START();
+
+ pool = ssm_pool_create(getuid(), getgid());
+ if (pool == NULL) {
+ printf("Failed to create pool.\n");
+ goto fail_create;
+ }
+
+ indices = malloc(100 * sizeof(*indices));
+ if (indices == NULL) {
+ printf("Malloc failed.\n");
+ goto fail_alloc;
+ }
+
+ for (i = 0; i < 50; i++) {
+ size_t j;
+ size_t num;
+ size_t size;
+
+ num = (i % 50) + 1;
+
+ for (j = 0; j < num && count < 50; j++) {
+ switch (i % 4) {
+ case 0:
+ /* FALLTHRU */
+ case 1:
+ size = POOL_256;
+ break;
+ case 2:
+ /* FALLTHRU */
+ case 3:
+ size = POOL_1K;
+ break;
+ default:
+ size = POOL_256;
+ break;
+ }
+
+ ret = ssm_pool_alloc(pool, size, &ptr, &spb);
+ if (ret < 0) {
+ printf("Alloc at iter %zu: %zd.\n", i, ret);
+ goto fail_test;
+ }
+ indices[count++] = ret;
+ }
+
+ for (j = 0; j < count / 2; j++) {
+ size_t idx = j * 2;
+ if (idx < count) {
+ ret = ssm_pool_remove(pool, indices[idx]);
+ if (ret != 0) {
+ printf("Remove at iter %zu: %zd.\n",
+ i, ret);
+ goto fail_test;
+ }
+ memmove(&indices[idx], &indices[idx + 1],
+ (count - idx - 1) * sizeof(*indices));
+ count--;
+ }
+ }
+
+ if (i % 10 == 0) {
+ ret = ssm_pool_alloc(pool, POOL_256, &ptr, &spb);
+ if (ret < 0) {
+ printf("Periodic alloc at %zu: %zd.\n", i, ret);
+ goto fail_test;
+ }
+ ssm_pool_remove(pool, ret);
+ }
+ }
+
+ for (i = 0; i < count; i++)
+ ssm_pool_remove(pool, indices[i]);
+
+ free(indices);
+ ssm_pool_destroy(pool);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_test:
+ for (i = 0; i < count; i++)
+ ssm_pool_remove(pool, indices[i]);
+ free(indices);
+ fail_alloc:
+ ssm_pool_destroy(pool);
+ fail_create:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_ssm_pool_open_initializes_ssm(void)
+{
+ struct ssm_pool * creator;
+ struct ssm_pool * opener;
+ uint8_t * ptr;
+ struct ssm_pk_buff * spb;
+ ssize_t ret;
+
+ TEST_START();
+
+ creator = ssm_pool_create(getuid(), getgid());
+ if (creator == NULL) {
+ printf("Failed to create pool.\n");
+ goto fail_create;
+ }
+
+ ret = ssm_pool_alloc(creator, POOL_256, &ptr, &spb);
+ if (ret < 0) {
+ printf("Creator alloc failed: %zd.\n", ret);
+ goto fail_creator;
+ }
+ ssm_pool_remove(creator, ret);
+
+ opener = ssm_pool_open(getuid());
+ if (opener == NULL) {
+ printf("Open failed.\n");
+ goto fail_creator;
+ }
+
+ ret = ssm_pool_alloc(opener, POOL_256, &ptr, &spb);
+ if (ret < 0) {
+ printf("Opener alloc failed: %zd.\n", ret);
+ goto fail_opener;
+ }
+
+ ssm_pool_remove(opener, ret);
+ ssm_pool_close(opener);
+ ssm_pool_destroy(creator);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_opener:
+ ssm_pool_close(opener);
+ fail_creator:
+ ssm_pool_destroy(creator);
+ fail_create:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_ssm_pool_bounds_checking(void)
+{
+ struct ssm_pool * pool;
+ struct ssm_pk_buff * spb;
+ ssize_t ret;
+
+ TEST_START();
+
+ pool = ssm_pool_create(getuid(), getgid());
+ if (pool == NULL) {
+ printf("Failed to create pool.\n");
+ goto fail_create;
+ }
+
+ ret = ssm_pool_alloc(pool, POOL_256, NULL, &spb);
+ if (ret < 0) {
+ printf("alloc failed: %zd.\n", ret);
+ goto fail_alloc;
+ }
+
+ spb = ssm_pool_get(pool, 0);
+ if (spb != NULL) {
+ printf("Get at offset 0.\n");
+ goto fail_alloc;
+ }
+
+ spb = ssm_pool_get(pool, 100000000UL);
+ if (spb != NULL) {
+ printf("Get beyond pool.\n");
+ goto fail_alloc;
+ }
+
+ ret = ssm_pool_remove(pool, 0);
+ if (ret != -EINVAL) {
+ printf("Remove at offset 0: %zd.\n", ret);
+ goto fail_alloc;
+ }
+
+ ret = ssm_pool_remove(pool, 100000000UL);
+ if (ret != -EINVAL) {
+ printf("Remove beyond pool: %zd.\n", ret);
+ goto fail_alloc;
+ }
+
+ ssm_pool_destroy(pool);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_alloc:
+ ssm_pool_destroy(pool);
+ fail_create:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_ssm_pool_inter_process_communication(void)
+{
+ struct ssm_pool * pool;
+ struct ssm_rbuff * rb;
+ struct ssm_pk_buff * spb;
+ uint8_t * ptr;
+ uint8_t * data;
+ const char * msg = "inter-process test";
+ size_t len;
+ ssize_t idx;
+ pid_t pid;
+ int status;
+
+ TEST_START();
+
+ len = strlen(msg) + 1;
+
+ pool = ssm_pool_create(getuid(), getgid());
+ if (pool == NULL) {
+ printf("Failed to create pool.\n");
+ goto fail_create;
+ }
+
+ rb = ssm_rbuff_create(getpid(), 1);
+ if (rb == NULL) {
+ printf("Rbuff create failed.\n");
+ goto fail_pool;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ printf("Fork failed.\n");
+ goto fail_rbuff;
+ }
+
+ if (pid == 0) {
+ idx = ssm_rbuff_read_b(rb, NULL);
+ if (idx < 0) {
+ printf("Child: rbuff read: %zd.\n", idx);
+ exit(1);
+ }
+
+ spb = ssm_pool_get(pool, idx);
+ if (spb == NULL) {
+ printf("Child: pool get failed.\n");
+ exit(1);
+ }
+
+ data = ssm_pk_buff_head(spb);
+ if (data == NULL) {
+ printf("Child: data is NULL.\n");
+ ssm_pool_remove(pool, idx);
+ exit(1);
+ }
+
+ if (strcmp((char *)data, msg) != 0) {
+ printf("Child: data mismatch.\n");
+ ssm_pool_remove(pool, idx);
+ exit(1);
+ }
+
+ ssm_pool_remove(pool, idx);
+ exit(0);
+ }
+
+ idx = ssm_pool_alloc(pool, len, &ptr, &spb);
+ if (idx < 0) {
+ printf("Parent: pool alloc: %zd.\n", idx);
+ goto fail_child;
+ }
+
+ memcpy(ptr, msg, len);
+
+ if (ssm_rbuff_write(rb, idx) < 0) {
+ printf("Parent: rbuff write failed.\n");
+ ssm_pool_remove(pool, idx);
+ goto fail_child;
+ }
+
+ if (waitpid(pid, &status, 0) < 0) {
+ printf("Parent: waitpid failed.\n");
+ ssm_pool_remove(pool, idx);
+ goto fail_rbuff;
+ }
+
+ if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
+ printf("Child failed.\n");
+ ssm_pool_remove(pool, idx);
+ goto fail_rbuff;
+ }
+
+ ssm_rbuff_destroy(rb);
+ ssm_pool_destroy(pool);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_child:
+ waitpid(pid, &status, 0);
+ fail_rbuff:
+ ssm_rbuff_destroy(rb);
+ fail_pool:
+ ssm_pool_destroy(pool);
+ fail_create:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_ssm_pool_read_operation(void)
+{
+ struct ssm_pool * pool;
+ struct ssm_pk_buff * spb;
+ uint8_t * wptr;
+ uint8_t * rptr;
+ const char * data = "ssm_pool_read test";
+ size_t len;
+ ssize_t idx;
+ ssize_t ret;
+
+ TEST_START();
+
+ len = strlen(data) + 1;
+
+ pool = ssm_pool_create(getuid(), getgid());
+ if (pool == NULL) {
+ printf("Failed to create pool.\n");
+ goto fail_create;
+ }
+
+ idx = ssm_pool_alloc(pool, len, &wptr, &spb);
+ if (idx < 0) {
+ printf("alloc failed: %zd.\n", idx);
+ goto fail_alloc;
+ }
+
+ memcpy(wptr, data, len);
+
+ ret = ssm_pool_read(&rptr, pool, idx);
+ if (ret < 0) {
+ printf("Read failed: %zd.\n", ret);
+ goto fail_read;
+ }
+
+ if (rptr == NULL) {
+ printf("NULL pointer.\n");
+ goto fail_read;
+ }
+
+ if (strcmp((char *)rptr, data) != 0) {
+ printf("Data mismatch.\n");
+ goto fail_read;
+ }
+
+ ssm_pool_remove(pool, idx);
+ ssm_pool_destroy(pool);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_read:
+ ssm_pool_remove(pool, idx);
+ fail_alloc:
+ ssm_pool_destroy(pool);
+ fail_create:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_ssm_pool_mlock_operation(void)
+{
+ struct ssm_pool * pool;
+ int ret;
+
+ TEST_START();
+
+ pool = ssm_pool_create(getuid(), getgid());
+ if (pool == NULL) {
+ printf("Failed to create pool.\n");
+ goto fail_create;
+ }
+
+ ret = ssm_pool_mlock(pool);
+ if (ret < 0)
+ printf("Mlock failed: %d (may need privileges).\n", ret);
+
+ ssm_pool_destroy(pool);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_create:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_ssm_pk_buff_operations(void)
+{
+ struct ssm_pool * pool;
+ struct ssm_pk_buff * spb;
+ uint8_t * ptr;
+ uint8_t * head;
+ uint8_t * tail;
+ const char * data = "packet buffer test";
+ size_t dlen;
+ size_t len;
+ ssize_t idx;
+
+ TEST_START();
+
+ dlen = strlen(data);
+
+ pool = ssm_pool_create(getuid(), getgid());
+ if (pool == NULL) {
+ printf("Failed to create pool.\n");
+ goto fail_create;
+ }
+
+ idx = ssm_pool_alloc(pool, POOL_256, &ptr, &spb);
+ if (idx < 0) {
+ printf("alloc failed: %zd.\n", idx);
+ goto fail_alloc;
+ }
+
+ head = ssm_pk_buff_head(spb);
+ if (head != ptr) {
+ printf("Head mismatch.\n");
+ goto fail_ops;
+ }
+
+ len = ssm_pk_buff_len(spb);
+ if (len != POOL_256) {
+ printf("Bad length: %zu.\n", len);
+ goto fail_ops;
+ }
+
+ tail = ssm_pk_buff_tail(spb);
+ if (tail != ptr + len) {
+ printf("Tail mismatch.\n");
+ goto fail_ops;
+ }
+
+ memcpy(head, data, dlen);
+
+ tail = ssm_pk_buff_tail_alloc(spb, 32);
+ if (tail == NULL) {
+ printf("Tail_alloc failed.\n");
+ goto fail_ops;
+ }
+
+ if (ssm_pk_buff_len(spb) != POOL_256 + 32) {
+ printf("Length after tail_alloc: %zu.\n",
+ ssm_pk_buff_len(spb));
+ goto fail_ops;
+ }
+
+ if (memcmp(head, data, dlen) != 0) {
+ printf("Data corrupted.\n");
+ goto fail_ops;
+ }
+
+ tail = ssm_pk_buff_tail_release(spb, 32);
+ if (tail == NULL) {
+ printf("Tail_release failed.\n");
+ goto fail_ops;
+ }
+
+ if (ssm_pk_buff_len(spb) != POOL_256) {
+ printf("Length after tail_release: %zu.\n",
+ ssm_pk_buff_len(spb));
+ goto fail_ops;
+ }
+
+ ssm_pool_remove(pool, idx);
+ ssm_pool_destroy(pool);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_ops:
+ ssm_pool_remove(pool, idx);
+ fail_alloc:
+ ssm_pool_destroy(pool);
+ fail_create:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+#define OVERHEAD (offsetof(struct ssm_pk_buff, data) + \
+ SSM_PK_BUFF_HEADSPACE + SSM_PK_BUFF_TAILSPACE)
+static int test_ssm_pool_size_class_boundaries(void)
+{
+ struct ssm_pool * pool;
+ struct ssm_pk_buff * spb;
+ uint8_t * ptr;
+ size_t sizes[] = {
+ POOL_512 - OVERHEAD,
+ POOL_512 - OVERHEAD + 1,
+ POOL_1K - OVERHEAD,
+ POOL_1K - OVERHEAD + 1,
+ POOL_2K - OVERHEAD,
+ POOL_2K - OVERHEAD + 1,
+ POOL_4K - OVERHEAD,
+ POOL_4K - OVERHEAD + 1,
+ POOL_16K - OVERHEAD,
+ POOL_16K - OVERHEAD + 1,
+ POOL_64K - OVERHEAD,
+ POOL_64K - OVERHEAD + 1,
+ POOL_256K - OVERHEAD,
+ };
+ size_t expected_classes[] = {
+ 512, 1024, 1024, 2048, 2048, 4096, 4096, 16384,
+ 16384, 65536, 65536, 262144, 262144
+ };
+ size_t i;
+ ssize_t idx;
+
+ TEST_START();
+
+ pool = ssm_pool_create(getuid(), getgid());
+ if (pool == NULL) {
+ printf("Failed to create pool.\n");
+ goto fail_create;
+ }
+
+ for (i = 0; i < sizeof(sizes) / sizeof(sizes[0]); i++) {
+ struct ssm_pk_buff * hdr;
+ size_t actual_class;
+
+ idx = ssm_pool_alloc(pool, sizes[i], &ptr, &spb);
+ if (idx < 0) {
+ printf("Alloc at %zu failed: %zd.\n", sizes[i], idx);
+ goto fail_alloc;
+ }
+
+ if (ssm_pk_buff_len(spb) != sizes[i]) {
+ printf("Length mismatch at %zu: %zu.\n",
+ sizes[i], ssm_pk_buff_len(spb));
+ ssm_pool_remove(pool, idx);
+ goto fail_alloc;
+ }
+
+ /* Verify correct size class was used
+ * hdr->size is the data array size (object_size - header) */
+ hdr = spb;
+ actual_class = hdr->size + offsetof(struct ssm_pk_buff, data);
+ if (actual_class != expected_classes[i]) {
+ printf("Wrong class for len=%zu: want %zu, got %zu.\n",
+ sizes[i], expected_classes[i], actual_class);
+ ssm_pool_remove(pool, idx);
+ goto fail_alloc;
+ }
+
+ memset(ptr, i & 0xFF, sizes[i]);
+
+ ssm_pool_remove(pool, idx);
+ }
+
+ ssm_pool_destroy(pool);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_alloc:
+ ssm_pool_destroy(pool);
+ fail_create:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_ssm_pool_exhaustion(void)
+{
+ struct ssm_pool * pool;
+ struct ssm_pk_buff * spb;
+ uint8_t * ptr;
+ ssize_t * indices;
+ size_t count = 0;
+ size_t i;
+ ssize_t ret;
+
+ TEST_START();
+
+ pool = ssm_pool_create(getuid(), getgid());
+ if (pool == NULL) {
+ printf("Failed to create pool.\n");
+ goto fail_create;
+ }
+
+ indices = malloc(2048 * sizeof(*indices));
+ if (indices == NULL) {
+ printf("Malloc failed.\n");
+ goto fail_alloc;
+ }
+
+ for (i = 0; i < 2048; i++) {
+ ret = ssm_pool_alloc(pool, POOL_256, &ptr, &spb);
+ if (ret < 0) {
+ if (ret == -EAGAIN)
+ break;
+ printf("Alloc error: %zd.\n", ret);
+ goto fail_test;
+ }
+ indices[count++] = ret;
+ }
+
+ if (count == 0) {
+ printf("No allocs succeeded.\n");
+ goto fail_test;
+ }
+
+ ret = ssm_pool_alloc(pool, POOL_256, &ptr, &spb);
+ if (ret >= 0) {
+ ssm_pool_remove(pool, ret);
+ } else if (ret != -EAGAIN) {
+ printf("Unexpected error: %zd.\n", ret);
+ goto fail_test;
+ }
+
+ for (i = 0; i < count; i++)
+ ssm_pool_remove(pool, indices[i]);
+
+ ret = ssm_pool_alloc(pool, POOL_256, &ptr, &spb);
+ if (ret < 0) {
+ printf("Alloc after free failed: %zd.\n", ret);
+ goto fail_test;
+ }
+ ssm_pool_remove(pool, ret);
+
+ free(indices);
+ ssm_pool_destroy(pool);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_test:
+ for (i = 0; i < count; i++)
+ ssm_pool_remove(pool, indices[i]);
+ free(indices);
+ fail_alloc:
+ ssm_pool_destroy(pool);
+ fail_create:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_ssm_pool_reclaim_orphans(void)
+{
+ struct ssm_pool * pool;
+ uint8_t * ptr1;
+ uint8_t * ptr2;
+ uint8_t * ptr3;
+ struct ssm_pk_buff * spb1;
+ struct ssm_pk_buff * spb2;
+ struct ssm_pk_buff * spb3;
+ ssize_t ret1;
+ ssize_t ret2;
+ ssize_t ret3;
+ pid_t my_pid;
+ pid_t fake_pid = 99999;
+
+ TEST_START();
+
+ pool = ssm_pool_create(getuid(), getgid());
+ if (pool == NULL) {
+ printf("Failed to create pool.\n");
+ goto fail_create;
+ }
+
+ my_pid = getpid();
+
+ /* Allocate some blocks */
+ ret1 = ssm_pool_alloc(pool, POOL_256, &ptr1, &spb1);
+ ret2 = ssm_pool_alloc(pool, POOL_512, &ptr2, &spb2);
+ ret3 = ssm_pool_alloc(pool, POOL_1K, &ptr3, &spb3);
+ if (ret1 < 0 || ret2 < 0 || ret3 < 0) {
+ printf("Allocs failed: %zd, %zd, %zd.\n", ret1, ret2, ret3);
+ goto fail_alloc;
+ }
+
+ /* Simulate blocks from another process by changing allocator_pid */
+ spb1->allocator_pid = fake_pid;
+ spb2->allocator_pid = fake_pid;
+ /* Keep spb3 with our pid */
+
+ /* Reclaim orphans from fake_pid */
+ ssm_pool_reclaim_orphans(pool, fake_pid);
+
+ /* Verify spb1 and spb2 have refcount 0 (reclaimed) */
+ if (spb1->refcount != 0) {
+ printf("spb1 refcount should be 0, got %u.\n", spb1->refcount);
+ goto fail_test;
+ }
+
+ if (spb2->refcount != 0) {
+ printf("spb2 refcount should be 0, got %u.\n", spb2->refcount);
+ goto fail_test;
+ }
+
+ /* Verify spb3 still has refcount 1 (not reclaimed) */
+ if (spb3->refcount != 1) {
+ printf("spb3 refcount should be 1, got %u.\n", spb3->refcount);
+ goto fail_test;
+ }
+
+ /* Clean up */
+ ssm_pool_remove(pool, ret3);
+
+ /* Try allocating again - should get blocks from reclaimed pool */
+ ret1 = ssm_pool_alloc(pool, POOL_256, &ptr1, &spb1);
+ if (ret1 < 0) {
+ printf("Alloc after reclaim failed: %zd.\n", ret1);
+ goto fail_test;
+ }
+
+ /* Verify new allocation has our pid */
+ if (spb1->allocator_pid != my_pid) {
+ printf("New block has wrong pid: %d vs %d.\n",
+ spb1->allocator_pid, my_pid);
+ goto fail_test;
+ }
+
+ ssm_pool_remove(pool, ret1);
+ ssm_pool_destroy(pool);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_test:
+ ssm_pool_remove(pool, ret3);
+ fail_alloc:
+ ssm_pool_destroy(pool);
+ fail_create:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+int pool_test(int argc,
+ char ** argv)
+{
+ int ret = 0;
+
+ (void) argc;
+ (void) argv;
+
+ ret |= test_ssm_pool_basic_allocation();
+ ret |= test_ssm_pool_multiple_allocations();
+ ret |= test_ssm_pool_no_fallback_for_large();
+ ret |= test_ssm_pool_blocking_vs_nonblocking();
+ ret |= test_ssm_pool_stress_test();
+ ret |= test_ssm_pool_open_initializes_ssm();
+ ret |= test_ssm_pool_bounds_checking();
+ ret |= test_ssm_pool_inter_process_communication();
+ ret |= test_ssm_pool_read_operation();
+ ret |= test_ssm_pool_mlock_operation();
+ ret |= test_ssm_pk_buff_operations();
+ ret |= test_ssm_pool_size_class_boundaries();
+ ret |= test_ssm_pool_exhaustion();
+ ret |= test_ssm_pool_reclaim_orphans();
+
+ return ret;
+}
diff --git a/src/lib/ssm/tests/rbuff_test.c b/src/lib/ssm/tests/rbuff_test.c
new file mode 100644
index 00000000..58cb39c3
--- /dev/null
+++ b/src/lib/ssm/tests/rbuff_test.c
@@ -0,0 +1,675 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * Test of the SSM notification ring buffer
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#if defined(__linux__) || defined(__CYGWIN__)
+#define _DEFAULT_SOURCE
+#else
+#define _POSIX_C_SOURCE 200112L
+#endif
+
+#include "config.h"
+#include "ssm.h"
+
+#include <test/test.h>
+#include <ouroboros/ssm_rbuff.h>
+#include <ouroboros/errno.h>
+#include <ouroboros/time.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <pthread.h>
+
+static int test_ssm_rbuff_create_destroy(void)
+{
+ struct ssm_rbuff * rb;
+
+ TEST_START();
+
+ rb = ssm_rbuff_create(getpid(), 1);
+ if (rb == NULL) {
+ printf("Failed to create rbuff.\n");
+ goto fail;
+ }
+
+ ssm_rbuff_destroy(rb);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_ssm_rbuff_write_read(void)
+{
+ struct ssm_rbuff * rb;
+ ssize_t idx;
+
+ TEST_START();
+
+ rb = ssm_rbuff_create(getpid(), 2);
+ if (rb == NULL) {
+ printf("Failed to create rbuff.\n");
+ goto fail;
+ }
+
+ if (ssm_rbuff_write(rb, 42) < 0) {
+ printf("Failed to write value.\n");
+ goto fail_rb;
+ }
+
+ if (ssm_rbuff_queued(rb) != 1) {
+ printf("Queue length should be 1, got %zu.\n",
+ ssm_rbuff_queued(rb));
+ goto fail_rb;
+ }
+
+ idx = ssm_rbuff_read(rb);
+ if (idx != 42) {
+ printf("Expected 42, got %zd.\n", idx);
+ goto fail_rb;
+ }
+
+ if (ssm_rbuff_queued(rb) != 0) {
+ printf("Queue should be empty, got %zu.\n",
+ ssm_rbuff_queued(rb));
+ goto fail_rb;
+ }
+
+ ssm_rbuff_destroy(rb);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_rb:
+ ssm_rbuff_destroy(rb);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_ssm_rbuff_read_empty(void)
+{
+ struct ssm_rbuff * rb;
+ ssize_t ret;
+
+ TEST_START();
+
+ rb = ssm_rbuff_create(getpid(), 3);
+ if (rb == NULL) {
+ printf("Failed to create rbuff.\n");
+ goto fail;
+ }
+
+ ret = ssm_rbuff_read(rb);
+ if (ret != -EAGAIN) {
+ printf("Expected -EAGAIN, got %zd.\n", ret);
+ goto fail_rb;
+ }
+
+ ssm_rbuff_destroy(rb);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_rb:
+ ssm_rbuff_destroy(rb);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_ssm_rbuff_fill_drain(void)
+{
+ struct ssm_rbuff * rb;
+ size_t i;
+ ssize_t ret;
+
+ TEST_START();
+
+ rb = ssm_rbuff_create(getpid(), 4);
+ if (rb == NULL) {
+ printf("Failed to create rbuff.\n");
+ goto fail;
+ }
+
+ for (i = 0; i < SSM_RBUFF_SIZE - 1; ++i) {
+ if (ssm_rbuff_queued(rb) != i) {
+ printf("Expected %zu queued, got %zu.\n",
+ i, ssm_rbuff_queued(rb));
+ goto fail_rb;
+ }
+ if (ssm_rbuff_write(rb, i) < 0) {
+ printf("Failed to write at index %zu.\n", i);
+ goto fail_rb;
+ }
+ }
+
+ if (ssm_rbuff_queued(rb) != SSM_RBUFF_SIZE - 1) {
+ printf("Expected %d queued, got %zu.\n",
+ SSM_RBUFF_SIZE - 1, ssm_rbuff_queued(rb));
+ goto fail_rb;
+ }
+
+ ret = ssm_rbuff_write(rb, 999);
+ if (ret != -EAGAIN) {
+ printf("Expected -EAGAIN on full buffer, got %zd.\n", ret);
+ goto fail_rb;
+ }
+
+ for (i = 0; i < SSM_RBUFF_SIZE - 1; ++i) {
+ ret = ssm_rbuff_read(rb);
+ if (ret != (ssize_t) i) {
+ printf("Expected %zu, got %zd.\n", i, ret);
+ goto fail_rb;
+ }
+ }
+
+ if (ssm_rbuff_queued(rb) != 0) {
+ printf("Expected empty queue, got %zu.\n",
+ ssm_rbuff_queued(rb));
+ goto fail_rb;
+ }
+
+ ssm_rbuff_destroy(rb);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_rb:
+ while (ssm_rbuff_read(rb) >= 0)
+ ;
+ ssm_rbuff_destroy(rb);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_ssm_rbuff_acl(void)
+{
+ struct ssm_rbuff * rb;
+ uint32_t acl;
+
+ TEST_START();
+
+ rb = ssm_rbuff_create(getpid(), 5);
+ if (rb == NULL) {
+ printf("Failed to create rbuff.\n");
+ goto fail;
+ }
+
+ acl = ssm_rbuff_get_acl(rb);
+ if (acl != ACL_RDWR) {
+ printf("Expected ACL_RDWR, got %u.\n", acl);
+ goto fail_rb;
+ }
+
+ ssm_rbuff_set_acl(rb, ACL_RDONLY);
+ acl = ssm_rbuff_get_acl(rb);
+ if (acl != ACL_RDONLY) {
+ printf("Expected ACL_RDONLY, got %u.\n", acl);
+ goto fail_rb;
+ }
+
+ if (ssm_rbuff_write(rb, 1) != -ENOTALLOC) {
+ printf("Expected -ENOTALLOC on RDONLY.\n");
+ goto fail_rb;
+ }
+
+ ssm_rbuff_set_acl(rb, ACL_FLOWDOWN);
+ if (ssm_rbuff_write(rb, 1) != -EFLOWDOWN) {
+ printf("Expected -EFLOWDOWN on FLOWDOWN.\n");
+ goto fail_rb;
+ }
+
+ if (ssm_rbuff_read(rb) != -EFLOWDOWN) {
+ printf("Expected -EFLOWDOWN on read with FLOWDOWN.\n");
+ goto fail_rb;
+ }
+
+ ssm_rbuff_destroy(rb);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_rb:
+ ssm_rbuff_destroy(rb);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_ssm_rbuff_open_close(void)
+{
+ struct ssm_rbuff * rb1;
+ struct ssm_rbuff * rb2;
+ pid_t pid;
+
+ TEST_START();
+
+ pid = getpid();
+
+ rb1 = ssm_rbuff_create(pid, 6);
+ if (rb1 == NULL) {
+ printf("Failed to create rbuff.\n");
+ goto fail;
+ }
+
+ if (ssm_rbuff_write(rb1, 123) < 0) {
+ printf("Failed to write value.\n");
+ goto fail_rb1;
+ }
+
+ rb2 = ssm_rbuff_open(pid, 6);
+ if (rb2 == NULL) {
+ printf("Failed to open existing rbuff.\n");
+ goto fail_rb1;
+ }
+
+ if (ssm_rbuff_queued(rb2) != 1) {
+ printf("Expected 1 queued in opened rbuff, got %zu.\n",
+ ssm_rbuff_queued(rb2));
+ goto fail_rb2;
+ }
+
+ if (ssm_rbuff_read(rb2) != 123) {
+ printf("Failed to read from opened rbuff.\n");
+ goto fail_rb2;
+ }
+
+ ssm_rbuff_close(rb2);
+ ssm_rbuff_destroy(rb1);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_rb2:
+ ssm_rbuff_close(rb2);
+ fail_rb1:
+ ssm_rbuff_destroy(rb1);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+struct thread_args {
+ struct ssm_rbuff * rb;
+ int iterations;
+ int delay_us;
+};
+
+static void * writer_thread(void * arg)
+{
+ struct thread_args * args = (struct thread_args *) arg;
+ struct timespec delay = {0, 0};
+ int i;
+
+ delay.tv_nsec = args->delay_us * 1000L;
+
+ for (i = 0; i < args->iterations; ++i) {
+ while (ssm_rbuff_write(args->rb, i) < 0)
+ nanosleep(&delay, NULL);
+ }
+
+ return NULL;
+}
+
+static void * reader_thread(void * arg)
+{
+ struct thread_args * args = (struct thread_args *) arg;
+ struct timespec delay = {0, 0};
+ int i;
+ ssize_t val;
+
+ delay.tv_nsec = args->delay_us * 1000L;
+
+ for (i = 0; i < args->iterations; ++i) {
+ val = ssm_rbuff_read(args->rb);
+ while (val < 0) {
+ nanosleep(&delay, NULL);
+ val = ssm_rbuff_read(args->rb);
+ }
+ if (val != i) {
+ printf("Expected %d, got %zd.\n", i, val);
+ return (void *) -1;
+ }
+ }
+
+ return NULL;
+}
+
+static void * blocking_writer_thread(void * arg)
+{
+ struct thread_args * args = (struct thread_args *) arg;
+ int i;
+
+ for (i = 0; i < args->iterations; ++i) {
+ if (ssm_rbuff_write_b(args->rb, i, NULL) < 0)
+ return (void *) -1;
+ }
+
+ return NULL;
+}
+
+static void * blocking_reader_thread(void * arg)
+{
+ struct thread_args * args = (struct thread_args *) arg;
+ int i;
+ ssize_t val;
+
+ for (i = 0; i < args->iterations; ++i) {
+ val = ssm_rbuff_read_b(args->rb, NULL);
+ if (val < 0 || val != i) {
+ printf("Expected %d, got %zd.\n", i, val);
+ return (void *) -1;
+ }
+ }
+
+ return NULL;
+}
+
+static int test_ssm_rbuff_blocking(void)
+{
+ struct ssm_rbuff * rb;
+ pthread_t wthread;
+ pthread_t rthread;
+ struct thread_args args;
+ struct timespec delay = {0, 10 * MILLION};
+ void * ret_w;
+ void * ret_r;
+
+ TEST_START();
+
+ rb = ssm_rbuff_create(getpid(), 8);
+ if (rb == NULL) {
+ printf("Failed to create rbuff.\n");
+ goto fail;
+ }
+
+ args.rb = rb;
+ args.iterations = 50;
+ args.delay_us = 0;
+
+ if (pthread_create(&rthread, NULL, blocking_reader_thread, &args)) {
+ printf("Failed to create reader thread.\n");
+ goto fail_rthread;
+ }
+
+ nanosleep(&delay, NULL);
+
+ if (pthread_create(&wthread, NULL, blocking_writer_thread, &args)) {
+ printf("Failed to create writer thread.\n");
+ pthread_cancel(rthread);
+ goto fail_wthread;
+ }
+
+ pthread_join(wthread, &ret_w);
+ pthread_join(rthread, &ret_r);
+
+ if (ret_w != NULL || ret_r != NULL) {
+ printf("Thread returned error.\n");
+ goto fail_ret;
+ }
+
+ ssm_rbuff_destroy(rb);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_ret:
+ fail_wthread:
+ pthread_join(rthread, NULL);
+ fail_rthread:
+ ssm_rbuff_destroy(rb);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_ssm_rbuff_blocking_timeout(void)
+{
+ struct ssm_rbuff * rb;
+ struct timespec abs_timeout;
+ struct timespec interval = {0, 100 * MILLION};
+ struct timespec start;
+ struct timespec end;
+ ssize_t ret;
+ long elapsed_ms;
+ size_t i;
+
+ TEST_START();
+
+ rb = ssm_rbuff_create(getpid(), 9);
+ if (rb == NULL) {
+ printf("Failed to create rbuff.\n");
+ goto fail;
+ }
+
+ clock_gettime(PTHREAD_COND_CLOCK, &start);
+ ts_add(&start, &interval, &abs_timeout);
+
+ ret = ssm_rbuff_read_b(rb, &abs_timeout);
+
+ clock_gettime(PTHREAD_COND_CLOCK, &end);
+
+ if (ret != -ETIMEDOUT) {
+ printf("Expected -ETIMEDOUT, got %zd.\n", ret);
+ goto fail_rb;
+ }
+
+ elapsed_ms = (end.tv_sec - start.tv_sec) * 1000L +
+ (end.tv_nsec - start.tv_nsec) / 1000000L;
+
+ if (elapsed_ms < 90 || elapsed_ms > 200) {
+ printf("Timeout took %ld ms, expected ~100 ms.\n",
+ elapsed_ms);
+ goto fail_rb;
+ }
+
+ for (i = 0; i < SSM_RBUFF_SIZE - 1; ++i) {
+ if (ssm_rbuff_write(rb, i) < 0) {
+ printf("Failed to fill buffer.\n");
+ goto fail_rb;
+ }
+ }
+
+ clock_gettime(PTHREAD_COND_CLOCK, &start);
+ ts_add(&start, &interval, &abs_timeout);
+
+ ret = ssm_rbuff_write_b(rb, 999, &abs_timeout);
+
+ clock_gettime(PTHREAD_COND_CLOCK, &end);
+
+ if (ret != -ETIMEDOUT) {
+ printf("Expected -ETIMEDOUT on full buffer, got %zd.\n",
+ ret);
+ goto fail_rb;
+ }
+
+ elapsed_ms = (end.tv_sec - start.tv_sec) * 1000L +
+ (end.tv_nsec - start.tv_nsec) / 1000000L;
+
+ if (elapsed_ms < 90 || elapsed_ms > 200) {
+ printf("Write timeout took %ld ms, expected ~100 ms.\n",
+ elapsed_ms);
+ goto fail_rb;
+ }
+
+ while (ssm_rbuff_read(rb) >= 0)
+ ;
+
+ ssm_rbuff_destroy(rb);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_rb:
+ while (ssm_rbuff_read(rb) >= 0)
+ ;
+ ssm_rbuff_destroy(rb);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_ssm_rbuff_blocking_flowdown(void)
+{
+ struct ssm_rbuff * rb;
+ struct timespec abs_timeout;
+ struct timespec now;
+ struct timespec interval = {5, 0};
+ ssize_t ret;
+ size_t i;
+
+ TEST_START();
+
+ rb = ssm_rbuff_create(getpid(), 10);
+ if (rb == NULL) {
+ printf("Failed to create rbuff.\n");
+ goto fail;
+ }
+
+ clock_gettime(PTHREAD_COND_CLOCK, &now);
+ ts_add(&now, &interval, &abs_timeout);
+
+ ssm_rbuff_set_acl(rb, ACL_FLOWDOWN);
+
+ ret = ssm_rbuff_read_b(rb, &abs_timeout);
+ if (ret != -EFLOWDOWN) {
+ printf("Expected -EFLOWDOWN, got %zd.\n", ret);
+ goto fail_rb;
+ }
+
+ ssm_rbuff_set_acl(rb, ACL_RDWR);
+
+ for (i = 0; i < SSM_RBUFF_SIZE - 1; ++i) {
+ if (ssm_rbuff_write(rb, i) < 0) {
+ printf("Failed to fill buffer.\n");
+ goto fail_rb;
+ }
+ }
+
+ clock_gettime(PTHREAD_COND_CLOCK, &now);
+ ts_add(&now, &interval, &abs_timeout);
+
+ ssm_rbuff_set_acl(rb, ACL_FLOWDOWN);
+
+ ret = ssm_rbuff_write_b(rb, 999, &abs_timeout);
+ if (ret != -EFLOWDOWN) {
+ printf("Expected -EFLOWDOWN on write, got %zd.\n", ret);
+ goto fail_rb;
+ }
+
+ ssm_rbuff_set_acl(rb, ACL_RDWR);
+ while (ssm_rbuff_read(rb) >= 0)
+ ;
+
+ ssm_rbuff_destroy(rb);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_rb:
+ while (ssm_rbuff_read(rb) >= 0)
+ ;
+ ssm_rbuff_destroy(rb);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_ssm_rbuff_threaded(void)
+{
+ struct ssm_rbuff * rb;
+ pthread_t wthread;
+ pthread_t rthread;
+ struct thread_args args;
+ void * ret_w;
+ void * ret_r;
+
+ TEST_START();
+
+ rb = ssm_rbuff_create(getpid(), 7);
+ if (rb == NULL) {
+ printf("Failed to create rbuff.\n");
+ goto fail;
+ }
+
+ args.rb = rb;
+ args.iterations = 100;
+ args.delay_us = 100;
+
+ if (pthread_create(&wthread, NULL, writer_thread, &args)) {
+ printf("Failed to create writer thread.\n");
+ goto fail_rb;
+ }
+
+ if (pthread_create(&rthread, NULL, reader_thread, &args)) {
+ printf("Failed to create reader thread.\n");
+ pthread_cancel(wthread);
+ pthread_join(wthread, NULL);
+ goto fail_rb;
+ }
+
+ pthread_join(wthread, &ret_w);
+ pthread_join(rthread, &ret_r);
+
+ if (ret_w != NULL || ret_r != NULL) {
+ printf("Thread returned error.\n");
+ goto fail_rb;
+ }
+
+ ssm_rbuff_destroy(rb);
+
+ TEST_SUCCESS();
+ return TEST_RC_SUCCESS;
+
+ fail_rb:
+ ssm_rbuff_destroy(rb);
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+int rbuff_test(int argc,
+ char ** argv)
+{
+ int ret = 0;
+
+ (void) argc;
+ (void) argv;
+
+ ret |= test_ssm_rbuff_create_destroy();
+ ret |= test_ssm_rbuff_write_read();
+ ret |= test_ssm_rbuff_read_empty();
+ ret |= test_ssm_rbuff_fill_drain();
+ ret |= test_ssm_rbuff_acl();
+ ret |= test_ssm_rbuff_open_close();
+ ret |= test_ssm_rbuff_threaded();
+ ret |= test_ssm_rbuff_blocking();
+ ret |= test_ssm_rbuff_blocking_timeout();
+ ret |= test_ssm_rbuff_blocking_flowdown();
+
+ return ret;
+}
diff --git a/src/lib/tests/CMakeLists.txt b/src/lib/tests/CMakeLists.txt
index c795c1ac..5a2f2c52 100644
--- a/src/lib/tests/CMakeLists.txt
+++ b/src/lib/tests/CMakeLists.txt
@@ -1,17 +1,22 @@
get_filename_component(PARENT_PATH ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY)
get_filename_component(PARENT_DIR ${PARENT_PATH} NAME)
+compute_test_prefix()
+
create_test_sourcelist(${PARENT_DIR}_tests test_suite.c
# Add new tests here
auth_test.c
+ auth_test_ml_dsa.c
+ auth_test_slh_dsa.c
bitmap_test.c
btree_test.c
crc32_test.c
crypt_test.c
hash_test.c
+ kex_test.c
+ kex_test_ml_kem.c
md5_test.c
sha3_test.c
- shm_rbuff_test.c
sockets_test.c
time_test.c
tpm_test.c
@@ -19,21 +24,9 @@ create_test_sourcelist(${PARENT_DIR}_tests test_suite.c
add_executable(${PARENT_DIR}_test ${${PARENT_DIR}_tests})
+disable_test_logging_for_target(${PARENT_DIR}_test)
target_link_libraries(${PARENT_DIR}_test ouroboros-common)
-add_dependencies(check ${PARENT_DIR}_test)
-
-set(tests_to_run ${${PARENT_DIR}_tests})
-if(CMAKE_VERSION VERSION_LESS "3.29.0")
- remove(tests_to_run test_suite.c)
-else ()
- list(POP_FRONT tests_to_run)
-endif()
-
-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)
+add_dependencies(build_tests ${PARENT_DIR}_test)
-set_property(TEST auth_test PROPERTY SKIP_RETURN_CODE 1)
-set_property(TEST crypt_test PROPERTY SKIP_RETURN_CODE 1)
+ouroboros_register_tests(TARGET ${PARENT_DIR}_test TESTS ${${PARENT_DIR}_tests})
diff --git a/src/lib/tests/auth_test.c b/src/lib/tests/auth_test.c
index ede294b8..1a5a87af 100644
--- a/src/lib/tests/auth_test.c
+++ b/src/lib/tests/auth_test.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Test of the authentication functions
*
@@ -22,110 +22,14 @@
#include "config.h"
-#include <ouroboros/test.h>
+#include <test/test.h>
#include <ouroboros/crypt.h>
#include <ouroboros/random.h>
#include <ouroboros/utils.h>
-#define TEST_MSG_SIZE 1500
+#include <test/certs/ecdsa.h>
-/*
-* Certificates created following the guide
-* Building an openssl certificate authority
-* on
-* https://community.f5.com/kb/technicalarticles/
-*/
-
-/* Root certificate for CA ca.unittest.o7s */
-static const char * root_ca_crt = \
-"-----BEGIN CERTIFICATE-----\n"
-"MIICXTCCAgOgAwIBAgIURlENlCOy1OsA/AXFscPUQ2li8OYwCgYIKoZIzj0EAwIw\n"
-"fDELMAkGA1UEBhMCQkUxDDAKBgNVBAgMA09WTDEOMAwGA1UEBwwFR2hlbnQxDDAK\n"
-"BgNVBAoMA283czEVMBMGA1UECwwMdW5pdHRlc3QubzdzMRgwFgYDVQQDDA9jYS51\n"
-"bml0dGVzdC5vN3MxEDAOBgkqhkiG9w0BCQEWASAwHhcNMjUwODAzMTg1MzE1WhcN\n"
-"NDUwNzI5MTg1MzE1WjB8MQswCQYDVQQGEwJCRTEMMAoGA1UECAwDT1ZMMQ4wDAYD\n"
-"VQQHDAVHaGVudDEMMAoGA1UECgwDbzdzMRUwEwYDVQQLDAx1bml0dGVzdC5vN3Mx\n"
-"GDAWBgNVBAMMD2NhLnVuaXR0ZXN0Lm83czEQMA4GCSqGSIb3DQEJARYBIDBZMBMG\n"
-"ByqGSM49AgEGCCqGSM49AwEHA0IABEPMseCScbd/d5TlHmyYVszn/YGVeNdUCnFR\n"
-"naOr95WlTNo3MyKKBuoiEFwHhjPASgXr/VDVjJLSyM3JUPebAcGjYzBhMB0GA1Ud\n"
-"DgQWBBQkxjMILHH6lZ+rnCMnD/63GO3y1zAfBgNVHSMEGDAWgBQkxjMILHH6lZ+r\n"
-"nCMnD/63GO3y1zAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAKBggq\n"
-"hkjOPQQDAgNIADBFAiEA1jVJWW4idkCgAYv0m2LT9C33Dq42aLyRkJ+9YdzDqLwC\n"
-"IHT6MS4I0k52YP/hxoqWVBbpOW79PKYMRLyXTk1r7+Fa\n"
-"-----END CERTIFICATE-----\n";
-
-
-/* Certificate for intermediary im.unittest.o7s used for signing */
-static const char * intermediate_ca_crt = \
-"-----BEGIN CERTIFICATE-----\n"
-"MIICbTCCAhOgAwIBAgICEAMwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMCQkUxDDAK\n"
-"BgNVBAgMA09WTDEOMAwGA1UEBwwFR2hlbnQxDDAKBgNVBAoMA283czEVMBMGA1UE\n"
-"CwwMdW5pdHRlc3QubzdzMRgwFgYDVQQDDA9jYS51bml0dGVzdC5vN3MxEDAOBgkq\n"
-"hkiG9w0BCQEWASAwHhcNMjUwODAzMTkwMjU3WhcNNDUwNzI5MTkwMjU3WjBaMQsw\n"
-"CQYDVQQGEwJCRTEMMAoGA1UECAwDT1ZMMQwwCgYDVQQKDANvN3MxFTATBgNVBAsM\n"
-"DHVuaXR0ZXN0Lm83czEYMBYGA1UEAwwPaW0udW5pdHRlc3QubzdzMFkwEwYHKoZI\n"
-"zj0CAQYIKoZIzj0DAQcDQgAEdlra08XItIPtVl5veaq4UF6LIcBXj2mZFqKNEXFh\n"
-"l9uAz6UAbIc+FUPNfom6dwKbg/AjQ82a100eh6K/jCY7eKOBpjCBozAdBgNVHQ4E\n"
-"FgQUy8Go8BIO6i0lJ+mgBr9lvh2L0eswHwYDVR0jBBgwFoAUJMYzCCxx+pWfq5wj\n"
-"Jw/+txjt8tcwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwEQYD\n"
-"VR0fBAowCDAGoASgAoYAMCoGCCsGAQUFBwEBBB4wHDAMBggrBgEFBQcwAoYAMAwG\n"
-"CCsGAQUFBzABhgAwCgYIKoZIzj0EAwIDSAAwRQIhAN3ZYhqu6mVLGidmONsbANk5\n"
-"rzT6aHJcmvj19OxMusaXAiBKy0gBFCri/GLizi4wZo09wf31yZMqfr8IrApvPaLw\n"
-"qA==\n"
-"-----END CERTIFICATE-----\n";
-
-/* Server test-1.unittest.o7s private-public key pair */
-static const char * server_ec_pkp = \
-"-----BEGIN EC PRIVATE KEY-----\n"
-"MHcCAQEEIA4/bcmquVvGrY4+TtfnFSy1SpXs896r5xJjGuD6NmGRoAoGCCqGSM49\n"
-"AwEHoUQDQgAE4BSOhv36q4bCMLSkJaCvzwZ3pPy2M0YzRKFKeV48tG5eD+MBaTrT\n"
-"eoHUcRfpz0EO/inq3FVDzEoAQ2NWpnz0kA==\n"
-"-----END EC PRIVATE KEY-----\n";
-
-/* Public key for the Private key */
-static const char * server_ec_pk = \
-"-----BEGIN PUBLIC KEY-----\n"
-"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE4BSOhv36q4bCMLSkJaCvzwZ3pPy2\n"
-"M0YzRKFKeV48tG5eD+MBaTrTeoHUcRfpz0EO/inq3FVDzEoAQ2NWpnz0kA==\n"
-"-----END PUBLIC KEY-----\n";
-
-/* Valid signed server certificate for test-1.unittest.o7s */
-static const char * signed_server_crt = \
-"-----BEGIN CERTIFICATE-----\n"
-"MIIDiTCCAy+gAwIBAgICEAUwCgYIKoZIzj0EAwIwWjELMAkGA1UEBhMCQkUxDDAK\n"
-"BgNVBAgMA09WTDEMMAoGA1UECgwDbzdzMRUwEwYDVQQLDAx1bml0dGVzdC5vN3Mx\n"
-"GDAWBgNVBAMMD2ltLnVuaXR0ZXN0Lm83czAeFw0yNTA4MDgxODQ4NTNaFw00NTA4\n"
-"MDMxODQ4NTNaMG4xCzAJBgNVBAYTAkJFMQwwCgYDVQQIDANPVkwxDjAMBgNVBAcM\n"
-"BUdoZW50MQwwCgYDVQQKDANvN3MxFTATBgNVBAsMDHVuaXR0ZXN0Lm83czEcMBoG\n"
-"A1UEAwwTdGVzdC0xLnVuaXR0ZXN0Lm83czBZMBMGByqGSM49AgEGCCqGSM49AwEH\n"
-"A0IABOAUjob9+quGwjC0pCWgr88Gd6T8tjNGM0ShSnlePLRuXg/jAWk603qB1HEX\n"
-"6c9BDv4p6txVQ8xKAENjVqZ89JCjggHPMIIByzAJBgNVHRMEAjAAMBEGCWCGSAGG\n"
-"+EIBAQQEAwIGQDA4BglghkgBhvhCAQ0EKxYpbzdzIHVuaXR0ZXN0IEdlbmVyYXRl\n"
-"ZCBTZXJ2ZXIgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFI+htsK0xxy6e1CqCyxn7mqi\n"
-"wRrpMIGoBgNVHSMEgaAwgZ2AFMvBqPASDuotJSfpoAa/Zb4di9HroYGApH4wfDEL\n"
-"MAkGA1UEBhMCQkUxDDAKBgNVBAgMA09WTDEOMAwGA1UEBwwFR2hlbnQxDDAKBgNV\n"
-"BAoMA283czEVMBMGA1UECwwMdW5pdHRlc3QubzdzMRgwFgYDVQQDDA9jYS51bml0\n"
-"dGVzdC5vN3MxEDAOBgkqhkiG9w0BCQEWASCCAhADMA4GA1UdDwEB/wQEAwIFoDAT\n"
-"BgNVHSUEDDAKBggrBgEFBQcDATAoBgNVHR8EITAfMB2gG6AZhhdodHRwczovL291\n"
-"cm9ib3Jvcy5yb2NrczBYBggrBgEFBQcBAQRMMEowIwYIKwYBBQUHMAKGF2h0dHBz\n"
-"Oi8vb3Vyb2Jvcm9zLnJvY2tzMCMGCCsGAQUFBzABhhdodHRwczovL291cm9ib3Jv\n"
-"cy5yb2NrczAKBggqhkjOPQQDAgNIADBFAiBZuw/Yb2pq925H7pEiOXr4fMo0wknz\n"
-"ktkxoHAFbjQEPQIhAMInHI7lvRmS0IMw1wBF/WlUZWKvhyU/TeMIZfk/JGCS\n"
-"-----END CERTIFICATE-----\n";
-
-/* Self-signed by server test-1.unittest.o7s using its key */
-static const char * server_crt = \
-"-----BEGIN CERTIFICATE-----\n"
-"MIIBfjCCASWgAwIBAgIUB5VYxp7i+sgYjvLiwfpf0W5NfqQwCgYIKoZIzj0EAwIw\n"
-"HjEcMBoGA1UEAwwTdGVzdC0xLnVuaXR0ZXN0Lm83czAeFw0yNTA4MDMxOTI4MzVa\n"
-"Fw00NTA3MjkxOTI4MzVaMB4xHDAaBgNVBAMME3Rlc3QtMS51bml0dGVzdC5vN3Mw\n"
-"WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATgFI6G/fqrhsIwtKQloK/PBnek/LYz\n"
-"RjNEoUp5Xjy0bl4P4wFpOtN6gdRxF+nPQQ7+KercVUPMSgBDY1amfPSQo0EwPzAe\n"
-"BgNVHREEFzAVghN0ZXN0LTEudW5pdHRlc3QubzdzMB0GA1UdDgQWBBSPobbCtMcc\n"
-"untQqgssZ+5qosEa6TAKBggqhkjOPQQDAgNHADBEAiAoFC/rqgrRXmMUx4y5cPbv\n"
-"jOKpoL3FpehRgGkPatmL/QIgMRHc2TSGo6q1SG22Xt1dHAIBsaN2AlSfhjKULMH5\n"
-"gRo=\n"
-"-----END CERTIFICATE-----\n";
+#define TEST_MSG_SIZE 1500
static int test_auth_create_destroy_ctx(void)
{
@@ -155,7 +59,7 @@ static int test_load_free_crt(void)
TEST_START();
- if (crypt_load_crt_str(root_ca_crt, &crt) < 0) {
+ if (crypt_load_crt_str(root_ca_crt_ec, &crt) < 0) {
printf("Failed to load certificate string.\n");
goto fail_load;
}
@@ -177,7 +81,7 @@ static int test_crypt_get_pubkey_crt(void)
TEST_START();
- if (crypt_load_crt_str(signed_server_crt, &crt) < 0) {
+ if (crypt_load_crt_str(signed_server_crt_ec, &crt) < 0) {
printf("Failed to load server certificate from string.\n");
goto fail_load;
}
@@ -207,7 +111,7 @@ static int test_check_crt_name(void)
TEST_START();
- if (crypt_load_crt_str(signed_server_crt, &crt) < 0) {
+ if (crypt_load_crt_str(signed_server_crt_ec, &crt) < 0) {
printf("Failed to load certificate from string.\n");
goto fail_load;
}
@@ -240,7 +144,7 @@ static int test_load_free_privkey(void)
TEST_START();
- if (crypt_load_privkey_str(server_ec_pkp, &key) < 0) {
+ if (crypt_load_privkey_str(server_pkp_ec, &key) < 0) {
printf("Failed to load server key pair from string.\n");
goto fail_load;
}
@@ -261,7 +165,7 @@ static int test_load_free_pubkey(void)
TEST_START();
- if (crypt_load_pubkey_str(server_ec_pk, &key) < 0) {
+ if (crypt_load_pubkey_str(server_pk_ec, &key) < 0) {
printf("Failed to load server public key from string.\n");
goto fail_load;
}
@@ -284,12 +188,12 @@ static int test_crypt_check_pubkey_crt(void)
TEST_START();
- if (crypt_load_crt_str(signed_server_crt, &crt) < 0) {
+ if (crypt_load_crt_str(signed_server_crt_ec, &crt) < 0) {
printf("Failed to load public certificate from string.\n");
goto fail_crt;
}
- if (crypt_load_pubkey_str(server_ec_pk, &pk) < 0) {
+ if (crypt_load_pubkey_str(server_pk_ec, &pk) < 0) {
printf("Failed to load public key from string.\n");
goto fail_pubkey;
}
@@ -336,7 +240,7 @@ static int test_store_add(void)
goto fail_create;
}
- if (crypt_load_crt_str(root_ca_crt, &_root_ca_crt) < 0) {
+ if (crypt_load_crt_str(root_ca_crt_ec, &_root_ca_crt) < 0) {
printf("Failed to load root crt from string.\n");
goto fail_load;
}
@@ -368,7 +272,7 @@ static int test_verify_crt(void)
void * _server_crt;
void * _signed_server_crt;
void * _root_ca_crt;
- void * _intermediate_ca_crt;
+ void * _im_ca_crt;
TEST_START();
@@ -378,24 +282,24 @@ static int test_verify_crt(void)
goto fail_create_ctx;
}
- if (crypt_load_crt_str(server_crt, &_server_crt) < 0) {
+ if (crypt_load_crt_str(server_crt_ec, &_server_crt) < 0) {
printf("Failed to load self-signed crt from string.\n");
goto fail_load_server_crt;
}
- if (crypt_load_crt_str(signed_server_crt, &_signed_server_crt) < 0) {
+ if (crypt_load_crt_str(signed_server_crt_ec, &_signed_server_crt) < 0) {
printf("Failed to load signed crt from string.\n");
goto fail_load_signed_server_crt;
}
- if (crypt_load_crt_str(root_ca_crt, &_root_ca_crt) < 0) {
+ if (crypt_load_crt_str(root_ca_crt_ec, &_root_ca_crt) < 0) {
printf("Failed to load root crt from string.\n");
goto fail_load_root_ca_crt;
}
- if (crypt_load_crt_str(intermediate_ca_crt, &_intermediate_ca_crt) < 0) {
+ if (crypt_load_crt_str(im_ca_crt_ec, &_im_ca_crt) < 0) {
printf("Failed to load intermediate crt from string.\n");
- goto fail_load_intermediate_ca_crt;
+ goto fail_load_im_ca_crt;
}
if (auth_add_crt_to_store(auth, _root_ca_crt) < 0) {
@@ -403,7 +307,7 @@ static int test_verify_crt(void)
goto fail_verify;
}
- if (auth_add_crt_to_store(auth, _intermediate_ca_crt) < 0) {
+ if (auth_add_crt_to_store(auth, _im_ca_crt) < 0) {
printf("Failed to add intermediate ca crt to auth store.\n");
goto fail_verify;
}
@@ -418,7 +322,7 @@ static int test_verify_crt(void)
goto fail_verify;
}
- crypt_free_crt(_intermediate_ca_crt);
+ crypt_free_crt(_im_ca_crt);
crypt_free_crt(_root_ca_crt);
crypt_free_crt(_signed_server_crt);
crypt_free_crt(_server_crt);
@@ -429,8 +333,8 @@ static int test_verify_crt(void)
return TEST_RC_SUCCESS;
fail_verify:
- crypt_free_crt(_intermediate_ca_crt);
- fail_load_intermediate_ca_crt:
+ crypt_free_crt(_im_ca_crt);
+ fail_load_im_ca_crt:
crypt_free_crt(_root_ca_crt);
fail_load_root_ca_crt:
crypt_free_crt(_signed_server_crt);
@@ -461,22 +365,22 @@ int test_auth_sign(void)
goto fail_init;
}
- if (crypt_load_privkey_str(server_ec_pkp, &pkp) < 0) {
+ if (crypt_load_privkey_str(server_pkp_ec, &pkp) < 0) {
printf("Failed to load server key pair from string.\n");
goto fail_init;
}
- if (crypt_load_pubkey_str(server_ec_pk, &pk) < 0) {
+ if (crypt_load_pubkey_str(server_pk_ec, &pk) < 0) {
printf("Failed to load public key.\n");
goto fail_pubkey;
}
- if (auth_sign(pkp, msg, &sig) < 0) {
+ if (auth_sign(pkp, 0, msg, &sig) < 0) {
printf("Failed to sign message.\n");
goto fail_sign;
}
- if (auth_verify_sig(pk, msg, sig) < 0) {
+ if (auth_verify_sig(pk, 0, msg, sig) < 0) {
printf("Failed to verify signature.\n");
goto fail_verify;
}
@@ -518,17 +422,17 @@ int test_auth_bad_signature(void)
goto fail_init;
}
- if (crypt_load_privkey_str(server_ec_pkp, &pkp) < 0) {
+ if (crypt_load_privkey_str(server_pkp_ec, &pkp) < 0) {
printf("Failed to load server key pair from string.\n");
goto fail_init;
}
- if (crypt_load_pubkey_str(server_ec_pk, &pk) < 0) {
+ if (crypt_load_pubkey_str(server_pk_ec, &pk) < 0) {
printf("Failed to load public key.\n");
goto fail_pubkey;
}
- if (auth_sign(pkp, msg, &sig) < 0) {
+ if (auth_sign(pkp, 0, msg, &sig) < 0) {
printf("Failed to sign message.\n");
goto fail_sign;
}
@@ -545,7 +449,7 @@ int test_auth_bad_signature(void)
goto fail_malloc;
}
- if (auth_verify_sig(pk, msg, fake_sig) == 0) {
+ if (auth_verify_sig(pk, 0, msg, fake_sig) == 0) {
printf("Failed to detect bad signature.\n");
goto fail_verify;
}
@@ -571,14 +475,15 @@ int test_auth_bad_signature(void)
return TEST_RC_FAIL;
}
+#define SSC_BUF_SIZE 4096 /* OpenSSL version my return different lengths */
int test_crt_str(void)
{
- char str[2295];
+ char str[SSC_BUF_SIZE];
void * crt;
TEST_START();
- if (crypt_load_crt_str(signed_server_crt, &crt) < 0) {
+ if (crypt_load_crt_str(signed_server_crt_ec, &crt) < 0) {
printf("Failed to load certificate from string.\n");
goto fail_load;
}
diff --git a/src/lib/tests/auth_test_ml_dsa.c b/src/lib/tests/auth_test_ml_dsa.c
new file mode 100644
index 00000000..cc72e61b
--- /dev/null
+++ b/src/lib/tests/auth_test_ml_dsa.c
@@ -0,0 +1,356 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * Test of the ML-DSA-65 authentication functions
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#include "config.h"
+
+#include <test/test.h>
+#include <ouroboros/crypt.h>
+#include <ouroboros/random.h>
+#include <ouroboros/utils.h>
+
+#include <test/certs/ml_dsa.h>
+
+#define TEST_MSG_SIZE 1500
+
+static int test_auth_create_destroy_ctx(void)
+{
+ struct auth_ctx * ctx;
+
+ TEST_START();
+
+ ctx = auth_create_ctx();
+ if (ctx == NULL) {
+ printf("Failed to create auth context.\n");
+ goto fail_create;
+ }
+
+ auth_destroy_ctx(ctx);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail_create:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_load_free_crt(void)
+{
+ void * crt;
+
+ TEST_START();
+
+ if (crypt_load_crt_str(root_ca_crt_ml, &crt) < 0) {
+ printf("Failed to load root crt from string.\n");
+ goto fail_load;
+ }
+
+ crypt_free_crt(crt);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail_load:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_load_free_privkey(void)
+{
+ void * key;
+
+ TEST_START();
+
+ if (crypt_load_privkey_str(server_pkp_ml, &key) < 0) {
+ printf("Failed to load server key pair from string.\n");
+ goto fail_load;
+ }
+
+ crypt_free_key(key);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail_load:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_load_free_pubkey(void)
+{
+ void * key;
+
+ TEST_START();
+
+ if (crypt_load_pubkey_str(server_pk_ml, &key) < 0) {
+ printf("Failed to load server public key from string.\n");
+ goto fail_load;
+ }
+
+ crypt_free_key(key);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail_load:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_verify_crt(void)
+{
+ struct auth_ctx * auth;
+ void * _server_crt;
+ void * _signed_server_crt;
+ void * _root_ca_crt;
+ void * _im_ca_crt;
+
+ TEST_START();
+
+ auth = auth_create_ctx();
+ if (auth == NULL) {
+ printf("Failed to create auth context.\n");
+ goto fail_create_ctx;
+ }
+
+ if (crypt_load_crt_str(server_crt_ml, &_server_crt) < 0) {
+ printf("Failed to load self-signed crt from string.\n");
+ goto fail_load_server_crt;
+ }
+
+ if (crypt_load_crt_str(signed_server_crt_ml, &_signed_server_crt) < 0) {
+ printf("Failed to load signed crt from string.\n");
+ goto fail_load_signed_server_crt;
+ }
+
+ if (crypt_load_crt_str(root_ca_crt_ml, &_root_ca_crt) < 0) {
+ printf("Failed to load root crt from string.\n");
+ goto fail_load_root_ca_crt;
+ }
+
+ if (crypt_load_crt_str(im_ca_crt_ml, &_im_ca_crt) < 0) {
+ printf("Failed to load intermediate crt from string.\n");
+ goto fail_load_im_ca_crt;
+ }
+
+ if (auth_add_crt_to_store(auth, _root_ca_crt) < 0) {
+ printf("Failed to add root ca crt to auth store.\n");
+ goto fail_verify;
+ }
+
+ if (auth_add_crt_to_store(auth, _im_ca_crt) < 0) {
+ printf("Failed to add intermediate ca crt to auth store.\n");
+ goto fail_verify;
+ }
+
+ if (auth_verify_crt(auth, _signed_server_crt) < 0) {
+ printf("Failed to verify signed crt with ca crt.\n");
+ goto fail_verify;
+ }
+
+ if (auth_verify_crt(auth, _server_crt) == 0) {
+ printf("Failed to detect untrusted crt.\n");
+ goto fail_verify;
+ }
+
+ crypt_free_crt(_im_ca_crt);
+ crypt_free_crt(_root_ca_crt);
+ crypt_free_crt(_signed_server_crt);
+ crypt_free_crt(_server_crt);
+
+ auth_destroy_ctx(auth);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail_verify:
+ crypt_free_crt(_im_ca_crt);
+ fail_load_im_ca_crt:
+ crypt_free_crt(_root_ca_crt);
+ fail_load_root_ca_crt:
+ crypt_free_crt(_signed_server_crt);
+ fail_load_signed_server_crt:
+ crypt_free_crt(_server_crt);
+ fail_load_server_crt:
+ auth_destroy_ctx(auth);
+ fail_create_ctx:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_auth_sign(void)
+{
+ uint8_t buf[TEST_MSG_SIZE];
+ void * pkp;
+ void * pk;
+ buffer_t msg;
+ buffer_t sig;
+
+ TEST_START();
+
+ msg.data = buf;
+ msg.len = sizeof(buf);
+
+ if (random_buffer(msg.data, msg.len) < 0) {
+ printf("Failed to generate random message.\n");
+ goto fail_init;
+ }
+
+ if (crypt_load_privkey_str(server_pkp_ml, &pkp) < 0) {
+ printf("Failed to load server key pair from string.\n");
+ goto fail_init;
+ }
+
+ if (crypt_load_pubkey_str(server_pk_ml, &pk) < 0) {
+ printf("Failed to load public key from string.\n");
+ goto fail_pubkey;
+ }
+
+ if (auth_sign(pkp, 0, msg, &sig) < 0) {
+ printf("Failed to sign message.\n");
+ goto fail_sign;
+ }
+
+ if (auth_verify_sig(pk, 0, msg, sig) < 0) {
+ printf("Failed to verify signature.\n");
+ goto fail_verify;
+ }
+
+ freebuf(sig);
+
+ crypt_free_key(pk);
+ crypt_free_key(pkp);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail_verify:
+ freebuf(sig);
+ fail_sign:
+ crypt_free_key(pk);
+ fail_pubkey:
+ crypt_free_key(pkp);
+ fail_init:
+ return TEST_RC_FAIL;
+}
+
+static int test_auth_bad_signature(void)
+{
+ uint8_t buf[TEST_MSG_SIZE];
+ void * pkp;
+ void * pk;
+ buffer_t msg;
+ buffer_t sig;
+ buffer_t fake_sig;
+
+ TEST_START();
+
+ msg.data = buf;
+ msg.len = sizeof(buf);
+
+ if (random_buffer(msg.data, msg.len) < 0) {
+ printf("Failed to generate random message.\n");
+ goto fail_init;
+ }
+
+ if (crypt_load_privkey_str(server_pkp_ml, &pkp) < 0) {
+ printf("Failed to load server key pair from string.\n");
+ goto fail_init;
+ }
+
+ if (crypt_load_pubkey_str(server_pk_ml, &pk) < 0) {
+ printf("Failed to load public key from string.\n");
+ goto fail_pubkey;
+ }
+
+ if (auth_sign(pkp, 0, msg, &sig) < 0) {
+ printf("Failed to sign message.\n");
+ goto fail_sign;
+ }
+
+ fake_sig.data = malloc(sig.len);
+ if (fake_sig.data == NULL) {
+ printf("Failed to allocate memory for fake signature.\n");
+ goto fail_malloc;
+ }
+
+ fake_sig.len = sig.len;
+ if (random_buffer(fake_sig.data, fake_sig.len) < 0) {
+ printf("Failed to generate random fake signature.\n");
+ goto fail_malloc;
+ }
+
+ if (auth_verify_sig(pk, 0, msg, fake_sig) == 0) {
+ printf("Failed to detect bad ML-DSA-65 signature.\n");
+ goto fail_verify;
+ }
+
+ freebuf(fake_sig);
+ freebuf(sig);
+
+ crypt_free_key(pk);
+ crypt_free_key(pkp);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail_verify:
+ freebuf(fake_sig);
+ fail_malloc:
+ freebuf(sig);
+ fail_sign:
+ crypt_free_key(pk);
+ fail_pubkey:
+ crypt_free_key(pkp);
+ fail_init:
+ return TEST_RC_FAIL;
+}
+
+int auth_test_ml_dsa(int argc,
+ char ** argv)
+{
+ int ret = 0;
+
+ (void) argc;
+ (void) argv;
+
+#ifdef HAVE_OPENSSL_ML_DSA
+ ret |= test_auth_create_destroy_ctx();
+ ret |= test_load_free_crt();
+ ret |= test_load_free_privkey();
+ ret |= test_load_free_pubkey();
+ ret |= test_verify_crt();
+ ret |= test_auth_sign();
+ ret |= test_auth_bad_signature();
+#else
+ (void) test_auth_create_destroy_ctx;
+ (void) test_load_free_crt;
+ (void) test_load_free_privkey;
+ (void) test_load_free_pubkey;
+ (void) test_verify_crt;
+ (void) test_auth_sign;
+ (void) test_auth_bad_signature;
+
+ ret = TEST_RC_SKIP;
+#endif
+ return ret;
+}
diff --git a/src/lib/tests/auth_test_slh_dsa.c b/src/lib/tests/auth_test_slh_dsa.c
new file mode 100644
index 00000000..511d20fe
--- /dev/null
+++ b/src/lib/tests/auth_test_slh_dsa.c
@@ -0,0 +1,367 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * Test of the SLH-DSA-SHA2-128s authentication functions
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#include "config.h"
+
+#include <test/test.h>
+#include <ouroboros/crypt.h>
+#include <ouroboros/random.h>
+#include <ouroboros/utils.h>
+
+#include <test/certs/slh_dsa.h>
+
+#define TEST_MSG_SIZE 1500
+
+static int test_auth_create_destroy_ctx(void)
+{
+ struct auth_ctx * ctx;
+
+ TEST_START();
+
+ ctx = auth_create_ctx();
+ if (ctx == NULL) {
+ printf("Failed to create auth context.\n");
+ goto fail_create;
+ }
+
+ auth_destroy_ctx(ctx);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail_create:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_load_free_crt(void)
+{
+ void * crt;
+
+ TEST_START();
+
+ if (crypt_load_crt_str(root_ca_crt_slh, &crt) < 0) {
+ printf("Failed to load root crt.\n");
+ goto fail_load;
+ }
+
+ crypt_free_crt(crt);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail_load:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_load_free_privkey(void)
+{
+ void * key;
+
+ TEST_START();
+
+ if (crypt_load_privkey_str(server_pkp_slh, &key) < 0) {
+ printf("Failed to load server key pair.\n");
+ goto fail_load;
+ }
+
+ crypt_free_key(key);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail_load:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_load_free_pubkey(void)
+{
+ void * key;
+
+ TEST_START();
+
+ if (crypt_load_pubkey_str(server_pk_slh, &key) < 0) {
+ printf("Failed to load server public key.\n");
+ goto fail_load;
+ }
+
+ crypt_free_key(key);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail_load:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_verify_crt(void)
+{
+ struct auth_ctx * auth;
+ void * _server_crt;
+ void * _signed_server_crt;
+ void * _root_ca_crt;
+ void * _im_ca_crt;
+
+ TEST_START();
+
+ auth = auth_create_ctx();
+ if (auth == NULL) {
+ printf("Failed to create auth context.\n");
+ goto fail_create_ctx;
+ }
+
+ if (crypt_load_crt_str(server_crt_slh,
+ &_server_crt) < 0) {
+ printf("Failed to load self-signed crt.\n");
+ goto fail_load_server_crt;
+ }
+
+ if (crypt_load_crt_str(signed_server_crt_slh,
+ &_signed_server_crt) < 0) {
+ printf("Failed to load signed crt.\n");
+ goto fail_load_signed_server_crt;
+ }
+
+ if (crypt_load_crt_str(root_ca_crt_slh,
+ &_root_ca_crt) < 0) {
+ printf("Failed to load root crt.\n");
+ goto fail_load_root_ca_crt;
+ }
+
+ if (crypt_load_crt_str(im_ca_crt_slh,
+ &_im_ca_crt) < 0) {
+ printf("Failed to load im crt.\n");
+ goto fail_load_im_ca_crt;
+ }
+
+ if (auth_add_crt_to_store(auth, _root_ca_crt) < 0) {
+ printf("Failed to add root ca crt.\n");
+ goto fail_verify;
+ }
+
+ if (auth_add_crt_to_store(auth, _im_ca_crt) < 0) {
+ printf("Failed to add im ca crt.\n");
+ goto fail_verify;
+ }
+
+ if (auth_verify_crt(auth, _signed_server_crt) < 0) {
+ printf("Failed to verify signed crt.\n");
+ goto fail_verify;
+ }
+
+ if (auth_verify_crt(auth, _server_crt) == 0) {
+ printf("Failed to detect untrusted crt.\n");
+ goto fail_verify;
+ }
+
+ crypt_free_crt(_im_ca_crt);
+ crypt_free_crt(_root_ca_crt);
+ crypt_free_crt(_signed_server_crt);
+ crypt_free_crt(_server_crt);
+
+ auth_destroy_ctx(auth);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail_verify:
+ crypt_free_crt(_im_ca_crt);
+ fail_load_im_ca_crt:
+ crypt_free_crt(_root_ca_crt);
+ fail_load_root_ca_crt:
+ crypt_free_crt(_signed_server_crt);
+ fail_load_signed_server_crt:
+ crypt_free_crt(_server_crt);
+ fail_load_server_crt:
+ auth_destroy_ctx(auth);
+ fail_create_ctx:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_auth_sign(void)
+{
+ uint8_t buf[TEST_MSG_SIZE];
+ void * pkp;
+ void * pk;
+ buffer_t msg;
+ buffer_t sig;
+
+ TEST_START();
+
+ msg.data = buf;
+ msg.len = sizeof(buf);
+
+ if (random_buffer(msg.data, msg.len) < 0) {
+ printf("Failed to gen random message.\n");
+ goto fail_init;
+ }
+
+ if (crypt_load_privkey_str(server_pkp_slh,
+ &pkp) < 0) {
+ printf("Failed to load server key pair.\n");
+ goto fail_init;
+ }
+
+ if (crypt_load_pubkey_str(server_pk_slh,
+ &pk) < 0) {
+ printf("Failed to load public key.\n");
+ goto fail_pubkey;
+ }
+
+ if (auth_sign(pkp, 0, msg, &sig) < 0) {
+ printf("Failed to sign message.\n");
+ goto fail_sign;
+ }
+
+ if (auth_verify_sig(pk, 0, msg, sig) < 0) {
+ printf("Failed to verify signature.\n");
+ goto fail_verify;
+ }
+
+ freebuf(sig);
+
+ crypt_free_key(pk);
+ crypt_free_key(pkp);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail_verify:
+ freebuf(sig);
+ fail_sign:
+ crypt_free_key(pk);
+ fail_pubkey:
+ crypt_free_key(pkp);
+ fail_init:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_auth_bad_signature(void)
+{
+ uint8_t buf[TEST_MSG_SIZE];
+ void * pkp;
+ void * pk;
+ buffer_t msg;
+ buffer_t sig;
+ buffer_t fake_sig;
+
+ TEST_START();
+
+ msg.data = buf;
+ msg.len = sizeof(buf);
+
+ if (random_buffer(msg.data, msg.len) < 0) {
+ printf("Failed to gen random message.\n");
+ goto fail_init;
+ }
+
+ if (crypt_load_privkey_str(server_pkp_slh,
+ &pkp) < 0) {
+ printf("Failed to load server key pair.\n");
+ goto fail_init;
+ }
+
+ if (crypt_load_pubkey_str(server_pk_slh,
+ &pk) < 0) {
+ printf("Failed to load public key.\n");
+ goto fail_pubkey;
+ }
+
+ if (auth_sign(pkp, 0, msg, &sig) < 0) {
+ printf("Failed to sign message.\n");
+ goto fail_sign;
+ }
+
+ fake_sig.data = malloc(sig.len);
+ if (fake_sig.data == NULL) {
+ printf("Failed to alloc fake sig buf.\n");
+ goto fail_malloc;
+ }
+
+ fake_sig.len = sig.len;
+ if (random_buffer(fake_sig.data,
+ fake_sig.len) < 0) {
+ printf("Failed to gen random fake sig.\n");
+ goto fail_malloc;
+ }
+
+ if (auth_verify_sig(pk, 0, msg, fake_sig) == 0) {
+ printf("Failed to detect bad sig.\n");
+ goto fail_verify;
+ }
+
+ freebuf(fake_sig);
+ freebuf(sig);
+
+ crypt_free_key(pk);
+ crypt_free_key(pkp);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail_verify:
+ freebuf(fake_sig);
+ fail_malloc:
+ freebuf(sig);
+ fail_sign:
+ crypt_free_key(pk);
+ fail_pubkey:
+ crypt_free_key(pkp);
+ fail_init:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+int auth_test_slh_dsa(int argc,
+ char ** argv)
+{
+ int ret = 0;
+
+ (void) argc;
+ (void) argv;
+
+#ifdef HAVE_OPENSSL_SLH_DSA
+ ret |= test_auth_create_destroy_ctx();
+ ret |= test_load_free_crt();
+ ret |= test_load_free_privkey();
+ ret |= test_load_free_pubkey();
+ ret |= test_verify_crt();
+ ret |= test_auth_sign();
+ ret |= test_auth_bad_signature();
+#else
+ (void) test_auth_create_destroy_ctx;
+ (void) test_load_free_crt;
+ (void) test_load_free_privkey;
+ (void) test_load_free_pubkey;
+ (void) test_verify_crt;
+ (void) test_auth_sign;
+ (void) test_auth_bad_signature;
+
+ ret = TEST_RC_SKIP;
+#endif
+ return ret;
+}
diff --git a/src/lib/tests/bitmap_test.c b/src/lib/tests/bitmap_test.c
index 4dbd6653..81735a41 100644
--- a/src/lib/tests/bitmap_test.c
+++ b/src/lib/tests/bitmap_test.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Test of the bitmap
*
diff --git a/src/lib/tests/btree_test.c b/src/lib/tests/btree_test.c
index 8bd30370..d1a72af8 100644
--- a/src/lib/tests/btree_test.c
+++ b/src/lib/tests/btree_test.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Test of the B-tree implementation
*
diff --git a/src/lib/tests/crc32_test.c b/src/lib/tests/crc32_test.c
index a26c8220..5a1ddd87 100644
--- a/src/lib/tests/crc32_test.c
+++ b/src/lib/tests/crc32_test.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Test of the CRC32 function
*
diff --git a/src/lib/tests/crypt_test.c b/src/lib/tests/crypt_test.c
index e7a09e8f..028c4eb5 100644
--- a/src/lib/tests/crypt_test.c
+++ b/src/lib/tests/crypt_test.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Test of the cryptography functions
*
@@ -22,20 +22,33 @@
#include "config.h"
-#include <ouroboros/test.h>
-#include <ouroboros/crypt.h>
+#include <test/test.h>
#include <ouroboros/random.h>
+#include <ouroboros/crypt.h>
#include <ouroboros/utils.h>
+#include <stdio.h>
+
#define TEST_PACKET_SIZE 1500
+extern const uint16_t crypt_supported_nids[];
+extern const uint16_t md_supported_nids[];
+
static int test_crypt_create_destroy(void)
{
struct crypt_ctx * ctx;
+ uint8_t key[SYMMKEYSZ];
+ struct crypt_sk sk = {
+ .nid = NID_aes_256_gcm,
+ .key = key,
+ .rot_bit = KEY_ROTATION_BIT
+ };
TEST_START();
- ctx = crypt_create_ctx(NULL);
+ memset(key, 0, sizeof(key));
+
+ ctx = crypt_create_ctx(&sk);
if (ctx == NULL) {
printf("Failed to initialize cryptography.\n");
goto fail;
@@ -51,44 +64,155 @@ static int test_crypt_create_destroy(void)
return TEST_RC_FAIL;
}
-static int test_crypt_create_destroy_with_key(void)
+static int test_crypt_encrypt_decrypt(int nid)
{
+ uint8_t pkt[TEST_PACKET_SIZE];
struct crypt_ctx * ctx;
uint8_t key[SYMMKEYSZ];
+ struct crypt_sk sk = {
+ .nid = NID_aes_256_gcm,
+ .key = key,
+ .rot_bit = KEY_ROTATION_BIT
+ };
+ buffer_t in;
+ buffer_t out;
+ buffer_t out2;
+ const char * cipher;
- TEST_START();
+ cipher = crypt_nid_to_str(nid);
+ TEST_START("(%s)", cipher);
- memset(key, 0, sizeof(key));
+ if (random_buffer(key, sizeof(key)) < 0) {
+ printf("Failed to generate random key.\n");
+ goto fail_init;
+ }
+
+ if (random_buffer(pkt, sizeof(pkt)) < 0) {
+ printf("Failed to generate random data.\n");
+ goto fail_init;
+ }
- ctx = crypt_create_ctx(key);
+ ctx = crypt_create_ctx(&sk);
if (ctx == NULL) {
printf("Failed to initialize cryptography.\n");
- goto fail;
+ goto fail_init;
+ }
+
+ in.len = sizeof(pkt);
+ in.data = pkt;
+
+ if (crypt_encrypt(ctx, in, &out) < 0) {
+ printf("Encryption failed.\n");
+ goto fail_encrypt;
+ }
+
+ if (out.len < in.len) {
+ printf("Encryption returned too little data.\n");
+ goto fail_encrypt;
+ }
+
+ if (crypt_decrypt(ctx, out, &out2) < 0) {
+ printf("Decryption failed.\n");
+ goto fail_decrypt;
+ }
+
+ if (out2.len != in.len) {
+ printf("Decrypted data length does not match original.\n");
+ goto fail_chk;
+ }
+
+ if (memcmp(in.data, out2.data, in.len) != 0) {
+ printf("Decrypted data does not match original.\n");
+ goto fail_chk;
}
crypt_destroy_ctx(ctx);
+ freebuf(out2);
+ freebuf(out);
- TEST_SUCCESS();
+ TEST_SUCCESS("(%s)", cipher);
return TEST_RC_SUCCESS;
- fail:
- TEST_FAIL();
+ fail_chk:
+ freebuf(out2);
+ fail_decrypt:
+ freebuf(out);
+ fail_encrypt:
+ crypt_destroy_ctx(ctx);
+ fail_init:
+ TEST_FAIL("(%s)", cipher);
return TEST_RC_FAIL;
}
-static int test_crypt_dh_pkp_create_destroy(void)
+static int test_encrypt_decrypt_all(void)
+{
+ int ret = 0;
+ int i;
+
+ for (i = 0; crypt_supported_nids[i] != NID_undef; i++)
+ ret |= test_crypt_encrypt_decrypt(crypt_supported_nids[i]);
+
+ return ret;
+}
+
+#ifdef HAVE_OPENSSL
+#include <openssl/evp.h>
+#include <openssl/obj_mac.h>
+
+static int test_cipher_nid_values(void)
{
- void * pkp;
- uint8_t buf[MSGBUFSZ];
+ int i;
TEST_START();
- if (crypt_dh_pkp_create(&pkp, buf) < 0) {
- printf("Failed to create DH PKP.");
+ /* Loop over all supported ciphers and verify NIDs match OpenSSL's */
+ for (i = 0; crypt_supported_nids[i] != NID_undef; i++) {
+ uint16_t our_nid = crypt_supported_nids[i];
+ const char * str = crypt_nid_to_str(our_nid);
+ const EVP_CIPHER * cipher;
+ int openssl_nid;
+
+ if (str == NULL) {
+ printf("crypt_nid_to_str failed for NID %u\n", our_nid);
+ goto fail;
+ }
+
+ cipher = EVP_get_cipherbyname(str);
+ if (cipher == NULL) {
+ printf("OpenSSL doesn't recognize cipher '%s'\n", str);
+ goto fail;
+ }
+
+ openssl_nid = EVP_CIPHER_nid(cipher);
+
+ if (our_nid != openssl_nid) {
+ printf("NID mismatch for '%s': ours=%u, OpenSSL=%d\n",
+ str, our_nid, openssl_nid);
+ goto fail;
+ }
+
+ /* Test reverse conversion */
+ if (crypt_str_to_nid(str) != our_nid) {
+ printf("crypt_str_to_nid failed for '%s'\n", str);
+ goto fail;
+ }
+ }
+
+ /* Test error cases */
+ if (crypt_str_to_nid("invalid") != NID_undef) {
+ printf("crypt_str_to_nid: no NID_undef for invalid.\n");
goto fail;
}
- crypt_dh_pkp_destroy(pkp);
+ if (crypt_nid_to_str(9999) != NULL) {
+ printf("crypt_nid_to_str should return NULL for invalid NID\n");
+ goto fail;
+ }
+
+ if (crypt_str_to_nid(NULL) != NID_undef) {
+ printf("crypt_str_to_nid should return NID_undef for NULL\n");
+ goto fail;
+ }
TEST_SUCCESS();
@@ -98,137 +222,214 @@ static int test_crypt_dh_pkp_create_destroy(void)
return TEST_RC_FAIL;
}
-static int test_crypt_dh_derive(void)
+static int test_md_nid_values(void)
{
- void * pkp1;
- void * pkp2;
- buffer_t pk1;
- buffer_t pk2;
- ssize_t len;
- uint8_t buf1[MSGBUFSZ];
- uint8_t buf2[MSGBUFSZ];
- uint8_t s1[SYMMKEYSZ];
- uint8_t s2[SYMMKEYSZ];
+ int i;
TEST_START();
- len = crypt_dh_pkp_create(&pkp1, buf1);
- if (len < 0) {
- printf("Failed to create first key pair.");
- goto fail_pkp1;
+ for (i = 0; md_supported_nids[i] != NID_undef; i++) {
+ uint16_t our_nid = md_supported_nids[i];
+ const EVP_MD * md;
+ int openssl_nid;
+
+ md = EVP_get_digestbynid(our_nid);
+ if (md == NULL) {
+ printf("OpenSSL doesn't recognize NID %u\n", our_nid);
+ goto fail;
+ }
+
+ openssl_nid = EVP_MD_nid(md);
+ if (our_nid != openssl_nid) {
+ printf("NID mismatch: ours=%u, OpenSSL=%d\n",
+ our_nid, openssl_nid);
+ goto fail;
+ }
}
- pk1.len = (size_t) len;
- pk1.data = buf1;
+ TEST_SUCCESS();
- len = crypt_dh_pkp_create(&pkp2, buf2);
- if (len < 0) {
- printf("Failed to create second key pair.");
- goto fail_pkp2;
- }
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+#endif
+
+static int test_key_rotation(void)
+{
+ uint8_t pkt[TEST_PACKET_SIZE];
+ struct crypt_ctx * tx_ctx;
+ struct crypt_ctx * rx_ctx;
+ uint8_t key[SYMMKEYSZ];
+ struct crypt_sk sk = {
+ .nid = NID_aes_256_gcm,
+ .key = key,
+ .rot_bit = 7
+ };
+ buffer_t in;
+ buffer_t enc;
+ buffer_t dec;
+ uint32_t i;
+ uint32_t threshold;
- pk2.len = (size_t) len;
- pk2.data = buf2;
+ TEST_START();
- if (crypt_dh_derive(pkp1, pk2, s1) < 0) {
- printf("Failed to derive first key.");
+ if (random_buffer(key, sizeof(key)) < 0) {
+ printf("Failed to generate random key.\n");
goto fail;
}
- if (crypt_dh_derive(pkp2, pk1, s2) < 0) {
- printf("Failed to derive second key.");
+ if (random_buffer(pkt, sizeof(pkt)) < 0) {
+ printf("Failed to generate random data.\n");
goto fail;
}
- if (memcmp(s1, s2, SYMMKEYSZ) != 0) {
- printf("Derived keys do not match.");
+ tx_ctx = crypt_create_ctx(&sk);
+ if (tx_ctx == NULL) {
+ printf("Failed to create TX context.\n");
goto fail;
}
- crypt_dh_pkp_destroy(pkp2);
- crypt_dh_pkp_destroy(pkp1);
+ rx_ctx = crypt_create_ctx(&sk);
+ if (rx_ctx == NULL) {
+ printf("Failed to create RX context.\n");
+ goto fail_tx;
+ }
+
+ in.len = sizeof(pkt);
+ in.data = pkt;
+
+ threshold = (1U << sk.rot_bit);
+
+ /* Encrypt and decrypt across multiple rotations */
+ for (i = 0; i < threshold * 3; i++) {
+ if (crypt_encrypt(tx_ctx, in, &enc) < 0) {
+ printf("Encryption failed at packet %u.\n", i);
+ goto fail_rx;
+ }
+
+ if (crypt_decrypt(rx_ctx, enc, &dec) < 0) {
+ printf("Decryption failed at packet %u.\n", i);
+ freebuf(enc);
+ goto fail_rx;
+ }
+
+ if (dec.len != in.len ||
+ memcmp(in.data, dec.data, in.len) != 0) {
+ printf("Data mismatch at packet %u.\n", i);
+ freebuf(dec);
+ freebuf(enc);
+ goto fail_rx;
+ }
+
+ freebuf(dec);
+ freebuf(enc);
+ }
+
+ crypt_destroy_ctx(rx_ctx);
+ crypt_destroy_ctx(tx_ctx);
TEST_SUCCESS();
return TEST_RC_SUCCESS;
+ fail_rx:
+ crypt_destroy_ctx(rx_ctx);
+ fail_tx:
+ crypt_destroy_ctx(tx_ctx);
fail:
- crypt_dh_pkp_destroy(pkp2);
- fail_pkp2:
- crypt_dh_pkp_destroy(pkp1);
- fail_pkp1:
TEST_FAIL();
return TEST_RC_FAIL;
}
-int test_crypt_encrypt_decrypt(void)
+static int test_key_phase_bit(void)
{
uint8_t pkt[TEST_PACKET_SIZE];
- uint8_t key[SYMMKEYSZ];
struct crypt_ctx * ctx;
+ uint8_t key[SYMMKEYSZ];
+ struct crypt_sk sk = {
+ .nid = NID_aes_256_gcm,
+ .key = key,
+ .rot_bit = 7
+ };
buffer_t in;
buffer_t out;
- buffer_t out2;
+ uint32_t count;
+ uint32_t threshold;
+ uint8_t phase_before;
+ uint8_t phase_after;
+ int ivsz;
TEST_START();
if (random_buffer(key, sizeof(key)) < 0) {
printf("Failed to generate random key.\n");
- goto fail_init;
+ goto fail;
}
if (random_buffer(pkt, sizeof(pkt)) < 0) {
printf("Failed to generate random data.\n");
- goto fail_init;
+ goto fail;
}
- ctx = crypt_create_ctx(key);
+ ctx = crypt_create_ctx(&sk);
if (ctx == NULL) {
printf("Failed to initialize cryptography.\n");
- goto fail_init;
+ goto fail;
+ }
+
+ ivsz = crypt_get_ivsz(ctx);
+ if (ivsz <= 0) {
+ printf("Invalid IV size.\n");
+ goto fail_ctx;
}
in.len = sizeof(pkt);
in.data = pkt;
- if (crypt_encrypt(ctx, in, &out) < 0) {
- printf("Encryption failed.\n");
- goto fail_encrypt;
- }
+ /* Encrypt packets up to just before rotation threshold */
+ threshold = (1U << sk.rot_bit);
- if (out.len < in.len) {
- printf("Encryption returned too little data.\n");
- goto fail_encrypt;
+ /* Encrypt threshold - 1 packets (indices 0 to threshold-2) */
+ for (count = 0; count < threshold - 1; count++) {
+ if (crypt_encrypt(ctx, in, &out) < 0) {
+ printf("Encryption failed at count %u.\n", count);
+ goto fail_ctx;
+ }
+ freebuf(out);
}
- if (crypt_decrypt(ctx, out, &out2) < 0) {
- printf("Decryption failed.\n");
- goto fail_decrypt;
+ /* Packet at index threshold-1: phase should still be initial */
+ if (crypt_encrypt(ctx, in, &out) < 0) {
+ printf("Encryption failed before rotation.\n");
+ goto fail_ctx;
}
+ phase_before = (out.data[0] & 0x80) ? 1 : 0;
+ freebuf(out);
- if (out2.len != in.len) {
- printf("Decrypted data length does not match original.\n");
- goto fail_chk;
+ /* Packet at index threshold: phase should have toggled */
+ if (crypt_encrypt(ctx, in, &out) < 0) {
+ printf("Encryption failed at rotation threshold.\n");
+ goto fail_ctx;
}
+ phase_after = (out.data[0] & 0x80) ? 1 : 0;
+ freebuf(out);
- if (memcmp(in.data, out2.data, in.len) != 0) {
- printf("Decrypted data does not match original.\n");
- goto fail_chk;
+ /* Phase bit should have toggled */
+ if (phase_before == phase_after) {
+ printf("Phase bit did not toggle: before=%u, after=%u.\n",
+ phase_before, phase_after);
+ goto fail_ctx;
}
crypt_destroy_ctx(ctx);
- freebuf(out2);
- freebuf(out);
TEST_SUCCESS();
return TEST_RC_SUCCESS;
- fail_chk:
- freebuf(out2);
- fail_decrypt:
- freebuf(out);
- fail_encrypt:
+ fail_ctx:
crypt_destroy_ctx(ctx);
- fail_init:
+ fail:
TEST_FAIL();
return TEST_RC_FAIL;
}
@@ -242,17 +443,17 @@ int crypt_test(int argc,
(void) argv;
ret |= test_crypt_create_destroy();
- ret |= test_crypt_create_destroy_with_key();
+ ret |= test_encrypt_decrypt_all();
#ifdef HAVE_OPENSSL
- ret |= test_crypt_dh_pkp_create_destroy();
- ret |= test_crypt_dh_derive();
- ret |= test_crypt_encrypt_decrypt();
+ ret |= test_cipher_nid_values();
+ ret |= test_md_nid_values();
+ ret |= test_key_rotation();
+ ret |= test_key_phase_bit();
#else
- (void) test_crypt_dh_pkp_create_destroy;
- (void) test_crypt_dh_derive;
- (void) test_crypt_encrypt_decrypt;
+ (void) test_key_rotation;
+ (void) test_key_phase_bit;
- ret = TEST_RC_SKIP;
+ return TEST_RC_SKIP;
#endif
return ret;
}
diff --git a/src/lib/tests/hash_test.c b/src/lib/tests/hash_test.c
index 970d9185..e43847e1 100644
--- a/src/lib/tests/hash_test.c
+++ b/src/lib/tests/hash_test.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Test of the hashing functions
*
@@ -21,7 +21,7 @@
*/
#include <ouroboros/hash.h>
-#include <ouroboros/test.h>
+#include <test/test.h>
#include <stdlib.h>
#include <stdint.h>
diff --git a/src/lib/tests/kex_test.c b/src/lib/tests/kex_test.c
new file mode 100644
index 00000000..ced760fe
--- /dev/null
+++ b/src/lib/tests/kex_test.c
@@ -0,0 +1,844 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * Test of the key exchange functions
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#define _POSIX_C_SOURCE 200809L
+
+#include "config.h"
+
+#include <test/test.h>
+#include <ouroboros/utils.h>
+#include <ouroboros/crypt.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#ifdef HAVE_OPENSSL
+#include <openssl/evp.h>
+#include <openssl/x509.h>
+#endif
+
+/* Test configuration strings */
+#define KEX_CONFIG_CUSTOM \
+ "kex=X25519\n"
+
+#define KEX_CONFIG_NONE \
+ "none\n"
+
+#define KEX_CONFIG_WHITESPACE \
+ "# Comment line\n" \
+ "kex = X448" \
+ "\n" \
+ "# Another comment\n"
+
+#define KEX_CONFIG_CIPHER \
+ "kex=X25519\n" \
+ "cipher=chacha20-poly1305\n"
+
+#define KEX_CONFIG_DIGEST \
+ "kex=X25519\n" \
+ "digest=sha384\n"
+
+/* Test key material for key loading tests */
+#define X25519_PRIVKEY_PEM \
+ "-----BEGIN PRIVATE KEY-----\n" \
+ "MC4CAQAwBQYDK2VuBCIEIJDd3+/0k2IZlaH5sZ9Z2e5J8dV2U0nsXaSUm70ZaMhL\n" \
+ "-----END PRIVATE KEY-----\n"
+
+#define X25519_PUBKEY_PEM \
+ "-----BEGIN PUBLIC KEY-----\n" \
+ "MCowBQYDK2VuAyEAKYLIycSZtLFlwAX07YWWgBAYhEnRxHfgK1TVw9+mtBs=\n" \
+ "-----END PUBLIC KEY-----\n"
+
+/* Helper macro to open string constant as FILE stream */
+#define FMEMOPEN_STR(str) fmemopen((void *) (str), strlen(str), "r")
+
+extern const uint16_t kex_supported_nids[];
+
+int parse_sec_config(struct sec_config * cfg,
+ FILE * fp);
+
+static int test_kex_create_destroy(void)
+{
+ struct sec_config cfg;
+
+ TEST_START();
+
+ memset(&cfg, 0, sizeof(cfg));
+ cfg.x.nid = NID_X9_62_prime256v1;
+ cfg.x.str = kex_nid_to_str(cfg.x.nid);
+ cfg.c.nid = NID_aes_256_gcm;
+ cfg.c.str = crypt_nid_to_str(cfg.c.nid);
+
+ if (cfg.x.nid == NID_undef || cfg.c.nid == NID_undef) {
+ printf("Failed to initialize kex config.\n");
+ goto fail;
+ }
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_dh_pkp_create_destroy(void)
+{
+ struct sec_config kex;
+ void * pkp;
+ uint8_t buf[MSGBUFSZ];
+
+ TEST_START();
+
+ memset(&kex, 0, sizeof(kex));
+ SET_KEX_ALGO(&kex, "prime256v1");
+
+ if (kex_pkp_create(&kex, &pkp, buf) < 0) {
+ printf("Failed to create DH PKP.\n");
+ goto fail;
+ }
+
+ kex_pkp_destroy(pkp);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_get_algo_from_pk(const char * algo)
+{
+ struct sec_config kex;
+ void * pkp;
+ buffer_t pk;
+ ssize_t len;
+ uint8_t buf[MSGBUFSZ];
+ char extracted_algo[256];
+
+ TEST_START("(%s)", algo);
+
+ memset(&kex, 0, sizeof(kex));
+ SET_KEX_ALGO(&kex, algo);
+
+ len = kex_pkp_create(&kex, &pkp, buf);
+ if (len < 0) {
+ printf("Failed to create key pair.\n");
+ goto fail;
+ }
+
+ pk.len = (size_t) len;
+ pk.data = buf;
+
+ /* Use raw decode for hybrid KEMs, DER for others */
+ if (IS_HYBRID_KEM(algo)) {
+ if (kex_get_algo_from_pk_raw(pk, extracted_algo) < 0) {
+ printf("Failed to extract algo from pk.\n");
+ goto fail_pkp;
+ }
+ } else {
+ if (kex_get_algo_from_pk_der(pk, extracted_algo) < 0) {
+ printf("Failed to extract algo from pk.\n");
+ goto fail_pkp;
+ }
+ }
+
+ /* All algorithms should now return the specific group name */
+ if (strcmp(extracted_algo, algo) != 0) {
+ printf("Algo mismatch: expected %s, got %s.\n",
+ algo, extracted_algo);
+ goto fail_pkp;
+ }
+
+ kex_pkp_destroy(pkp);
+
+ TEST_SUCCESS("(%s)", algo);
+
+ return TEST_RC_SUCCESS;
+ fail_pkp:
+ kex_pkp_destroy(pkp);
+ fail:
+ TEST_FAIL("(%s)", algo);
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_get_algo_from_pk_all(void)
+{
+ int ret = 0;
+ int i;
+
+ for (i = 0; kex_supported_nids[i] != NID_undef; i++) {
+ const char * algo = kex_nid_to_str(kex_supported_nids[i]);
+ ret |= test_kex_get_algo_from_pk(algo);
+ }
+
+ return ret;
+}
+
+static int test_kex_dhe_derive(const char * algo)
+{
+ struct sec_config kex;
+ void * pkp1;
+ void * pkp2;
+ buffer_t pk1;
+ buffer_t pk2;
+ ssize_t len;
+ uint8_t buf1[MSGBUFSZ];
+ uint8_t buf2[MSGBUFSZ];
+ uint8_t s1[SYMMKEYSZ];
+ uint8_t s2[SYMMKEYSZ];
+
+ TEST_START("(%s)", algo);
+
+ memset(&kex, 0, sizeof(kex));
+ SET_KEX_ALGO(&kex, algo);
+
+ len = kex_pkp_create(&kex, &pkp1, buf1);
+ if (len < 0) {
+ printf("Failed to create first key pair for %s.\n", algo);
+ goto fail;
+ }
+
+ pk1.len = (size_t) len;
+ pk1.data = buf1;
+
+ len = kex_pkp_create(&kex, &pkp2, buf2);
+ if (len < 0) {
+ printf("Failed to create second key pair for %s.\n", algo);
+ goto fail_pkp1;
+ }
+
+ pk2.len = (size_t) len;
+ pk2.data = buf2;
+
+ if (kex_dhe_derive(&kex, pkp1, pk2, s1) < 0) {
+ printf("Failed to derive first key for %s.\n", algo);
+ goto fail_pkp2;
+ }
+
+ if (kex_dhe_derive(&kex, pkp2, pk1, s2) < 0) {
+ printf("Failed to derive second key for %s.\n", algo);
+ goto fail_pkp2;
+ }
+
+ if (memcmp(s1, s2, SYMMKEYSZ) != 0) {
+ printf("Derived keys do not match for %s.\n", algo);
+ goto fail_pkp2;
+ }
+
+ kex_pkp_destroy(pkp2);
+ kex_pkp_destroy(pkp1);
+
+ TEST_SUCCESS("(%s)", algo);
+
+ return TEST_RC_SUCCESS;
+ fail_pkp2:
+ kex_pkp_destroy(pkp2);
+ fail_pkp1:
+ kex_pkp_destroy(pkp1);
+ fail:
+ TEST_FAIL("(%s)", algo);
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_validate_algo(void)
+{
+ TEST_START();
+
+ if (kex_validate_algo("prime256v1") != 0) {
+ printf("prime256v1 should be valid.\n");
+ goto fail;
+ }
+
+ if (kex_validate_algo("X25519") != 0) {
+ printf("X25519 should be valid.\n");
+ goto fail;
+ }
+
+#ifdef HAVE_OPENSSL_ML_KEM
+ if (kex_validate_algo("ML-KEM-768") != 0) {
+ printf("ML-KEM-768 should be valid.\n");
+ goto fail;
+ }
+#endif
+
+ if (kex_validate_algo("ffdhe2048") != 0) {
+ printf("ffdhe2048 should be valid.\n");
+ goto fail;
+ }
+
+ if (kex_validate_algo("invalid_algo") == 0) {
+ printf("invalid_algo should be rejected.\n");
+ goto fail;
+ }
+
+ if (kex_validate_algo("rsa2048") == 0) {
+ printf("rsa2048 should be rejected.\n");
+ goto fail;
+ }
+
+ if (kex_validate_algo(NULL) == 0) {
+ printf("NULL should be rejected.\n");
+ goto fail;
+ }
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_dhe_corrupted_pubkey(const char * algo)
+{
+ struct sec_config kex;
+ void * pkp;
+ buffer_t pk;
+ ssize_t len;
+ uint8_t buf[MSGBUFSZ];
+ uint8_t s[SYMMKEYSZ];
+
+ TEST_START("(%s)", algo);
+
+ memset(&kex, 0, sizeof(kex));
+ SET_KEX_ALGO(&kex, algo);
+
+ len = kex_pkp_create(&kex, &pkp, buf);
+ if (len < 0) {
+ printf("Failed to create key pair.\n");
+ goto fail;
+ }
+
+ pk.len = (size_t) len;
+ pk.data = buf;
+
+ /* Corrupt the public key */
+ buf[0] ^= 0xFF;
+ buf[len - 1] ^= 0xFF;
+
+ if (kex_dhe_derive(&kex, pkp, pk, s) == 0) {
+ printf("Should fail with corrupted public key.\n");
+ goto fail_pkp;
+ }
+
+ kex_pkp_destroy(pkp);
+
+ TEST_SUCCESS("(%s)", algo);
+
+ return TEST_RC_SUCCESS;
+ fail_pkp:
+ kex_pkp_destroy(pkp);
+ fail:
+ TEST_FAIL("(%s)", algo);
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_dhe_wrong_algo(void)
+{
+ struct sec_config kex1;
+ struct sec_config kex2;
+ void * pkp1;
+ void * pkp2;
+ buffer_t pk2;
+ ssize_t len;
+ uint8_t buf1[MSGBUFSZ];
+ uint8_t buf2[MSGBUFSZ];
+ uint8_t s[SYMMKEYSZ];
+ const char * algo1 = "X25519";
+ const char * algo2 = "X448";
+
+ TEST_START("(%s vs %s)", algo1, algo2);
+
+ memset(&kex1, 0, sizeof(kex1));
+ memset(&kex2, 0, sizeof(kex2));
+ SET_KEX_ALGO(&kex1, algo1);
+ SET_KEX_ALGO(&kex2, algo2);
+
+ if (kex_pkp_create(&kex1, &pkp1, buf1) < 0) {
+ printf("Failed to create first key pair.\n");
+ goto fail;
+ }
+
+ len = kex_pkp_create(&kex2, &pkp2, buf2);
+ if (len < 0) {
+ printf("Failed to create second key pair.\n");
+ goto fail_pkp1;
+ }
+
+ pk2.len = (size_t) len;
+ pk2.data = buf2;
+
+ /* Try to derive with mismatched algorithms */
+ if (kex_dhe_derive(&kex1, pkp1, pk2, s) == 0) {
+ printf("Should fail with mismatched algorithms.\n");
+ goto fail_pkp2;
+ }
+
+ kex_pkp_destroy(pkp2);
+ kex_pkp_destroy(pkp1);
+
+ TEST_SUCCESS("(%s vs %s)", algo1, algo2);
+
+ return TEST_RC_SUCCESS;
+ fail_pkp2:
+ kex_pkp_destroy(pkp2);
+ fail_pkp1:
+ kex_pkp_destroy(pkp1);
+ fail:
+ TEST_FAIL("(%s vs %s)", algo1, algo2);
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_load_dhe_privkey(void)
+{
+ void * key;
+
+ TEST_START();
+
+ if (crypt_load_privkey_str(X25519_PRIVKEY_PEM, &key) < 0) {
+ printf("Failed to load X25519 private key.\n");
+ goto fail;
+ }
+
+ crypt_free_key(key);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_load_dhe_pubkey(void)
+{
+ void * key;
+
+ TEST_START();
+
+ if (crypt_load_pubkey_str(X25519_PUBKEY_PEM, &key) < 0) {
+ printf("Failed to load X25519 public key.\n");
+ goto fail;
+ }
+
+ crypt_free_key(key);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+#ifdef HAVE_OPENSSL
+#include <openssl/obj_mac.h>
+
+static int test_kex_nid_values(void)
+{
+ int i;
+
+ TEST_START();
+
+ /* Verify all KEX algorithm NIDs match OpenSSL's */
+ for (i = 0; kex_supported_nids[i] != NID_undef; i++) {
+ uint16_t our_nid = kex_supported_nids[i];
+ const char * kex_name;
+ int openssl_nid;
+
+ kex_name = kex_nid_to_str(our_nid);
+ if (kex_name == NULL) {
+ printf("kex_nid_to_str failed for NID %u\n", our_nid);
+ goto fail;
+ }
+
+ /* Test reverse conversion */
+ if (kex_str_to_nid(kex_name) != our_nid) {
+ printf("kex_str_to_nid failed for '%s'\n", kex_name);
+ goto fail;
+ }
+
+ /* Get OpenSSL's NID for this name */
+ openssl_nid = OBJ_txt2nid(kex_name);
+ if (openssl_nid != NID_undef) {
+ /* OpenSSL recognizes this algorithm */
+ if (our_nid != openssl_nid) {
+ printf("NID mismatch for '%s': "
+ "ours=%d, OpenSSL=%d\n",
+ kex_name, our_nid, openssl_nid);
+ goto fail;
+ }
+ } else {
+ /* Verify no NID collision with different algorithm */
+ const char * ossl_name = OBJ_nid2sn(our_nid);
+ if (ossl_name != NULL &&
+ strcmp(ossl_name, kex_name) != 0) {
+ printf("NID collision for '%d': "
+ "ours=%s, OpenSSL=%s\n",
+ our_nid, kex_name, ossl_name);
+ goto fail;
+ }
+ }
+ }
+
+ /* Test error cases */
+ if (kex_str_to_nid("invalid") != NID_undef) {
+ printf("kex_str_to_nid should return NID_undef for invalid\n");
+ goto fail;
+ }
+
+ if (kex_nid_to_str(9999) != NULL) {
+ printf("kex_nid_to_str should return NULL for invalid NID\n");
+ goto fail;
+ }
+
+ if (kex_str_to_nid(NULL) != NID_undef) {
+ printf("kex_str_to_nid should return NID_undef for NULL\n");
+ goto fail;
+ }
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+#endif
+
+static int test_kex_all(void)
+{
+ int ret = 0;
+ int i;
+
+ for (i = 0; kex_supported_nids[i] != NID_undef; i++) {
+ const char * algo = kex_nid_to_str(kex_supported_nids[i]);
+
+ /* KEM tests are in kex_test_ml_kem.c */
+ if (IS_KEM_ALGORITHM(algo))
+ continue;
+
+ ret |= test_kex_dhe_derive(algo);
+ }
+
+ return ret;
+}
+
+static int test_kex_dhe_corrupted_pubkey_all(void)
+{
+ int ret = 0;
+ int i;
+
+ /* Test corruption for all DHE algorithms */
+ /* KEM error injection tests are in kex_test_ml_kem.c */
+ for (i = 0; kex_supported_nids[i] != NID_undef; i++) {
+ const char * algo = kex_nid_to_str(kex_supported_nids[i]);
+
+ if (IS_KEM_ALGORITHM(algo))
+ continue;
+
+ ret |= test_kex_dhe_corrupted_pubkey(algo);
+ }
+
+ return ret;
+}
+
+static int test_kex_parse_config_empty(void)
+{
+ struct sec_config kex;
+ FILE * fp;
+
+ TEST_START();
+
+ memset(&kex, 0, sizeof(kex));
+
+ fp = FMEMOPEN_STR("\n");
+ if (fp == NULL) {
+ printf("Failed to open memory stream.\n");
+ goto fail;
+ }
+
+ if (parse_sec_config(&kex, fp) < 0) {
+ printf("Failed to parse empty config.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ if (strcmp(kex.x.str, "prime256v1") != 0) {
+ printf("Empty config should use prime256v1.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ fclose(fp);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_parse_config_custom(void)
+{
+ struct sec_config kex;
+ FILE * fp;
+
+ TEST_START();
+
+ memset(&kex, 0, sizeof(kex));
+
+ fp = FMEMOPEN_STR(KEX_CONFIG_CUSTOM);
+ if (fp == NULL) {
+ printf("Failed to open memory stream.\n");
+ goto fail;
+ }
+
+ if (parse_sec_config(&kex, fp) < 0) {
+ printf("Failed to parse custom config.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ if (strcmp(kex.x.str, "X25519") != 0) {
+ printf("Algorithm not set correctly.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ fclose(fp);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_parse_config_none(void)
+{
+ struct sec_config kex;
+ FILE * fp;
+
+ TEST_START();
+
+ memset(&kex, 0, sizeof(kex));
+
+ fp = FMEMOPEN_STR(KEX_CONFIG_NONE);
+ if (fp == NULL) {
+ printf("Failed to open memory stream.\n");
+ goto fail;
+ }
+
+ if (parse_sec_config(&kex, fp) < 0) {
+ printf("Failed to parse 'none' config.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ if (kex.x.nid != NID_undef) {
+ printf("'none' keyword should disable encryption.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ fclose(fp);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_parse_config_whitespace(void)
+{
+ struct sec_config kex;
+ FILE * fp;
+
+ TEST_START();
+
+ memset(&kex, 0, sizeof(kex));
+
+ fp = FMEMOPEN_STR(KEX_CONFIG_WHITESPACE);
+ if (fp == NULL) {
+ printf("Failed to open memory stream.\n");
+ goto fail;
+ }
+
+ if (parse_sec_config(&kex, fp) < 0) {
+ printf("Failed to parse config with comments.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ if (strcmp(kex.x.str, "X448") != 0) {
+ printf("Algorithm with whitespace not parsed correctly.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ fclose(fp);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_parse_config_cipher(void)
+{
+ struct sec_config kex;
+ FILE * fp;
+
+ TEST_START();
+
+ memset(&kex, 0, sizeof(kex));
+
+ fp = FMEMOPEN_STR(KEX_CONFIG_CIPHER);
+ if (fp == NULL) {
+ printf("Failed to open memory stream.\n");
+ goto fail;
+ }
+
+ if (parse_sec_config(&kex, fp) < 0) {
+ printf("Failed to parse cipher config.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ if (strcmp(kex.x.str, "X25519") != 0) {
+ printf("Algorithm not set correctly.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ if (kex.c.nid != NID_chacha20_poly1305) {
+ printf("Cipher not set correctly.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ fclose(fp);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_parse_config_digest(void)
+{
+ struct sec_config kex;
+ FILE * fp;
+
+ TEST_START();
+
+ memset(&kex, 0, sizeof(kex));
+
+ fp = FMEMOPEN_STR(KEX_CONFIG_DIGEST);
+ if (fp == NULL) {
+ printf("Failed to open memory stream.\n");
+ goto fail;
+ }
+
+ if (parse_sec_config(&kex, fp) < 0) {
+ printf("Failed to parse digest config.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ if (strcmp(kex.x.str, "X25519") != 0) {
+ printf("Algorithm not set correctly.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ if (kex.d.nid != NID_sha384) {
+ printf("Digest not set correctly.\n");
+ fclose(fp);
+ goto fail;
+ }
+
+ fclose(fp);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+int kex_test(int argc,
+ char ** argv)
+{
+ int ret = 0;
+
+ (void) argc;
+ (void) argv;
+
+ ret |= test_kex_create_destroy();
+ ret |= test_kex_parse_config_empty();
+ ret |= test_kex_parse_config_none();
+#ifdef HAVE_OPENSSL
+ ret |= test_kex_parse_config_custom();
+ ret |= test_kex_parse_config_whitespace();
+ ret |= test_kex_parse_config_cipher();
+ ret |= test_kex_parse_config_digest();
+ ret |= test_kex_nid_values();
+ ret |= test_kex_dh_pkp_create_destroy();
+ ret |= test_kex_all();
+ ret |= test_kex_validate_algo();
+ ret |= test_kex_get_algo_from_pk_all();
+ ret |= test_kex_dhe_wrong_algo();
+ ret |= test_kex_dhe_corrupted_pubkey_all();
+ ret |= test_kex_load_dhe_privkey();
+ ret |= test_kex_load_dhe_pubkey();
+#else
+ (void) test_kex_parse_config_custom;
+ (void) test_kex_parse_config_whitespace;
+ (void) test_kex_parse_config_cipher;
+ (void) test_kex_parse_config_digest;
+ (void) test_kex_dh_pkp_create_destroy;
+ (void) test_kex_all;
+ (void) test_kex_validate_algo;
+ (void) test_kex_get_algo_from_pk_all;
+ (void) test_kex_dhe_wrong_algo();
+ (void) test_kex_dhe_corrupted_pubkey_all;
+ (void) test_kex_load_dhe_privkey;
+ (void) test_kex_load_dhe_pubkey;
+
+ ret = TEST_RC_SKIP;
+#endif
+ return ret;
+}
diff --git a/src/lib/tests/kex_test_ml_kem.c b/src/lib/tests/kex_test_ml_kem.c
new file mode 100644
index 00000000..3bb9ae7c
--- /dev/null
+++ b/src/lib/tests/kex_test_ml_kem.c
@@ -0,0 +1,549 @@
+/*
+ * Ouroboros - Copyright (C) 2016 - 2026
+ *
+ * Test of the post-quantum key exchange functions
+ *
+ * Dimitri Staessens <dimitri@ouroboros.rocks>
+ * Sander Vrijders <sander@ouroboros.rocks>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., http://www.fsf.org/about/contact/.
+ */
+
+#define _POSIX_C_SOURCE 200809L
+
+#include "config.h"
+
+#include <test/test.h>
+#include <ouroboros/utils.h>
+#include <ouroboros/crypt.h>
+#include <ouroboros/random.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#ifdef HAVE_OPENSSL
+#include <openssl/evp.h>
+#include <openssl/x509.h>
+#endif
+
+extern const uint16_t kex_supported_nids[];
+extern const uint16_t md_supported_nids[];
+
+static int get_random_kdf(void)
+{
+ static int idx = 0;
+ int count;
+
+ if (md_supported_nids[0] == NID_undef)
+ return NID_undef;
+
+ for (count = 0; md_supported_nids[count] != NID_undef; count++)
+ ;
+
+ return md_supported_nids[(idx++) % count];
+}
+
+/* ML-KEM-768 test key material */
+
+#define MLKEM768_PRIVKEY_PEM \
+ "-----BEGIN PRIVATE KEY-----\n" \
+ "MIIJvgIBADALBglghkgBZQMEBAIEggmqMIIJpgRA+QIIiQLQkS5fl5RluSmgXRjZ\n" \
+ "YU16W4TVt0dmnBP41rLTTRT3S8CRtkb+xmoFAcWTfEzbdr5pp3g2CBRx+APXTwSC\n" \
+ "CWBll6AecTd1Kqdyix3zNQcthDBP0XnwdTHDqkKuFzMP58Y+0gc9Bo+W0xBOK2ZK\n" \
+ "gcAmix3YLJuDS8Teep/Tdc7KIm5AaLNoI8BIMgKC/ASsW8kC+78BV4OIgqNWurS9\n" \
+ "BrTiCmiag7c+6DsVDJHJ4kfcccwUDBKiW0v+LAkk1HXBcx6usrwuFC0H3ICli2sC\n" \
+ "o5DfGL7g4kWHhobXjAZnxn298C8FGmLQK5kah4nZiJ+MuHqrirziCGTLKkY1a8vC\n" \
+ "GFgzfHIcvB4dtyi9dxZmWpSXqDf2AVNgqrD2C7WQEULQOKxm/I8Mw31Yp8TC6SAP\n" \
+ "RzM4cBAXF00W4Rce05O0am/ga5dStAhikMESyckCoEGlPFFXOmjy1HmOasI+AbGk\n" \
+ "2BKp6cfbImbjd0ePdCSFEgIQwAQHm7+4UoZR2JmNwSI1AC2P4FMRAIaD2A69i6LC\n" \
+ "kFniGcOog5m09nw5FqZmeEfNs6yyFGSX16D1YyjuooAFGlU0FFX7aKwsYM8t1gkS\n" \
+ "YSUfMxIW9yzhSW4vZHuGyxlxBMr1y51RZrW8gnvW5p/Ip5yDBJRahY6KMWT15C14\n" \
+ "C2rIe8U+d4Xi5IMI3D1JNpwFebYhKs3/ManxoU7Fwwa0GzQrgLYU5KhqO8/hopnl\n" \
+ "8mQH+BPh+TR5lqYawS7HZXFJE8JzOnCtOSgB6Hz2U7oG9ik8h0FRqVD3ak20EmZU\n" \
+ "c7gpGW8Odc51uaIBzDu4ej4dGgwo4awYaX4ugLOutHqGqRfCjIVb6XQ4m35p4KKi\n" \
+ "qBVQ211aIhavUIgNECJ7WUETilXyyHLB9x3EFJdidEfSRUxLYJNAC5XM2WFCyhnE\n" \
+ "pKmossSNq6ZOqBjPegE0J6zfNg65dR/OlIdGVDgrVTIpwYAUzBMW2nTnCa00EmPj\n" \
+ "F7tRscHI8qb/QlnRVEUN+S+A2CtVIH1c666zOoRFRI9G4bmVoa8k2x0ANB51tCns\n" \
+ "vAYqkMybIgMvWwbqoAxeW0G1O3qObGXtgs94BzhAEM3RbG/hy3GR1qUNSk/qyDKc\n" \
+ "t1qpiaao0aLVsnpb28eBIk6+q0I82reGdV31OYvUpnVxRbRPFXEFs5PNS3s/7I8a\n" \
+ "SlSLUGOh+mhrUzDPSJCzgEvOmrwrRxe3F52tS0nAt6Z5zKToASHphoISUi7lGX1F\n" \
+ "Owx62qhSqqlI98bKqh7yQRZYrHXqE0bscAHCcIaZ8RVya42JHDCoQWyxqBuLOWEl\n" \
+ "+Fz6vI5DqEnJkA7ke49EvBAOJ58lxAXQIV5remtzYGPKdyG2oamiFHiLVQDzGX/l\n" \
+ "aFNMGXRWcK4/Y3mnkJvx9QGtq6KstQN/J4a51ZeX5YwNBcoY9UcFS6kHRW5rR3UM\n" \
+ "tEZj5VN8BL9nyWM9h7hUSHQboaxO7M5qswfXB8f21xR16T40Ki4nawx/6zHGCQsc\n" \
+ "uKr5SaCV88tghqJYHBorU5iKB5KsLDSHqYYrNo/Vy8W6kMA2jGAO24d4G32DSshR\n" \
+ "sEF9W1nuAHK/5ste01G5KmX2KhdZBE37oGhM98HRQ6hU8qwuKrhdV7vZis5C8LXY\n" \
+ "7MbDyDt1NnFqWFc6lYeVa6eRcmYzeAbXahrxwiiaLIdHXD95aZ/0S6+tKBGgQzwm\n" \
+ "ZsbwdXhl+n+yqDNE6Sow2bwueqhDwZVWoMCv5SK+HAGPtcZ7UU9oWrqpiL085m7F\n" \
+ "5G49KJUEZadVtj4Z9zrkeQkida+4I7v3Y3MzsWsGJww7YhTDJpsxxmSm85bHwx98\n" \
+ "hZXSqckJTL4c2nBzgrBlukIT9Wl+qItMthVvABPzp4wGZhdgKrEIRl3yCnhhUgpL\n" \
+ "lUxYegwWDMEjZxKlSbIyl5p9lCS8w2lsBzsQ2FJiAy/MWLa56aA+wFs3C8smZ6Cf\n" \
+ "p5NWa8Rm+k898GWBxZivhF03CBOZ42du0YUZdCPoA5V1KC6bh4JyWFI49VFbQFMG\n" \
+ "gwAqc0ErAH3iMammKC9746WWagnUIG3o8LygZrusuGeTohXJhVUTJDw2s0rzNhbw\n" \
+ "5IyookkY5BWENKFKTIgdBxvYelOKwbGE8Z36FEW0ABlmx7SRCKWlNVjSEAIXmMiQ\n" \
+ "VLdQF33QVYD9RR5chja254VuJH4plo+5JwiKWz8LlCIBm7CVkifZMLofmMk3s3L4\n" \
+ "sXtE+Bhfm5Plk3RrgDdlHH+hK7gk61XGdynGjDY7aLtCKZ0SMsVskSLom1pbIR5M\n" \
+ "KLYsQ1Pse4mhfDOFCkWFLI5TShGMuIoo1k7XeIE6g8QoUlV5EXyWHHhIVaE4yWGP\n" \
+ "AVgEp0UswKFeeo3SoCAeADA3U88ymxpBJp73yDIqok5dM3SgkjfPWZDkgkAI8WHs\n" \
+ "CKKeqrSOs1kkE3JXtE7kcTHT6XHo162TmgGkqMVwOQ3EmR6FRpYxJhZvuVbjJsSx\n" \
+ "YjW3ScnR4Zivoi7q95ypco331pIlIZpqV0NydUpMyQaz1cnoPKYDh1xa6LhcqEKK\n" \
+ "8a68iXjQgzgqQBDABonVybNDtlJ5lnTTuKhak8PBFAmmhj1JdrPqoIvQRCmLaark\n" \
+ "J7/q9RLtk6kTOJ0qtLe2qqwCxJwyoMd2Q5F4+xTWZHu90ljRdcnYewarqcKzoL27\n" \
+ "tcpTOmVz88I1hYVUJEV7aB36QMhTS1dquTqJZCD0hBPWAMToEoD4OFvKWmbFmzaW\n" \
+ "xrMc4ECYeDAAKYs2YqoXSLfAixBmZjb6UDB61l2GA58pFJW0ZwN8S5tApA2NRi+7\n" \
+ "oC/zgMgBGHft6E0+OUVb8It89pY1t7ybq5+fkBvEixDId3f1pK3gqcaYqG/YhoMJ\n" \
+ "MJWkqYxCNGmdZ8gFo46V6K+4xZUblQWKypN6+RYO4kDh0koppWGEULjgBoCH+V8E\n" \
+ "7GcoE8SRdQY1BIMoRVWb8Ur8ZYIVU8lqgaZPlWM3oRCiWk0kRxexFF0i5WlILIK9\n" \
+ "GT8saX+bmRd9KSy3JrpPhQn59CpJBRxz8WKdJ3wwtqE/2TbxQhLooEWHYVrZEG5E\n" \
+ "SkIoOkUAJUR+CzLLFDMdUE8w3CasE4ys+hco7AA5TAms24A1FXcxMgNb6VHA0bi5\n" \
+ "c8rPCZvjubLXR4A0/A2Ualo4cy3UAr9k0rbZOJnjqk8eExkeaxbyh42cJpU75i4O\n" \
+ "NLYsRZJkg9bkCpPgZKb707sPZO72CX3h/lQdXVgGkZ7Tqd1qzM+JOhSWvrYiBLa+\n" \
+ "5IKSmFwT+5sw1InEesXwRN09000U90vAkbZG/sZqBQHFk3xM23a+aad4NggUcfgD\n" \
+ "108=\n" \
+ "-----END PRIVATE KEY-----\n"
+
+#define MLKEM768_PUBKEY_PEM \
+ "-----BEGIN PUBLIC KEY-----\n" \
+ "MIIEsjALBglghkgBZQMEBAIDggShAMPIO3U2cWpYVzqVh5Vrp5FyZjN4BtdqGvHC\n" \
+ "KJosh0dcP3lpn/RLr60oEaBDPCZmxvB1eGX6f7KoM0TpKjDZvC56qEPBlVagwK/l\n" \
+ "Ir4cAY+1xntRT2hauqmIvTzmbsXkbj0olQRlp1W2Phn3OuR5CSJ1r7gju/djczOx\n" \
+ "awYnDDtiFMMmmzHGZKbzlsfDH3yFldKpyQlMvhzacHOCsGW6QhP1aX6oi0y2FW8A\n" \
+ "E/OnjAZmF2AqsQhGXfIKeGFSCkuVTFh6DBYMwSNnEqVJsjKXmn2UJLzDaWwHOxDY\n" \
+ "UmIDL8xYtrnpoD7AWzcLyyZnoJ+nk1ZrxGb6Tz3wZYHFmK+EXTcIE5njZ27RhRl0\n" \
+ "I+gDlXUoLpuHgnJYUjj1UVtAUwaDACpzQSsAfeIxqaYoL3vjpZZqCdQgbejwvKBm\n" \
+ "u6y4Z5OiFcmFVRMkPDazSvM2FvDkjKiiSRjkFYQ0oUpMiB0HG9h6U4rBsYTxnfoU\n" \
+ "RbQAGWbHtJEIpaU1WNIQAheYyJBUt1AXfdBVgP1FHlyGNrbnhW4kfimWj7knCIpb\n" \
+ "PwuUIgGbsJWSJ9kwuh+YyTezcvixe0T4GF+bk+WTdGuAN2Ucf6EruCTrVcZ3KcaM\n" \
+ "Njtou0IpnRIyxWyRIuibWlshHkwotixDU+x7iaF8M4UKRYUsjlNKEYy4iijWTtd4\n" \
+ "gTqDxChSVXkRfJYceEhVoTjJYY8BWASnRSzAoV56jdKgIB4AMDdTzzKbGkEmnvfI\n" \
+ "MiqiTl0zdKCSN89ZkOSCQAjxYewIop6qtI6zWSQTcle0TuRxMdPpcejXrZOaAaSo\n" \
+ "xXA5DcSZHoVGljEmFm+5VuMmxLFiNbdJydHhmK+iLur3nKlyjffWkiUhmmpXQ3J1\n" \
+ "SkzJBrPVyeg8pgOHXFrouFyoQorxrryJeNCDOCpAEMAGidXJs0O2UnmWdNO4qFqT\n" \
+ "w8EUCaaGPUl2s+qgi9BEKYtpquQnv+r1Eu2TqRM4nSq0t7aqrALEnDKgx3ZDkXj7\n" \
+ "FNZke73SWNF1ydh7BqupwrOgvbu1ylM6ZXPzwjWFhVQkRXtoHfpAyFNLV2q5Oolk\n" \
+ "IPSEE9YAxOgSgPg4W8paZsWbNpbGsxzgQJh4MAApizZiqhdIt8CLEGZmNvpQMHrW\n" \
+ "XYYDnykUlbRnA3xLm0CkDY1GL7ugL/OAyAEYd+3oTT45RVvwi3z2ljW3vJurn5+Q\n" \
+ "G8SLEMh3d/WkreCpxpiob9iGgwkwlaSpjEI0aZ1nyAWjjpXor7jFlRuVBYrKk3r5\n" \
+ "Fg7iQOHSSimlYYRQuOAGgIf5XwTsZygTxJF1BjUEgyhFVZvxSvxlghVTyWqBpk+V\n" \
+ "YzehEKJaTSRHF7EUXSLlaUgsgr0ZPyxpf5uZF30pLLcmuk+FCfn0KkkFHHPxYp0n\n" \
+ "fDC2oT/ZNvFCEuigRYdhWtkQbkRKQig6RQAlRH4LMssUMx1QTzDcJqwTjKz6Fyjs\n" \
+ "ADlMCazbgDUVdzEyA1vpUcDRuLlzys8Jm+O5stdHgDT8DZRqWjhzLdQCv2TSttk4\n" \
+ "meOqTx4TGR5rFvKHjZwmlTvmLg40tixFkmSD1uQKk+BkpvvTuw9k7vYJfeH+VB1d\n" \
+ "WAaRntOp\n" \
+ "-----END PUBLIC KEY-----\n"
+
+/* Helper macro to open string constant as FILE stream */
+#define FMEMOPEN_STR(str) fmemopen((void *) (str), strlen(str), "r")
+
+static int test_kex_load_kem_privkey(void)
+{
+ void * key;
+
+ TEST_START();
+
+ if (crypt_load_privkey_str(MLKEM768_PRIVKEY_PEM, &key) < 0) {
+ printf("Failed to load ML-KEM-768 private key.\n");
+ goto fail;
+ }
+
+ crypt_free_key(key);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_load_kem_pubkey(void)
+{
+ void * key;
+
+ TEST_START();
+
+ if (crypt_load_pubkey_str(MLKEM768_PUBKEY_PEM, &key) < 0) {
+ printf("Failed to load ML-KEM-768 public key.\n");
+ goto fail;
+ }
+
+ crypt_free_key(key);
+
+ TEST_SUCCESS();
+
+ return TEST_RC_SUCCESS;
+ fail:
+ TEST_FAIL();
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_kem(const char * algo)
+{
+ struct sec_config kex;
+ void * pkp;
+ buffer_t pk;
+ buffer_t ct;
+ ssize_t len;
+ ssize_t ct_len;
+ uint8_t buf1[MSGBUFSZ];
+ uint8_t buf2[MSGBUFSZ];
+ uint8_t s1[SYMMKEYSZ];
+ uint8_t s2[SYMMKEYSZ];
+ int kdf;
+
+ TEST_START("(%s)", algo);
+
+ kdf = get_random_kdf();
+
+ memset(&kex, 0, sizeof(kex));
+ SET_KEX_ALGO(&kex, algo);
+
+ len = kex_pkp_create(&kex, &pkp, buf1);
+ if (len < 0) {
+ printf("Failed to create key pair for %s.\n", algo);
+ goto fail;
+ }
+
+ pk.len = (size_t) len;
+ pk.data = buf1;
+
+ if (IS_HYBRID_KEM(algo))
+ ct_len = kex_kem_encap_raw(pk, buf2, kdf, s1);
+ else
+ ct_len = kex_kem_encap(pk, buf2, kdf, s1);
+
+ if (ct_len < 0) {
+ printf("Failed to encapsulate for %s.\n", algo);
+ goto fail_pkp;
+ }
+
+ ct.len = (size_t) ct_len;
+ ct.data = buf2;
+
+ if (kex_kem_decap(pkp, ct, kdf, s2) < 0) {
+ printf("Failed to decapsulate for %s.\n", algo);
+ goto fail_pkp;
+ }
+
+ if (memcmp(s1, s2, SYMMKEYSZ) != 0) {
+ printf("Shared secrets don't match for %s.\n", algo);
+ goto fail_pkp;
+ }
+
+ kex_pkp_destroy(pkp);
+
+ TEST_SUCCESS("(%s)", algo);
+
+ return TEST_RC_SUCCESS;
+ fail_pkp:
+ kex_pkp_destroy(pkp);
+ fail:
+ TEST_FAIL("(%s)", algo);
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_kem_corrupted_ciphertext(const char * algo)
+{
+ struct sec_config kex;
+ void * pkp;
+ buffer_t pk;
+ buffer_t ct;
+ ssize_t len;
+ ssize_t ct_len;
+ uint8_t buf1[MSGBUFSZ];
+ uint8_t buf2[MSGBUFSZ];
+ uint8_t s1[SYMMKEYSZ];
+ uint8_t s2[SYMMKEYSZ];
+ int kdf;
+
+ TEST_START("(%s)", algo);
+
+ kdf = get_random_kdf();
+
+ memset(&kex, 0, sizeof(kex));
+ SET_KEX_ALGO(&kex, algo);
+
+ len = kex_pkp_create(&kex, &pkp, buf1);
+ if (len < 0) {
+ printf("Failed to create key pair.\n");
+ goto fail;
+ }
+
+ pk.len = (size_t) len;
+ pk.data = buf1;
+
+ if (IS_HYBRID_KEM(algo))
+ ct_len = kex_kem_encap_raw(pk, buf2, kdf, s1);
+ else
+ ct_len = kex_kem_encap(pk, buf2, kdf, s1);
+
+ if (ct_len < 0) {
+ printf("Failed to encapsulate.\n");
+ goto fail_pkp;
+ }
+
+ ct.len = (size_t) ct_len;
+ ct.data = buf2;
+
+ /* Corrupt the ciphertext */
+ buf2[0] ^= 0xFF;
+ buf2[ct_len - 1] ^= 0xFF;
+
+ /* ML-KEM uses implicit rejection */
+ if (kex_kem_decap(pkp, ct, kdf, s2) < 0) {
+ printf("Decapsulation failed unexpectedly.\n");
+ goto fail_pkp;
+ }
+
+ /* The shared secrets should NOT match with corrupted CT */
+ if (memcmp(s1, s2, SYMMKEYSZ) == 0) {
+ printf("Corrupted ciphertext produced same secret.\n");
+ goto fail_pkp;
+ }
+
+ kex_pkp_destroy(pkp);
+
+ TEST_SUCCESS("(%s)", algo);
+
+ return TEST_RC_SUCCESS;
+ fail_pkp:
+ kex_pkp_destroy(pkp);
+ fail:
+ TEST_FAIL("(%s)", algo);
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_kem_wrong_keypair(const char * algo)
+{
+ struct sec_config kex;
+ void * pkp1;
+ void * pkp2;
+ buffer_t pk1;
+ buffer_t ct;
+ ssize_t len;
+ ssize_t ct_len;
+ uint8_t buf1[MSGBUFSZ];
+ uint8_t buf2[MSGBUFSZ];
+ uint8_t buf3[MSGBUFSZ];
+ uint8_t s1[SYMMKEYSZ];
+ uint8_t s2[SYMMKEYSZ];
+
+ TEST_START("(%s)", algo);
+
+ memset(&kex, 0, sizeof(kex));
+ SET_KEX_ALGO(&kex, algo);
+
+ len = kex_pkp_create(&kex, &pkp1, buf1);
+ if (len < 0) {
+ printf("Failed to create first key pair.\n");
+ goto fail;
+ }
+
+ pk1.len = (size_t) len;
+ pk1.data = buf1;
+
+ if (kex_pkp_create(&kex, &pkp2, buf2) < 0) {
+ printf("Failed to create second key pair.\n");
+ goto fail_pkp1;
+ }
+
+ if (IS_HYBRID_KEM(algo))
+ ct_len = kex_kem_encap_raw(pk1, buf3, NID_sha256, s1);
+ else
+ ct_len = kex_kem_encap(pk1, buf3, NID_sha256, s1);
+
+ if (ct_len < 0) {
+ printf("Failed to encapsulate.\n");
+ goto fail_pkp2;
+ }
+
+ ct.len = (size_t) ct_len;
+ ct.data = buf3;
+
+ if (kex_kem_decap(pkp2, ct, NID_sha256, s2) == 0) {
+ if (memcmp(s1, s2, SYMMKEYSZ) == 0) {
+ printf("Wrong keypair produced same secret.\n");
+ goto fail_pkp2;
+ }
+ }
+
+ kex_pkp_destroy(pkp2);
+ kex_pkp_destroy(pkp1);
+
+ TEST_SUCCESS("(%s)", algo);
+
+ return TEST_RC_SUCCESS;
+ fail_pkp2:
+ kex_pkp_destroy(pkp2);
+ fail_pkp1:
+ kex_pkp_destroy(pkp1);
+ fail:
+ TEST_FAIL("(%s)", algo);
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_kem_truncated_ciphertext(const char * algo)
+{
+ struct sec_config kex;
+ void * pkp;
+ buffer_t pk;
+ buffer_t ct;
+ ssize_t len;
+ ssize_t ct_len;
+ uint8_t buf1[MSGBUFSZ];
+ uint8_t buf2[MSGBUFSZ];
+ uint8_t s1[SYMMKEYSZ];
+ uint8_t s2[SYMMKEYSZ];
+
+ TEST_START("(%s)", algo);
+
+ memset(&kex, 0, sizeof(kex));
+ SET_KEX_ALGO(&kex, algo);
+
+ len = kex_pkp_create(&kex, &pkp, buf1);
+ if (len < 0) {
+ printf("Failed to create key pair.\n");
+ goto fail;
+ }
+
+ pk.len = (size_t) len;
+ pk.data = buf1;
+
+ if (IS_HYBRID_KEM(algo))
+ ct_len = kex_kem_encap_raw(pk, buf2, NID_sha256, s1);
+ else
+ ct_len = kex_kem_encap(pk, buf2, NID_sha256, s1);
+
+ if (ct_len < 0) {
+ printf("Failed to encapsulate.\n");
+ goto fail_pkp;
+ }
+
+ /* Truncate the ciphertext */
+ ct.len = (size_t) ct_len / 2;
+ ct.data = buf2;
+
+ if (kex_kem_decap(pkp, ct, NID_sha256, s2) == 0) {
+ printf("Should fail with truncated ciphertext.\n");
+ goto fail_pkp;
+ }
+
+ kex_pkp_destroy(pkp);
+
+ TEST_SUCCESS("(%s)", algo);
+
+ return TEST_RC_SUCCESS;
+ fail_pkp:
+ kex_pkp_destroy(pkp);
+ fail:
+ TEST_FAIL("(%s)", algo);
+ return TEST_RC_FAIL;
+}
+
+static int test_kex_kem_all(void)
+{
+ int ret = 0;
+ int i;
+
+ for (i = 0; kex_supported_nids[i] != NID_undef; i++) {
+ const char * algo = kex_nid_to_str(kex_supported_nids[i]);
+
+ if (!IS_KEM_ALGORITHM(algo))
+ continue;
+
+ ret |= test_kex_kem(algo);
+ }
+
+ return ret;
+}
+
+static int test_kex_kem_corrupted_ciphertext_all(void)
+{
+ int ret = 0;
+ int i;
+
+ for (i = 0; kex_supported_nids[i] != NID_undef; i++) {
+ const char * algo = kex_nid_to_str(kex_supported_nids[i]);
+
+ if (!IS_KEM_ALGORITHM(algo))
+ continue;
+
+ ret |= test_kex_kem_corrupted_ciphertext(algo);
+ }
+
+ return ret;
+}
+
+static int test_kex_kem_wrong_keypair_all(void)
+{
+ int ret = 0;
+ int i;
+
+ for (i = 0; kex_supported_nids[i] != NID_undef; i++) {
+ const char * algo = kex_nid_to_str(kex_supported_nids[i]);
+
+ if (!IS_KEM_ALGORITHM(algo))
+ continue;
+
+ ret |= test_kex_kem_wrong_keypair(algo);
+ }
+
+ return ret;
+}
+
+static int test_kex_kem_truncated_ciphertext_all(void)
+{
+ int ret = 0;
+ int i;
+
+ for (i = 0; kex_supported_nids[i] != NID_undef; i++) {
+ const char * algo = kex_nid_to_str(kex_supported_nids[i]);
+
+ if (!IS_KEM_ALGORITHM(algo))
+ continue;
+
+ ret |= test_kex_kem_truncated_ciphertext(algo);
+ }
+
+ return ret;
+}
+
+int kex_test_ml_kem(int argc,
+ char ** argv)
+{
+ int ret = 0;
+
+ (void) argc;
+ (void) argv;
+
+#ifdef HAVE_OPENSSL_ML_KEM
+ ret |= test_kex_load_kem_privkey();
+ ret |= test_kex_load_kem_pubkey();
+ ret |= test_kex_kem_all();
+ ret |= test_kex_kem_corrupted_ciphertext_all();
+ ret |= test_kex_kem_wrong_keypair_all();
+ ret |= test_kex_kem_truncated_ciphertext_all();
+#else
+ (void) test_kex_load_kem_privkey;
+ (void) test_kex_load_kem_pubkey;
+ (void) test_kex_kem_all;
+ (void) test_kex_kem_corrupted_ciphertext_all;
+ (void) test_kex_kem_wrong_keypair_all;
+ (void) test_kex_kem_truncated_ciphertext_all;
+
+ ret = TEST_RC_SKIP;
+#endif
+ return ret;
+}
diff --git a/src/lib/tests/md5_test.c b/src/lib/tests/md5_test.c
index 28e8f42f..ea3e12b3 100644
--- a/src/lib/tests/md5_test.c
+++ b/src/lib/tests/md5_test.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Test of the MD5 function
*
diff --git a/src/lib/tests/sha3_test.c b/src/lib/tests/sha3_test.c
index 82b4ef0d..ccd4e12a 100644
--- a/src/lib/tests/sha3_test.c
+++ b/src/lib/tests/sha3_test.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Test of the SHA3 function
*
diff --git a/src/lib/tests/shm_rbuff_test.c b/src/lib/tests/shm_rbuff_test.c
deleted file mode 100644
index e36c3229..00000000
--- a/src/lib/tests/shm_rbuff_test.c
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Ouroboros - Copyright (C) 2016 - 2024
- *
- * Test of the shm_rbuff
- *
- * Dimitri Staessens <dimitri@ouroboros.rocks>
- * Sander Vrijders <sander@ouroboros.rocks>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., http://www.fsf.org/about/contact/.
- */
-
-#define _POSIX_C_SOURCE 200112L
-
-#include "config.h"
-
-#include <ouroboros/shm_rbuff.h>
-
-#include <errno.h>
-#include <stdio.h>
-#include <unistd.h>
-
-int shm_rbuff_test(int argc,
- char ** argv)
-{
- struct shm_rbuff * rb;
- size_t i;
-
- (void) argc;
- (void) argv;
-
- printf("Test: create rbuff...");
-
- rb = shm_rbuff_create(getpid(), 1);
- if (rb == NULL)
- goto err;
-
- printf("success.\n\n");
- printf("Test: write a value...");
-
- if (shm_rbuff_write(rb, 1) < 0)
- goto error;
-
- printf("success.\n\n");
- printf("Test: check queue length is 1...");
-
- if (shm_rbuff_queued(rb) != 1)
- goto error;
-
- printf("success.\n\n");
- printf("Test: read a value...");
-
- if (shm_rbuff_read(rb) != 1)
- goto error;
-
- printf("success.\n\n");
- printf("Test: check queue is empty...");
-
- if (shm_rbuff_read(rb) != -EAGAIN)
- goto error;
-
- printf("success.\n\n");
- printf("Test: fill the queue...");
-
- for (i = 0; i < SHM_RBUFF_SIZE - 1; ++i) {
- if (shm_rbuff_queued(rb) != i)
- goto error;
- if (shm_rbuff_write(rb, 1) < 0)
- goto error;
- }
-
- printf("success.\n\n");
- printf("Test: check queue is full...");
-
- if (shm_rbuff_queued(rb) != SHM_RBUFF_SIZE - 1)
- goto error;
-
- printf("success [%zd entries].\n\n", shm_rbuff_queued(rb));
-
- printf("Test: check queue is full by writing value...");
- if (!(shm_rbuff_write(rb, 1) < 0))
- goto error;
-
- printf("success [%zd entries].\n\n", shm_rbuff_queued(rb));
-
- /* empty the rbuff */
- while (shm_rbuff_read(rb) >= 0)
- ;
-
- shm_rbuff_destroy(rb);
-
- return 0;
-
- error:
- /* empty the rbuff */
- while (shm_rbuff_read(rb) >= 0)
- ;
-
- shm_rbuff_destroy(rb);
- err:
- printf("failed.\n\n");
- return -1;
-}
diff --git a/src/lib/tests/sockets_test.c b/src/lib/tests/sockets_test.c
index bbf2323b..c00bfdc1 100644
--- a/src/lib/tests/sockets_test.c
+++ b/src/lib/tests/sockets_test.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Tests for socket.c
*
@@ -20,10 +20,14 @@
* Foundation, Inc., http://www.fsf.org/about/contact/.
*/
+#if defined(__linux__) || defined(__CYGWIN__)
+#define _DEFAULT_SOURCE
+#else
#define _POSIX_C_SOURCE 200112L
+#endif
#include <ouroboros/sockets.h>
-#include <ouroboros/test.h>
+#include <test/test.h>
#include <assert.h>
#include <stdio.h>
diff --git a/src/lib/tests/time_test.c b/src/lib/tests/time_test.c
index 2b75b873..919cf075 100644
--- a/src/lib/tests/time_test.c
+++ b/src/lib/tests/time_test.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Test of the time utilities
*
@@ -22,7 +22,7 @@
#define _POSIX_C_SOURCE 200809L
-#include <ouroboros/test.h>
+#include <test/test.h>
#include <ouroboros/time.h>
#include <stdio.h>
diff --git a/src/lib/tests/tpm_test.c b/src/lib/tests/tpm_test.c
index 98d4fab3..df1d8850 100644
--- a/src/lib/tests/tpm_test.c
+++ b/src/lib/tests/tpm_test.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Tests for the threadpool manager
*
@@ -23,7 +23,7 @@
#include "tpm.c"
-#include <ouroboros/test.h>
+#include <test/test.h>
static void * test_func(void * o)
{
diff --git a/src/lib/timerwheel.c b/src/lib/timerwheel.c
index 96f4ac47..2c796c96 100644
--- a/src/lib/timerwheel.c
+++ b/src/lib/timerwheel.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Timerwheel
*
@@ -31,7 +31,7 @@ struct rxm {
struct list_head next;
uint32_t seqno;
#ifndef RXM_BUFFER_ON_HEAP
- struct shm_du_buff * sdb;
+ struct ssm_pk_buff * spb;
#endif
struct frct_pci * pkt;
size_t len;
@@ -81,8 +81,8 @@ static void timerwheel_fini(void)
#ifdef RXM_BUFFER_ON_HEAP
free(rxm->pkt);
#else
- shm_du_buff_ack(rxm->sdb);
- ipcp_sdb_release(rxm->sdb);
+ ssm_pk_buff_ack(rxm->spb);
+ ipcp_spb_release(rxm->spb);
#endif
free(rxm);
}
@@ -160,7 +160,7 @@ static void timerwheel_move(void)
size_t slot;
size_t rslot;
ssize_t idx;
- struct shm_du_buff * sdb;
+ struct ssm_pk_buff * spb;
struct frct_pci * pci;
struct flow * f;
uint32_t snd_lwe;
@@ -173,9 +173,9 @@ static void timerwheel_move(void)
snd_cr = &r->frcti->snd_cr;
rcv_cr = &r->frcti->rcv_cr;
- f = &ai.flows[r->fd];
+ f = &proc.flows[r->fd];
#ifndef RXM_BUFFER_ON_HEAP
- shm_du_buff_ack(r->sdb);
+ ssm_pk_buff_ack(r->spb);
#endif
if (f->frcti == NULL
|| f->info.id != r->flow_id)
@@ -224,45 +224,45 @@ static void timerwheel_move(void)
rslot = (rslot + slot + 1) & (RXMQ_SLOTS - 1);
#ifdef RXM_BLOCKING
- if (ipcp_sdb_reserve(&sdb, r->len) < 0)
+ if (ipcp_spb_reserve(&spb, r->len) < 0)
#else
- if (shm_rdrbuff_alloc(ai.rdrb, r->len, NULL,
- &sdb) < 0)
+ if (ssm_pool_alloc(proc.pool, r->len, NULL,
+ &spb) < 0)
#endif
goto reschedule; /* rdrbuff full */
- pci = (struct frct_pci *) shm_du_buff_head(sdb);
+ pci = (struct frct_pci *) ssm_pk_buff_head(spb);
memcpy(pci, r->pkt, r->len);
#ifndef RXM_BUFFER_ON_HEAP
- ipcp_sdb_release(r->sdb);
- r->sdb = sdb;
+ ipcp_spb_release(r->spb);
+ r->spb = spb;
r->pkt = pci;
- shm_du_buff_wait_ack(sdb);
+ ssm_pk_buff_wait_ack(spb);
#endif
- idx = shm_du_buff_get_idx(sdb);
+ idx = ssm_pk_buff_get_idx(spb);
/* Retransmit the copy. */
pci->ackno = hton32(rcv_lwe);
#ifdef RXM_BLOCKING
- if (shm_rbuff_write_b(f->tx_rb, idx, NULL) < 0)
+ if (ssm_rbuff_write_b(f->tx_rb, idx, NULL) < 0)
#else
- if (shm_rbuff_write(f->tx_rb, idx) < 0)
+ if (ssm_rbuff_write(f->tx_rb, idx) < 0)
#endif
goto flow_down;
- shm_flow_set_notify(f->set, f->info.id,
+ ssm_flow_set_notify(f->set, f->info.id,
FLOW_PKT);
reschedule:
list_add(&r->next, &rw.rxms[lvl][rslot]);
continue;
flow_down:
- shm_rbuff_set_acl(f->tx_rb, ACL_FLOWDOWN);
- shm_rbuff_set_acl(f->rx_rb, ACL_FLOWDOWN);
+ ssm_rbuff_set_acl(f->tx_rb, ACL_FLOWDOWN);
+ ssm_rbuff_set_acl(f->rx_rb, ACL_FLOWDOWN);
cleanup:
#ifdef RXM_BUFFER_ON_HEAP
free(r->pkt);
#else
- ipcp_sdb_release(r->sdb);
+ ipcp_spb_release(r->spb);
#endif
free(r);
}
@@ -288,7 +288,7 @@ static void timerwheel_move(void)
list_del(&a->next);
- f = &ai.flows[a->fd];
+ f = &proc.flows[a->fd];
rw.map[j & (ACKQ_SLOTS - 1)][a->fd] = false;
@@ -306,7 +306,7 @@ static void timerwheel_move(void)
static int timerwheel_rxm(struct frcti * frcti,
uint32_t seqno,
- struct shm_du_buff * sdb)
+ struct ssm_pk_buff * spb)
{
struct timespec now;
struct rxm * r;
@@ -323,17 +323,17 @@ static int timerwheel_rxm(struct frcti * frcti,
r->t0 = ts_to_ns(now);
r->seqno = seqno;
r->frcti = frcti;
- r->len = shm_du_buff_len(sdb);
+ r->len = ssm_pk_buff_len(spb);
#ifdef RXM_BUFFER_ON_HEAP
r->pkt = malloc(r->len);
if (r->pkt == NULL) {
free(r);
return -ENOMEM;
}
- memcpy(r->pkt, shm_du_buff_head(sdb), r->len);
+ memcpy(r->pkt, ssm_pk_buff_head(spb), r->len);
#else
- r->sdb = sdb;
- r->pkt = (struct frct_pci *) shm_du_buff_head(sdb);
+ r->spb = spb;
+ r->pkt = (struct frct_pci *) ssm_pk_buff_head(spb);
#endif
pthread_rwlock_rdlock(&r->frcti->lock);
@@ -341,7 +341,7 @@ static int timerwheel_rxm(struct frcti * frcti,
slot = r->t0 >> RXMQ_RES;
r->fd = frcti->fd;
- r->flow_id = ai.flows[r->fd].info.id;
+ r->flow_id = proc.flows[r->fd].info.id;
pthread_rwlock_unlock(&r->frcti->lock);
@@ -365,7 +365,7 @@ static int timerwheel_rxm(struct frcti * frcti,
list_add_tail(&r->next, &rw.rxms[lvl][slot]);
#ifndef RXM_BUFFER_ON_HEAP
- shm_du_buff_wait_ack(sdb);
+ ssm_pk_buff_wait_ack(spb);
#endif
pthread_mutex_unlock(&rw.lock);
@@ -394,7 +394,7 @@ static int timerwheel_delayed_ack(int fd,
a->fd = fd;
a->frcti = frcti;
- a->flow_id = ai.flows[fd].info.id;
+ a->flow_id = proc.flows[fd].info.id;
pthread_mutex_lock(&rw.lock);
diff --git a/src/lib/tpm.c b/src/lib/tpm.c
index 7a17ef6b..9229ea1a 100644
--- a/src/lib/tpm.c
+++ b/src/lib/tpm.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Threadpool management
*
@@ -100,7 +100,7 @@ static void tpm_debug_thread(struct pthr_el * e)
if (BETWEEN(TPM_DEBUG_REPORT_INTERVAL, 0, intv)) {
log_dbg("Thread %d:%lx running for %ld s.\n",
- getpid(),e->thr, diff);
+ getpid(), (unsigned long) e->thr, diff);
e->last = now;
}
diff --git a/src/lib/utils.c b/src/lib/utils.c
index fd275f63..6b49cc00 100644
--- a/src/lib/utils.c
+++ b/src/lib/utils.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Handy utilities
*
@@ -20,10 +20,15 @@
* Foundation, Inc., http://www.fsf.org/about/contact/.
*/
-#define _POSIX_C_SOURCE 200809L
+#define _DEFAULT_SOURCE
+
+#include "config.h"
#include <ouroboros/utils.h>
+#include <ctype.h>
+#include <grp.h>
+#include <pwd.h>
#include <stdlib.h>
#include <string.h>
@@ -67,6 +72,24 @@ char * path_strip(const char * src)
return dst;
}
+char * trim_whitespace(char * str)
+{
+ char * end;
+
+ while (isspace((unsigned char) *str))
+ str++;
+
+ if (*str == '\0')
+ return str;
+
+ /* Trim trailing space */
+ end = str + strlen(str) - 1;
+ while (end > str && isspace((unsigned char)*end))
+ *end-- = '\0';
+
+ return str;
+}
+
size_t argvlen(const char ** argv)
{
size_t argc = 0;
@@ -119,5 +142,72 @@ char ** argvdup(char ** argv)
}
argv_dup[argc] = NULL;
+
return argv_dup;
}
+
+bool is_ouroboros_member_uid(uid_t uid)
+{
+ struct group * grp;
+ struct passwd * pw;
+#ifdef __APPLE__
+ unsigned int gid;
+ int * groups = NULL;
+#else
+ gid_t gid;
+ gid_t * groups = NULL;
+#endif
+ int ngroups;
+ int i;
+
+ /* Root is always privileged */
+ if (uid == 0)
+ return true;
+
+ grp = getgrnam("ouroboros");
+ if (grp == NULL)
+ return false;
+
+ gid = grp->gr_gid;
+
+ pw = getpwuid(uid);
+ if (pw == NULL)
+ return false;
+
+ if (pw->pw_gid == gid)
+ return true;
+
+ ngroups = 0;
+ getgrouplist(pw->pw_name, pw->pw_gid, NULL, &ngroups);
+ if (ngroups <= 0)
+ return false;
+
+ groups = malloc(ngroups * sizeof(*groups));
+ if (groups == NULL)
+ return false;
+
+ if (getgrouplist(pw->pw_name, pw->pw_gid, groups, &ngroups) < 0) {
+ free(groups);
+ return false;
+ }
+
+ for (i = 0; i < ngroups; i++) {
+#ifdef __APPLE__
+ if (groups[i] == (int) gid) {
+#else
+ if (groups[i] == gid) {
+#endif
+ free(groups);
+ return true;
+ }
+ }
+
+ free(groups);
+
+ return false;
+}
+
+bool is_ouroboros_member(void)
+{
+ return is_ouroboros_member_uid(getuid());
+}
diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt
index 7c40d9ae..3cec8172 100644
--- a/src/tools/CMakeLists.txt
+++ b/src/tools/CMakeLists.txt
@@ -1,9 +1,71 @@
-add_subdirectory(irm)
-add_subdirectory(ocbr)
-add_subdirectory(oecho)
-add_subdirectory(obc)
-add_subdirectory(oping)
-add_subdirectory(operf)
-if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
- add_subdirectory(ovpn)
-endif ()
+# Tools build configuration
+
+set(TOOLS_INCLUDE_DIRS
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${CMAKE_SOURCE_DIR}/include
+ ${CMAKE_BINARY_DIR}/include
+)
+
+set(IRM_SOURCES
+ irm/irm.c
+ irm/irm_bind_program.c
+ irm/irm_bind_process.c
+ irm/irm_bind_ipcp.c
+ irm/irm_ipcp_create.c
+ irm/irm_ipcp_destroy.c
+ irm/irm_ipcp_bootstrap.c
+ irm/irm_ipcp_enroll.c
+ irm/irm_ipcp_list.c
+ irm/irm_ipcp_connect.c
+ irm/irm_ipcp_disconnect.c
+ irm/irm_unbind_program.c
+ irm/irm_unbind_process.c
+ irm/irm_unbind_ipcp.c
+ irm/irm_unbind.c
+ irm/irm_bind.c
+ irm/irm_ipcp.c
+ irm/irm_name.c
+ irm/irm_name_create.c
+ irm/irm_name_destroy.c
+ irm/irm_name_reg.c
+ irm/irm_name_unreg.c
+ irm/irm_name_list.c
+ irm/irm_utils.c
+)
+
+add_executable(irm ${IRM_SOURCES})
+target_include_directories(irm PRIVATE ${TOOLS_INCLUDE_DIRS})
+target_link_libraries(irm PRIVATE ouroboros-irm)
+install(TARGETS irm RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR})
+
+add_executable(oping oping/oping.c)
+target_include_directories(oping PRIVATE ${TOOLS_INCLUDE_DIRS})
+target_link_libraries(oping PRIVATE ${LIBM_LIBRARIES} ouroboros-dev)
+install(TARGETS oping RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+add_executable(oecho oecho/oecho.c)
+target_include_directories(oecho PRIVATE ${TOOLS_INCLUDE_DIRS})
+target_link_libraries(oecho PRIVATE ouroboros-dev)
+install(TARGETS oecho RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+add_executable(ocbr ocbr/ocbr.c)
+target_include_directories(ocbr PRIVATE ${TOOLS_INCLUDE_DIRS})
+target_link_libraries(ocbr PRIVATE ouroboros-dev)
+install(TARGETS ocbr RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+add_executable(obc obc/obc.c)
+target_include_directories(obc PRIVATE ${TOOLS_INCLUDE_DIRS})
+target_link_libraries(obc PRIVATE ouroboros-dev)
+install(TARGETS obc RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+add_executable(operf operf/operf.c)
+target_include_directories(operf PRIVATE ${TOOLS_INCLUDE_DIRS})
+target_link_libraries(operf PRIVATE ouroboros-dev)
+install(TARGETS operf RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+
+if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
+ add_executable(ovpn ovpn/ovpn.c)
+ target_include_directories(ovpn PRIVATE ${TOOLS_INCLUDE_DIRS})
+ target_link_libraries(ovpn PRIVATE ouroboros-dev)
+ install(TARGETS ovpn RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
+endif()
diff --git a/src/tools/irm/CMakeLists.txt b/src/tools/irm/CMakeLists.txt
deleted file mode 100644
index 3c599300..00000000
--- a/src/tools/irm/CMakeLists.txt
+++ /dev/null
@@ -1,44 +0,0 @@
-include_directories(${CMAKE_CURRENT_SOURCE_DIR})
-include_directories(${CMAKE_CURRENT_BINARY_DIR})
-
-include_directories(${CMAKE_SOURCE_DIR}/include)
-include_directories(${CMAKE_BINARY_DIR}/include)
-
-set(TOOLS_IRM_SOURCE_FILES
- # Add source files here
- irm.c
- irm_bind_program.c
- irm_bind_process.c
- irm_bind_ipcp.c
- irm_ipcp_create.c
- irm_ipcp_destroy.c
- irm_ipcp_bootstrap.c
- irm_ipcp_enroll.c
- irm_ipcp_list.c
- irm_ipcp_connect.c
- irm_ipcp_disconnect.c
- irm_unbind_program.c
- irm_unbind_process.c
- irm_unbind_ipcp.c
- irm_unbind.c
- irm_bind.c
- irm_ipcp.c
- irm_name.c
- irm_name_create.c
- irm_name_destroy.c
- irm_name_reg.c
- irm_name_unreg.c
- irm_name_list.c
- irm_utils.c
- )
-
-add_executable(irm ${TOOLS_IRM_SOURCE_FILES})
-
-target_link_libraries(irm LINK_PUBLIC ouroboros-irm)
-
-install(TARGETS irm RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR})
-
-# enable when we have tests
-# if(BUILD_TESTS)
-# add_subdirectory(tests)
-# endif ()
diff --git a/src/tools/irm/irm.c b/src/tools/irm/irm.c
index ba0f4713..6c2719d0 100644
--- a/src/tools/irm/irm.c
+++ b/src/tools/irm/irm.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* A tool to instruct the IRM daemon
*
diff --git a/src/tools/irm/irm_bind.c b/src/tools/irm/irm_bind.c
index 2e8b14ef..3107837a 100644
--- a/src/tools/irm/irm_bind.c
+++ b/src/tools/irm/irm_bind.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Bind names in the processing system
*
diff --git a/src/tools/irm/irm_bind_ipcp.c b/src/tools/irm/irm_bind_ipcp.c
index 7d5dd636..4c183534 100644
--- a/src/tools/irm/irm_bind_ipcp.c
+++ b/src/tools/irm/irm_bind_ipcp.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Bind IPCP Instance to a name
*
diff --git a/src/tools/irm/irm_bind_process.c b/src/tools/irm/irm_bind_process.c
index fffd5fe9..fee0c46b 100644
--- a/src/tools/irm/irm_bind_process.c
+++ b/src/tools/irm/irm_bind_process.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Bind a process to a name
*
diff --git a/src/tools/irm/irm_bind_program.c b/src/tools/irm/irm_bind_program.c
index 8a0dc33c..14d09db7 100644
--- a/src/tools/irm/irm_bind_program.c
+++ b/src/tools/irm/irm_bind_program.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Bind programs to a name
*
diff --git a/src/tools/irm/irm_ipcp.c b/src/tools/irm/irm_ipcp.c
index 63e617d9..34458a20 100644
--- a/src/tools/irm/irm_ipcp.c
+++ b/src/tools/irm/irm_ipcp.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* A tool to instruct the IRM daemon
*
diff --git a/src/tools/irm/irm_ipcp_bootstrap.c b/src/tools/irm/irm_ipcp_bootstrap.c
index 3fabc3cc..de73b076 100644
--- a/src/tools/irm/irm_ipcp_bootstrap.c
+++ b/src/tools/irm/irm_ipcp_bootstrap.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Bootstrap IPC Processes
*
diff --git a/src/tools/irm/irm_ipcp_connect.c b/src/tools/irm/irm_ipcp_connect.c
index 68e13bd0..f88c36dc 100644
--- a/src/tools/irm/irm_ipcp_connect.c
+++ b/src/tools/irm/irm_ipcp_connect.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Connect components of unicast or broadcast IPC processes
*
diff --git a/src/tools/irm/irm_ipcp_create.c b/src/tools/irm/irm_ipcp_create.c
index e2a5c488..c6b2074b 100644
--- a/src/tools/irm/irm_ipcp_create.c
+++ b/src/tools/irm/irm_ipcp_create.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Create IPC Processes
*
diff --git a/src/tools/irm/irm_ipcp_destroy.c b/src/tools/irm/irm_ipcp_destroy.c
index 1a5e564e..523836af 100644
--- a/src/tools/irm/irm_ipcp_destroy.c
+++ b/src/tools/irm/irm_ipcp_destroy.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Destroy IPC Processes
*
diff --git a/src/tools/irm/irm_ipcp_disconnect.c b/src/tools/irm/irm_ipcp_disconnect.c
index 7ce724e1..0f37ec91 100644
--- a/src/tools/irm/irm_ipcp_disconnect.c
+++ b/src/tools/irm/irm_ipcp_disconnect.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Connect components of unicast or broadcast IPC processes
*
diff --git a/src/tools/irm/irm_ipcp_enroll.c b/src/tools/irm/irm_ipcp_enroll.c
index 86a22a71..350b536e 100644
--- a/src/tools/irm/irm_ipcp_enroll.c
+++ b/src/tools/irm/irm_ipcp_enroll.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Enroll IPC Processes
*
diff --git a/src/tools/irm/irm_ipcp_list.c b/src/tools/irm/irm_ipcp_list.c
index 54985eb4..a211a02b 100644
--- a/src/tools/irm/irm_ipcp_list.c
+++ b/src/tools/irm/irm_ipcp_list.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* List IPC Processes
*
diff --git a/src/tools/irm/irm_name.c b/src/tools/irm/irm_name.c
index d60b6c78..830ae305 100644
--- a/src/tools/irm/irm_name.c
+++ b/src/tools/irm/irm_name.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* A tool to instruct the IRM daemon
*
diff --git a/src/tools/irm/irm_name_create.c b/src/tools/irm/irm_name_create.c
index 22341d2e..1055700c 100644
--- a/src/tools/irm/irm_name_create.c
+++ b/src/tools/irm/irm_name_create.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Create IPC Processes
*
@@ -51,10 +51,10 @@
#define RR "round-robin"
#define SPILL "spillover"
-#define SENC "<security_dir>/server/<name>/enc.cfg"
+#define SENC "<security_dir>/server/<name>/enc.conf"
#define SCRT "<security_dir>/server/<name>/crt.pem"
#define SKEY "<security_dir>/server/<name>/key.pem"
-#define CENC "<security_dir>/client/<name>/enc.cfg"
+#define CENC "<security_dir>/client/<name>/enc.conf"
#define CCRT "<security_dir>/client/<name>/crt.pem"
#define CKEY "<security_dir>/client/<name>/key.pem"
diff --git a/src/tools/irm/irm_name_destroy.c b/src/tools/irm/irm_name_destroy.c
index d4bd6c82..d5ed05d5 100644
--- a/src/tools/irm/irm_name_destroy.c
+++ b/src/tools/irm/irm_name_destroy.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Create IPC Processes
*
diff --git a/src/tools/irm/irm_name_list.c b/src/tools/irm/irm_name_list.c
index a807008c..37e1f023 100644
--- a/src/tools/irm/irm_name_list.c
+++ b/src/tools/irm/irm_name_list.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* List names
*
diff --git a/src/tools/irm/irm_name_reg.c b/src/tools/irm/irm_name_reg.c
index 7689119a..860f4a70 100644
--- a/src/tools/irm/irm_name_reg.c
+++ b/src/tools/irm/irm_name_reg.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Register names with IPCPs
*
diff --git a/src/tools/irm/irm_name_unreg.c b/src/tools/irm/irm_name_unreg.c
index 6e579f04..abf08548 100644
--- a/src/tools/irm/irm_name_unreg.c
+++ b/src/tools/irm/irm_name_unreg.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Unregister names from IPCPs
*
diff --git a/src/tools/irm/irm_ops.h b/src/tools/irm/irm_ops.h
index e04ffc02..195c5cbc 100644
--- a/src/tools/irm/irm_ops.h
+++ b/src/tools/irm/irm_ops.h
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Functions of the IRM tool that are one level deep
*
diff --git a/src/tools/irm/irm_unbind.c b/src/tools/irm/irm_unbind.c
index d6594d01..4e5914a9 100644
--- a/src/tools/irm/irm_unbind.c
+++ b/src/tools/irm/irm_unbind.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Unbind names in the processing system
*
diff --git a/src/tools/irm/irm_unbind_ipcp.c b/src/tools/irm/irm_unbind_ipcp.c
index 53a2d16c..23e25057 100644
--- a/src/tools/irm/irm_unbind_ipcp.c
+++ b/src/tools/irm/irm_unbind_ipcp.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Unbind name from IPCP Instance
*
diff --git a/src/tools/irm/irm_unbind_process.c b/src/tools/irm/irm_unbind_process.c
index 264ed538..bc7e545c 100644
--- a/src/tools/irm/irm_unbind_process.c
+++ b/src/tools/irm/irm_unbind_process.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Unbind process names
*
diff --git a/src/tools/irm/irm_unbind_program.c b/src/tools/irm/irm_unbind_program.c
index 0c751e80..031b9909 100644
--- a/src/tools/irm/irm_unbind_program.c
+++ b/src/tools/irm/irm_unbind_program.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Unbind programs
*
diff --git a/src/tools/irm/irm_utils.c b/src/tools/irm/irm_utils.c
index 9694d647..69873097 100644
--- a/src/tools/irm/irm_utils.c
+++ b/src/tools/irm/irm_utils.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Handy helper functions for the IRM tool
*
diff --git a/src/tools/irm/irm_utils.h b/src/tools/irm/irm_utils.h
index 27a0b941..c6d4bf18 100644
--- a/src/tools/irm/irm_utils.h
+++ b/src/tools/irm/irm_utils.h
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Handy helper functions for the IRM tool
*
diff --git a/src/tools/obc/CMakeLists.txt b/src/tools/obc/CMakeLists.txt
deleted file mode 100644
index db5e999b..00000000
--- a/src/tools/obc/CMakeLists.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-include_directories(${CMAKE_CURRENT_SOURCE_DIR})
-include_directories(${CMAKE_CURRENT_BINARY_DIR})
-
-include_directories(${CMAKE_SOURCE_DIR}/include)
-include_directories(${CMAKE_BINARY_DIR}/include)
-
-set(SOURCE_FILES
- # Add source files here
- obc.c
- )
-
-add_executable(obc ${SOURCE_FILES})
-
-target_link_libraries(obc LINK_PUBLIC ouroboros-dev)
-
-install(TARGETS obc RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
diff --git a/src/tools/obc/obc.c b/src/tools/obc/obc.c
index 778eb8a8..5b8470f0 100644
--- a/src/tools/obc/obc.c
+++ b/src/tools/obc/obc.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* A simple broadcast application
*
diff --git a/src/tools/ocbr/CMakeLists.txt b/src/tools/ocbr/CMakeLists.txt
deleted file mode 100644
index f7ba66cd..00000000
--- a/src/tools/ocbr/CMakeLists.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-include_directories(${CMAKE_CURRENT_SOURCE_DIR})
-include_directories(${CMAKE_CURRENT_BINARY_DIR})
-
-include_directories(${CMAKE_SOURCE_DIR}/include)
-include_directories(${CMAKE_BINARY_DIR}/include)
-
-get_filename_component(CURRENT_SOURCE_PARENT_DIR
- ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY)
-
-include_directories(${CURRENT_SOURCE_PARENT_DIR})
-
-set(SOURCE_FILES
- # Add source files here
- ocbr.c
- )
-
-add_executable(ocbr ${SOURCE_FILES})
-
-target_link_libraries(ocbr LINK_PUBLIC ouroboros-dev)
-
-install(TARGETS ocbr RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
diff --git a/src/tools/ocbr/ocbr.c b/src/tools/ocbr/ocbr.c
index 775bcaac..c92ba0e0 100644
--- a/src/tools/ocbr/ocbr.c
+++ b/src/tools/ocbr/ocbr.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* CBR traffic generator
*
diff --git a/src/tools/ocbr/ocbr_client.c b/src/tools/ocbr/ocbr_client.c
index eada6e60..9dd9904c 100644
--- a/src/tools/ocbr/ocbr_client.c
+++ b/src/tools/ocbr/ocbr_client.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* A simple CBR generator
*
diff --git a/src/tools/ocbr/ocbr_server.c b/src/tools/ocbr/ocbr_server.c
index 34c4fa94..c98b33e9 100644
--- a/src/tools/ocbr/ocbr_server.c
+++ b/src/tools/ocbr/ocbr_server.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* A simple CBR generator
*
diff --git a/src/tools/oecho/CMakeLists.txt b/src/tools/oecho/CMakeLists.txt
deleted file mode 100644
index 50a66138..00000000
--- a/src/tools/oecho/CMakeLists.txt
+++ /dev/null
@@ -1,16 +0,0 @@
-include_directories(${CMAKE_CURRENT_SOURCE_DIR})
-include_directories(${CMAKE_CURRENT_BINARY_DIR})
-
-include_directories(${CMAKE_SOURCE_DIR}/include)
-include_directories(${CMAKE_BINARY_DIR}/include)
-
-set(SOURCE_FILES
- # Add source files here
- oecho.c
- )
-
-add_executable(oecho ${SOURCE_FILES})
-
-target_link_libraries(oecho LINK_PUBLIC ouroboros-dev)
-
-install(TARGETS oecho RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
diff --git a/src/tools/oecho/oecho.c b/src/tools/oecho/oecho.c
index d5d03027..14caab53 100644
--- a/src/tools/oecho/oecho.c
+++ b/src/tools/oecho/oecho.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* A simple echo application
*
diff --git a/src/tools/operf/CMakeLists.txt b/src/tools/operf/CMakeLists.txt
deleted file mode 100644
index b6faf04e..00000000
--- a/src/tools/operf/CMakeLists.txt
+++ /dev/null
@@ -1,26 +0,0 @@
-include_directories(${CMAKE_CURRENT_SOURCE_DIR})
-include_directories(${CMAKE_CURRENT_BINARY_DIR})
-
-include_directories(${CMAKE_SOURCE_DIR}/include)
-include_directories(${CMAKE_BINARY_DIR}/include)
-
-get_filename_component(CURRENT_SOURCE_PARENT_DIR
- ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY)
-
-include_directories(${CURRENT_SOURCE_PARENT_DIR})
-
-find_library(LIBM_LIBRARIES m)
-if(NOT LIBM_LIBRARIES)
- message(FATAL_ERROR "libm not found")
-endif()
-
-set(SOURCE_FILES
- # Add source files here
- operf.c
- )
-
-add_executable(operf ${SOURCE_FILES})
-
-target_link_libraries(operf LINK_PUBLIC ${LIBM_LIBRARIES} ouroboros-dev)
-
-install(TARGETS operf RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
diff --git a/src/tools/operf/operf.c b/src/tools/operf/operf.c
index 10896bd5..1872b351 100644
--- a/src/tools/operf/operf.c
+++ b/src/tools/operf/operf.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Ouroboros perf application
*
@@ -54,7 +54,6 @@
#include <stdlib.h>
#include <sys/time.h>
#include <arpa/inet.h>
-#include <math.h>
#include <errno.h>
#include <float.h>
diff --git a/src/tools/operf/operf_client.c b/src/tools/operf/operf_client.c
index 7060ce5b..7e8f1a9b 100644
--- a/src/tools/operf/operf_client.c
+++ b/src/tools/operf/operf_client.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Ouroboros ping application
*
diff --git a/src/tools/operf/operf_server.c b/src/tools/operf/operf_server.c
index a611f79c..00f780ba 100644
--- a/src/tools/operf/operf_server.c
+++ b/src/tools/operf/operf_server.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Ouroboros perf application
*
diff --git a/src/tools/oping/CMakeLists.txt b/src/tools/oping/CMakeLists.txt
deleted file mode 100644
index 31a4f961..00000000
--- a/src/tools/oping/CMakeLists.txt
+++ /dev/null
@@ -1,28 +0,0 @@
-include_directories(${CMAKE_CURRENT_SOURCE_DIR})
-include_directories(${CMAKE_CURRENT_BINARY_DIR})
-
-include_directories(${CMAKE_SOURCE_DIR}/include)
-include_directories(${CMAKE_BINARY_DIR}/include)
-
-get_filename_component(CURRENT_SOURCE_PARENT_DIR
- ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY)
-
-include_directories(${CURRENT_SOURCE_PARENT_DIR})
-
-find_library(LIBM_LIBRARIES m)
-if(NOT LIBM_LIBRARIES)
- message(FATAL_ERROR "libm not found")
-endif()
-
-mark_as_advanced(LIBM_LIBRARIES)
-
-set(SOURCE_FILES
- # Add source files here
- oping.c
- )
-
-add_executable(oping ${SOURCE_FILES})
-
-target_link_libraries(oping LINK_PUBLIC ${LIBM_LIBRARIES} ouroboros-dev)
-
-install(TARGETS oping RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
diff --git a/src/tools/oping/oping.c b/src/tools/oping/oping.c
index 87c1ee18..763c0d62 100644
--- a/src/tools/oping/oping.c
+++ b/src/tools/oping/oping.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Ouroboros ping application
*
@@ -72,12 +72,16 @@
"and reports the Round Trip Time (RTT)\n" \
"\n" \
" -l, --listen Run in server mode\n" \
+" --poll Server uses polling (lower latency)\n" \
+" --busy Server uses busy-poll (single flow)\n" \
"\n" \
" -c, --count Number of packets\n" \
" -d, --duration Duration of the test (default 1s)\n" \
+" -f, --flood Send back-to-back without waiting\n" \
+" -F, --flood-busy Flood with busy-polling (lower latency)\n" \
" -i, --interval Interval (default 1000ms)\n" \
" -n, --server-name Name of the oping server\n" \
-" -q, --qos QoS (raw, best, video, voice, data)\n" \
+" -q, --qos QoS (raw, best, video, voice, data)\n" \
" -s, --size Payload size (B, default 64)\n" \
" -Q, --quiet Only print final statistics\n" \
" -D, --timeofday Print time of day before each line\n" \
@@ -90,6 +94,8 @@ struct {
uint32_t count;
int size;
bool timestamp;
+ bool flood;
+ bool flood_busy;
qosspec_t qs;
/* stats */
@@ -114,6 +120,8 @@ struct {
pthread_mutex_t lock;
bool quiet;
+ bool poll;
+ bool busy;
pthread_t cleaner_pt;
pthread_t accept_pt;
@@ -172,9 +180,13 @@ int main(int argc,
client.size = 64;
client.count = INT_MAX;
client.timestamp = false;
+ client.flood = false;
+ client.flood_busy = false;
client.qs = qos_raw;
client.quiet = false;
server.quiet = false;
+ server.poll = false;
+ server.busy = false;
while (argc > 0) {
if ((strcmp(*argv, "-i") == 0 ||
@@ -212,6 +224,12 @@ int main(int argc,
} else if (strcmp(*argv, "-l") == 0 ||
strcmp(*argv, "--listen") == 0) {
serv = true;
+ } else if (strcmp(*argv, "-f") == 0 ||
+ strcmp(*argv, "--flood") == 0) {
+ client.flood = true;
+ } else if (strcmp(*argv, "-F") == 0 ||
+ strcmp(*argv, "--flood-busy") == 0) {
+ client.flood_busy = true;
} else if (strcmp(*argv, "-D") == 0 ||
strcmp(*argv, "--timeofday") == 0) {
client.timestamp = true;
@@ -219,6 +237,10 @@ int main(int argc,
strcmp(*argv, "--quiet") == 0) {
client.quiet = true;
server.quiet = true;
+ } else if (strcmp(*argv, "--poll") == 0) {
+ server.poll = true;
+ } else if (strcmp(*argv, "--busy") == 0) {
+ server.busy = true;
} else {
goto fail;
}
diff --git a/src/tools/oping/oping_client.c b/src/tools/oping/oping_client.c
index 5a9e03dc..23807f65 100644
--- a/src/tools/oping/oping_client.c
+++ b/src/tools/oping/oping_client.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Ouroboros ping application
*
@@ -53,6 +53,40 @@ void shutdown_client(int signo, siginfo_t * info, void * c)
}
}
+static void update_rtt_stats(double ms)
+{
+ double d;
+
+ if (ms < client.rtt_min)
+ client.rtt_min = ms;
+ if (ms > client.rtt_max)
+ client.rtt_max = ms;
+
+ d = (ms - client.rtt_avg);
+ client.rtt_avg += d / client.rcvd;
+ client.rtt_m2 += d * (ms - client.rtt_avg);
+}
+
+static double rtt_val(double ms)
+{
+ return ms < 0.1 ? ms * 1000 : ms;
+}
+
+static const char * rtt_unit(double ms)
+{
+ return ms < 0.1 ? "µs" : "ms";
+}
+
+static void print_rtt(int len, int seq,
+ double ms, const char * suf)
+{
+ printf("%d bytes from %s: seq=%d "
+ "time=%.3f %s%s\n",
+ len, client.s_apn, seq,
+ rtt_val(ms), rtt_unit(ms),
+ suf != NULL ? suf : "");
+}
+
void * reader(void * o)
{
struct timespec timeout = {client.interval / 1000 + 2, 0};
@@ -64,7 +98,6 @@ void * reader(void * o)
int fd = *((int *) o);
int msg_len = 0;
double ms = 0;
- double d = 0;
uint32_t exp_id = 0;
fccntl(fd, FLOWSRCVTIMEO, &timeout);
@@ -114,22 +147,12 @@ void * reader(void * o)
(size_t) rtc.tv_nsec / 1000);
}
- printf("%d bytes from %s: seq=%d time=%.3f ms%s\n",
- msg_len,
- client.s_apn,
- ntohl(msg->id),
- ms,
- id < exp_id ? " [out-of-order]" : "");
+ print_rtt(msg_len, ntohl(msg->id), ms,
+ id < exp_id ?
+ " [out-of-order]" : NULL);
}
- if (ms < client.rtt_min)
- client.rtt_min = ms;
- if (ms > client.rtt_max)
- client.rtt_max = ms;
-
- d = (ms - client.rtt_avg);
- client.rtt_avg += d / client.rcvd;
- client.rtt_m2 += d * (ms - client.rtt_avg);
+ update_rtt_stats(ms);
if (id >= exp_id)
exp_id = id + 1;
@@ -204,13 +227,174 @@ static void client_fini(void)
return;
}
+static void print_stats(struct timespec * tic,
+ struct timespec * toc)
+{
+ printf("\n");
+ printf("--- %s ping statistics ---\n", client.s_apn);
+ printf("%d packets transmitted, ", client.sent);
+ printf("%d received, ", client.rcvd);
+ printf("%zd out-of-order, ", client.ooo);
+ printf("%.0lf%% packet loss, ", client.sent == 0 ? 0 :
+ ceil(100 - (100 * (client.rcvd / (float) client.sent))));
+ printf("time: %.3f ms\n", ts_diff_us(toc, tic) / 1000.0);
+
+ if (client.rcvd > 0) {
+ double a = client.rtt_avg;
+ double f = a < 0.1 ? 1000 : 1;
+ printf("rtt min/avg/max/mdev = %.3f/%.3f/%.3f/",
+ client.rtt_min * f, client.rtt_avg * f,
+ client.rtt_max * f);
+ if (client.rcvd > 1)
+ printf("%.3f %s\n",
+ sqrt(client.rtt_m2 /
+ (client.rcvd - 1)) * f,
+ rtt_unit(a));
+ else
+ printf("NaN %s\n", rtt_unit(a));
+ }
+}
+
+static int flood_busy_ping(int fd)
+{
+ char buf[OPING_BUF_SIZE];
+ struct oping_msg * msg = (struct oping_msg *) buf;
+ struct timespec sent;
+ struct timespec rcvd;
+ double ms;
+ int n;
+
+ memset(buf, 0, client.size);
+
+ fccntl(fd, FLOWSFLAGS,
+ FLOWFRDWR | FLOWFRNOPART | FLOWFRNOBLOCK);
+
+ if (!client.quiet)
+ printf("Pinging %s with %d bytes"
+ " of data (%u packets,"
+ " busy-poll):\n\n",
+ client.s_apn, client.size,
+ client.count);
+
+ while (!stop && client.sent < client.count) {
+ clock_gettime(CLOCK_MONOTONIC, &sent);
+
+ msg->type = htonl(ECHO_REQUEST);
+ msg->id = htonl(client.sent);
+ msg->tv_sec = sent.tv_sec;
+ msg->tv_nsec = sent.tv_nsec;
+
+ if (flow_write(fd, buf,
+ client.size) < 0) {
+ printf("Failed to send "
+ "packet.\n");
+ break;
+ }
+
+ ++client.sent;
+
+ do {
+ n = flow_read(fd, buf,
+ OPING_BUF_SIZE);
+ } while (n == -EAGAIN && !stop);
+
+ if (n < 0)
+ break;
+
+ clock_gettime(CLOCK_MONOTONIC, &rcvd);
+
+ if (ntohl(msg->type) != ECHO_REPLY)
+ continue;
+
+ ++client.rcvd;
+
+ sent.tv_sec = msg->tv_sec;
+ sent.tv_nsec = msg->tv_nsec;
+ ms = ts_diff_us(&rcvd, &sent) / 1000.0;
+
+ update_rtt_stats(ms);
+
+ if (!client.quiet)
+ print_rtt(client.size,
+ ntohl(msg->id), ms,
+ NULL);
+ }
+
+ return 0;
+}
+
+static int flood_ping(int fd)
+{
+ char buf[OPING_BUF_SIZE];
+ struct oping_msg * msg = (struct oping_msg *) buf;
+ struct timespec sent;
+ struct timespec rcvd;
+ double ms;
+
+ memset(buf, 0, client.size);
+
+ if (!client.quiet)
+ printf("Pinging %s with %d bytes of data (%u packets):\n\n",
+ client.s_apn, client.size, client.count);
+
+ while (!stop && client.sent < client.count) {
+ clock_gettime(CLOCK_MONOTONIC, &sent);
+
+ msg->type = htonl(ECHO_REQUEST);
+ msg->id = htonl(client.sent);
+ msg->tv_sec = sent.tv_sec;
+ msg->tv_nsec = sent.tv_nsec;
+
+ if (flow_write(fd, buf, client.size) < 0) {
+ printf("Failed to send packet.\n");
+ break;
+ }
+
+ ++client.sent;
+
+ if (flow_read(fd, buf, OPING_BUF_SIZE) < 0) {
+ printf("Failed to read packet.\n");
+ break;
+ }
+
+ clock_gettime(CLOCK_MONOTONIC, &rcvd);
+
+ if (ntohl(msg->type) != ECHO_REPLY)
+ continue;
+
+ ++client.rcvd;
+
+ sent.tv_sec = msg->tv_sec;
+ sent.tv_nsec = msg->tv_nsec;
+ ms = ts_diff_us(&rcvd, &sent) / 1000.0;
+
+ update_rtt_stats(ms);
+
+ if (!client.quiet)
+ print_rtt(client.size,
+ ntohl(msg->id), ms,
+ NULL);
+ }
+
+ return 0;
+}
+
+static int threaded_ping(int fd)
+{
+ pthread_create(&client.reader_pt, NULL, reader, &fd);
+ pthread_create(&client.writer_pt, NULL, writer, &fd);
+
+ pthread_join(client.writer_pt, NULL);
+ pthread_join(client.reader_pt, NULL);
+
+ return 0;
+}
+
static int client_main(void)
{
struct sigaction sig_act;
-
struct timespec tic;
struct timespec toc;
-
int fd;
memset(&sig_act, 0, sizeof sig_act);
@@ -241,37 +425,18 @@ static int client_main(void)
clock_gettime(CLOCK_REALTIME, &tic);
- pthread_create(&client.reader_pt, NULL, reader, &fd);
- pthread_create(&client.writer_pt, NULL, writer, &fd);
-
- pthread_join(client.writer_pt, NULL);
- pthread_join(client.reader_pt, NULL);
+ if (client.flood_busy)
+ flood_busy_ping(fd);
+ else if (client.flood)
+ flood_ping(fd);
+ else
+ threaded_ping(fd);
clock_gettime(CLOCK_REALTIME, &toc);
- printf("\n");
- printf("--- %s ping statistics ---\n", client.s_apn);
- printf("%d packets transmitted, ", client.sent);
- printf("%d received, ", client.rcvd);
- printf("%zd out-of-order, ", client.ooo);
- printf("%.0lf%% packet loss, ", client.sent == 0 ? 0 :
- ceil(100 - (100 * (client.rcvd / (float) client.sent))));
- printf("time: %.3f ms\n", ts_diff_us(&toc, &tic) / 1000.0);
-
- if (client.rcvd > 0) {
- printf("rtt min/avg/max/mdev = %.3f/%.3f/%.3f/",
- client.rtt_min,
- client.rtt_avg,
- client.rtt_max);
- if (client.rcvd > 1)
- printf("%.3f ms\n",
- sqrt(client.rtt_m2 / (client.rcvd - 1)));
- else
- printf("NaN ms\n");
- }
+ print_stats(&tic, &toc);
flow_dealloc(fd);
-
client_fini();
return 0;
diff --git a/src/tools/oping/oping_server.c b/src/tools/oping/oping_server.c
index c1d5e6e5..33af28c4 100644
--- a/src/tools/oping/oping_server.c
+++ b/src/tools/oping/oping_server.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Ouroboros ping application
*
@@ -89,12 +89,15 @@ void * server_thread(void *o)
struct oping_msg * msg = (struct oping_msg *) buf;
struct timespec now = {0, 0};
struct timespec timeout = {0, 100 * MILLION};
+ struct timespec poll_timeout = {0, 0};
int fd;
(void) o;
while (true) {
- if (fevent(server.flows, server.fq, &timeout) == -ETIMEDOUT)
+ if (fevent(server.flows, server.fq,
+ server.poll ? &poll_timeout : &timeout)
+ == -ETIMEDOUT)
continue;
while ((fd = fqueue_next(server.fq)) >= 0) {
@@ -135,7 +138,10 @@ void * accept_thread(void * o)
(void) o;
- printf("Ouroboros ping server started.\n");
+ printf("Ouroboros ping server started.");
+ if (server.busy)
+ printf(" [busy-poll]");
+ printf("\n");
while (true) {
fd = flow_accept(&qs, NULL);
@@ -155,12 +161,56 @@ void * accept_thread(void * o)
pthread_mutex_unlock(&server.lock);
fccntl(fd, FLOWSFLAGS,
- FLOWFRNOBLOCK | FLOWFRDWR | FLOWFRNOPART);
+ FLOWFRNOBLOCK | FLOWFRDWR
+ | FLOWFRNOPART);
}
return (void *) 0;
}
+void * busy_thread(void * o)
+{
+ char buf[OPING_BUF_SIZE];
+ struct oping_msg * msg = (struct oping_msg *) buf;
+ int fd;
+ int msg_len;
+
+ (void) o;
+
+ /* Accept a single flow. */
+ fd = flow_accept(NULL, NULL);
+ if (fd < 0) {
+ printf("Failed to accept flow.\n");
+ return (void *) -1;
+ }
+
+ printf("New flow %d (busy-poll).\n", fd);
+
+ fccntl(fd, FLOWSFLAGS,
+ FLOWFRNOBLOCK | FLOWFRDWR
+ | FLOWFRNOPART);
+
+ while (true) {
+ msg_len = flow_read(fd, buf,
+ OPING_BUF_SIZE);
+ if (msg_len == -EAGAIN)
+ continue;
+ if (msg_len < 0)
+ break;
+
+ if (ntohl(msg->type) != ECHO_REQUEST)
+ continue;
+
+ msg->type = htonl(ECHO_REPLY);
+
+ flow_write(fd, buf, msg_len);
+ }
+
+ flow_dealloc(fd);
+
+ return (void *) 0;
+}
+
int server_main(void)
{
struct sigaction sig_act;
@@ -188,12 +238,21 @@ int server_main(void)
}
pthread_create(&server.cleaner_pt, NULL, cleaner_thread, NULL);
- pthread_create(&server.accept_pt, NULL, accept_thread, NULL);
- pthread_create(&server.server_pt, NULL, server_thread, NULL);
- pthread_join(server.accept_pt, NULL);
+ if (server.busy) {
+ pthread_create(&server.server_pt, NULL,
+ busy_thread, NULL);
+ pthread_join(server.server_pt, NULL);
+ pthread_cancel(server.cleaner_pt);
+ } else {
+ pthread_create(&server.accept_pt, NULL,
+ accept_thread, NULL);
+ pthread_create(&server.server_pt, NULL,
+ server_thread, NULL);
+ pthread_join(server.accept_pt, NULL);
+ pthread_cancel(server.server_pt);
+ }
- pthread_cancel(server.server_pt);
pthread_cancel(server.cleaner_pt);
fset_destroy(server.flows);
diff --git a/src/tools/ovpn/CMakeLists.txt b/src/tools/ovpn/CMakeLists.txt
deleted file mode 100644
index f3a2cac8..00000000
--- a/src/tools/ovpn/CMakeLists.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-include_directories(${CMAKE_CURRENT_SOURCE_DIR})
-include_directories(${CMAKE_CURRENT_BINARY_DIR})
-
-include_directories(${CMAKE_SOURCE_DIR}/include)
-include_directories(${CMAKE_BINARY_DIR}/include)
-
-get_filename_component(CURRENT_SOURCE_PARENT_DIR
- ${CMAKE_CURRENT_SOURCE_DIR} DIRECTORY)
-
-include_directories(${CURRENT_SOURCE_PARENT_DIR})
-
-set(SOURCE_FILES
- # Add source files here
- ovpn.c
- )
-
-add_executable(ovpn ${SOURCE_FILES})
-
-target_link_libraries(ovpn LINK_PUBLIC ouroboros-dev)
-
-install(TARGETS ovpn RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
diff --git a/src/tools/ovpn/ovpn.c b/src/tools/ovpn/ovpn.c
index b25e3ea2..95b4572d 100644
--- a/src/tools/ovpn/ovpn.c
+++ b/src/tools/ovpn/ovpn.c
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Ouroboros VPN
*
diff --git a/src/tools/time_utils.h b/src/tools/time_utils.h
index a4117f44..a0729074 100644
--- a/src/tools/time_utils.h
+++ b/src/tools/time_utils.h
@@ -1,5 +1,5 @@
/*
- * Ouroboros - Copyright (C) 2016 - 2024
+ * Ouroboros - Copyright (C) 2016 - 2026
*
* Time utilities
*