summaryrefslogtreecommitdiff
path: root/src/irmd
Commit message (Collapse)AuthorAgeFilesLines
* lib: Update FRCP implementationDimitri Staessens4 days3-8/+8
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The Flow and Retransmission Control Protocol (FRCP) runs end-to-end between two peers over a flow. It provides reliability, in-order delivery, flow control, and liveness. Note that congestion avoidance is orthogonal to FRCP and handled in the IPCP. A fixed 16-octet header, network byte order, is prefixed to every FRCP packet: 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | flags | hcs | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | window | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | seqno | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | ackno | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | payload (variable) ... +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ hcs is a CRC-16-CCITT-FALSE checksum over the PCI (and the stream extension when present), verified before any flag-driven dispatch. A single packet can simultaneously carry DATA + ACK + FC + RXM by OR-ing flag bits. An optional CRC trailer covers the body on DATA when qs.ber == 0, and on every SACK packet; an optional AEAD wrap (per-flow keys) sits outermost. Flag bits (MSB-first; bits 13..15 reserved, MUST be zero): +------+--------+--------+----------------------------------------+ | Bit | Mask | Name | Meaning | +------+--------+--------+----------------------------------------+ | 0 | 0x8000 | DATA | Carries caller payload | | 1 | 0x4000 | DRF | Start of a fresh data run | | 2 | 0x2000 | ACK | ackno field valid | | 3 | 0x1000 | NACK | Pre-DRF nudge (seqno informational) | | 4 | 0x0800 | FC | window field valid (rwe advertisement) | | 5 | 0x0400 | RDVS | Rendezvous probe (window-closed) | | 6 | 0x0200 | FFGM | First Fragment of a multi-fragment SDU | | 7 | 0x0100 | LFGM | Last Fragment of a multi-fragment SDU | | 8 | 0x0080 | RXM | Retransmission | | 9 | 0x0040 | SACK | Block list follows in payload | | 10 | 0x0020 | RTTP | RTT probe / echo (payload follows) | | 11 | 0x0010 | KA | Keepalive | | 12 | 0x0008 | FIN | End of stream marker | | 13-15| -- | -- | Reserved (MUST be zero) | +------+--------+--------+----------------------------------------+ (FFGM, LFGM) encodes the fragment role of a DATA packet (SCTP-style B/E): 11=SOLE, 10=FIRST, 00=MID, 01=LAST. Each fragment carries its own seqno; Retransmission recovers fragments individually, reassembly runs at consume time. In stream mode FFGM/LFGM are unused; per-byte position is carried by the stream extension below and end-of-stream is signalled by FIN on a 0-byte DATA packet. SACK payload (FRCT_ACK | FRCT_FC | FRCT_SACK): 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | n_blocks | padding (2 octets) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | start[0] | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | end[0] | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ... n_blocks pairs total ... Each block describes a *present* (received) range strictly above the cumulative ACK in the PCI ackno. D-SACK (RFC 2883) is signalled in-band as block[0] - no flag bit, no extra framing - and consumed by the RACK reo_wnd_mult scaler (RFC 8985 sec. 7.2). RTTP payload (FRCT_RTTP only; 24 octets): 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | probe_id | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | echo_id | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | + nonce (16 octets, echoed verbatim) + | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Stream PCI extension (in_order == STREAM only; 8 octets after the base PCI on every DATA packet): 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 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | start | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | end | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ start, end are monotonic 32-bit byte offsets; end - start equals the on-wire payload length. Stream mode is negotiated at flow allocation; the extension is present iff stream mode is in use, never on a per-packet basis. Service modes are an orthogonal (in_order, loss, ber) vector selected at flow_alloc; the cubes above map to the axes: +----------------+---------+------+-----+-----------------------+ | Cube | in_order| loss | ber | Engaged | +----------------+---------+------+-----+-----------------------+ | qos_raw | 0 | 1 | 1 | Raw passthrough | | qos_raw_safe | 0 | 1 | 0 | Raw + CRC trailer | | qos_rt | 1 | 1 | 1 | FRCP, no FRTX, no CRC | | qos_rt_safe | 1 | 1 | 0 | FRCP, no FRTX, CRC | | qos_msg | 1 | 0 | 0 | FRCP + FRTX | | qos_stream | 2 | 0 | 0 | FRCP + FRTX, stream | +----------------+---------+------+-----+-----------------------+ in_order=0 sends raw datagrams with no PCI (UDP-equivalent); in_order=1 engages FRCP with SDU framing; in_order=2 (stream) requires loss=0 and is rejected otherwise. loss=0 engages the FRTX retransmit machinery. ber=0 appends the CRC-32 trailer; QOS_DISABLE_CRC at build time forces ber=1 for development. Encryption is a separate per-flow attribute layered as an AEAD wrap outside the FRCP packet. Heritage: delta-t (Watson 1981) supplies timer-based connection management - no SYN/FIN handshake, the DRF marker, the t_mpl / t_a / t_r timers. RINA (Day 2008) supplies the unified flow_alloc(name, qos, ...) primitive and the orthogonal QoS-cube axes. Loss detection follows TCP/QUIC practice (RFCs 2018, 2883, 6582, 6298, 8985); RTT probing is nonce-authenticated like QUIC PATH_CHALLENGE. Adds oftp, a minimal file-transfer tool over an FRCP stream flow. The client reads from stdin or --in FILE and writes through a flow_alloc(qos_stream); the server (--listen) calls flow_accept and writes to stdout or --out FILE. Both sides compute a CRC-64/NVMe over the bytes they handle and print the result. The server rejects flows whose negotiated qs.in_order != STREAM. Two FRCP knobs are exposed via env vars on either side: OFTP_FRCT_RTO_MIN fccntl FRCTSRTOMIN (ns) OFTP_FRCT_STREAM_RING_SZ fccntl FRCTSRRINGSZ (octets) The ocbr_client gains an OCBR_QOS env var to pick the cube the client uses for flow_alloc; recognised values are raw, safe, rt, rt_safe, msg, stream. Unknown values fall back to raw with a warning on stderr. Without the env set behaviour is unchanged. Removes the deprecated lib/timerwheel.c Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Pass MTU from IPCP to process for FRCTDimitri Staessens4 days4-0/+41
| | | | | | | | | FRCT needs to know the MTU for fragmentation. The MTU is now passed from the layer serving the flow to the process as part of flow allocation. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd, lib: Minor cleanup in oap/srv and irm.protoDimitri Staessens4 days1-6/+2
| | | | | Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Drop replayed flow alloc requestsDimitri Staessens4 days6-6/+112
| | | | | | | | | | | | | | A duplicating link could deliver the same alloc request twice. OAP detected the replay but still replied over the wire, so the requester saw a second flow_alloc_reply on an already-allocated flow and reg_respond_alloc tripped its PENDING-state assertion. Add EREPLAY so the OAP server can signal replays distinctly; flow_accept drops them silently. As a safety net, reg_respond_alloc warn-drops late replies instead of asserting. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Remove dead codeDimitri Staessens2026-03-141-130/+0
| | | | | | | | The oap.c source code was split into separate modules in the oap/ folder but some of it was never correctly removed. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Fix certificate DER encoding and key buffersDimitri Staessens2026-03-142-2/+2
| | | | | | | | | | | i2d_X509() allocated buf->data via OPENSSL_malloc(), but callers free it with freebuf() which uses free(). Fix by allocating with malloc() and encoding directly into the buffer. Also replaces MSGBUFSZ with CRYPT_KEY_BUFSZ (4096) for key material buffers and removes leftover debug logging. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Relax test flow accept timeoutDimitri Staessens2026-03-141-1/+1
| | | | | | | | The reg_test still had false-positive failures on slow machines / woodpecker. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Fix missing cleanup in authentication pathDimitri Staessens2026-03-144-2/+6
| | | | | | | | | | | | | When auth_verify_crt fails (e.g., missing root CA), crypt_get_pubkey_crt has already allocated pk but only crt was freed. Adds a crypt_cleanup() function to wrap OpenSSL_cleanup(), as OpenSSL lazily initializes a global decoder/provider registry the first time PEM_read_bio or OSSL_DECODER_CTX_new_for_pkey is called, and this leaves some memory owned by OpenSSL that triggers the leak sanitizer. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Add tests for missing root CADimitri Staessens2026-03-141-0/+70
| | | | | | | | | This adds authentication tests to verify flows are rejected with a missing root CA certificate in the store. Also adds one for the OAP protocol. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Fix bad merge in reg.cHEAD0.23.0masterSander Vrijders2026-02-221-4/+0
| | | | | | | A merge conflict was left unresolved, resulting in compilation errors. Signed-off-by: Sander Vrijders <sander@ouroboros.rocks> Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
* irmd: Fix memleak in reg testsDimitri Staessens2026-02-222-0/+8
| | | | | | | | | | Call freebuf(pbuf) before returning from each test thread function. Since clrbuf zeroes pbuf.data to NULL on the success path, free(NULL) is safe. On the failure path of reg_respond_*, it now properly frees the still-allocated data. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Allow direct rbuff between local processesDimitri Staessens2026-02-229-210/+605
| | | | | | | | | | | | | | | | | | | | | | This allows bypassing the IPCP for local processes that share the same packet pool, lowering latency between processes to comparable levels as Unix sockets (RTT in the order of a microsecond). For local processes, no IPCPs are needed: $ irm b prog oping n oping $ oping -l Ouroboros ping server started. New flow 64. Received 64 bytes on fd 64. The direct IPC can be disabled with the DISABLE_DIRECT_IPC build flag. Note that this is needed for rumba 'local' experiments to emulate network topologies. Without this flag all processes will just communicate directly. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* build: Add git hash to version stringDimitri Staessens2026-02-182-4/+4
| | | | | | | | | | | | | | | | | | | | | | | | Embed git commit hash into version.h and irmd --version output using git describe. Regenerated at build time to stay current across commits. Ouroboros version MAJOR.MINOR.PATCH (TAG-COMMITS-GHASH-dirty) Example for dirty work tree (uncommitted changes): $ irmd --version Ouroboros version 0.22.0 (0.22.0-36-g86dba544-dirty) Example after commit: $ sudo irmd --version Ouroboros version 0.22.0-37-g55fa9445 Officical release (on tag): Ouroboros version 0.22.0 Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Add struct llist for lists tracking lenDimitri Staessens2026-02-1811-261/+167
| | | | | | | | | | | | | The DHT uses a struct {struct list_head, size_t len} pattern, which is also useful in the registry and other places. Having a struct llist (defined in list.h) with consistent macros for addition/deletion etc removes a lot of duplication and boilerplate and reduces the risk of inconsistent updates. The list management is now a macro-only implementation. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* build: Update copyright to 2026Dimitri Staessens2026-02-1837-37/+37
| | | | | Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Add SLH-DSA tests and per-algorithm PQC gatingDimitri Staessens2026-02-185-21/+22
| | | | | | | | | | | | | | | | | This replaces the single HAVE_OPENSSL_PQC/DISABLE_PQC with per-algorithm CMake variables (ML-KEM, ML-DSA, SLH-DSA), gated by the OpenSSL versions: ML-KEM and ML-DSA require >= 3.4, SLH-DSA >= 3.5. SLH-DSA was already working, but now added explicit authentication tests for it with a full certificate chain (root CA, intermediate CA, server) to show full support. Rename PQC test files and cert headers to use algorithm-specific names (ml_kem, ml_dsa, slh_dsa) and move cert headers to include/test/certs/. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Check for PQC support when loading configDimitri Staessens2026-02-181-3/+9
| | | | | | | | The IRMd will now report a PQC algorithm in the enc.conf file if it is not supported, instead of failing on KEM key generation. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Fix client-side encryption requestDimitri Staessens2026-02-182-3/+84
| | | | | | | | | | | | | When the server had no cipher configured, sk->nid was set to NID_undef before negotiation and never updated, causing the response header to encode NID_undef as the cipher — even though negotiate_kex() correctly populated kcfg.c.nid from the client's request. Adds a test for the KEM case where the client request encryption with nothing specified server-side. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Clean up key exchange debug logsDimitri Staessens2026-02-183-17/+15
| | | | | | | | This cleans up a few debug logs related to encryption to not show KEM info for non-KEM algorithms. Also removes refcount logs for the PUP. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Add strength-based crypto negotiationDimitri Staessens2026-02-184-68/+349
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Each side's configured cipher, KDF, and KEX algorithm now represents a minimum security floor ("at least this strong"). Cipher and KDF use strongest-wins: the server compares ranks and selects the stronger of client vs server config. The negotiated values are sent in the response header. The client verifies the server's response meets its own minimum, which prevents downgrade attacks on the wire. KEX uses a minimum-floor check: the server extracts the client's algorithm from its public key and rejects if it ranks below the server's configured algorithm. A server configured with ML-KEM will reject all classical algorithms. Special case: for client-encap KEM, the client has already derived its key using its KDF, so the server must use the same KDF and can only reject if it is too weak. The supported_nids arrays are ordered weakest to strongest and serve as the single source of truth for ranking. Cipher ranking (weakest to strongest): aes-128-ctr, aes-192-ctr, aes-256-ctr, aes-128-gcm, aes-192-gcm, aes-256-gcm, chacha20-poly1305 KDF ranking (weakest to strongest): blake2s256, sha256, sha3-256, sha384, sha3-384, blake2b512, sha512, sha3-512 KEX ranking (weakest to strongest): ffdhe2048, prime256v1, X25519, ffdhe3072, secp384r1, ffdhe4096, X448, secp521r1, ML-KEM-512, ML-KEM-768, ML-KEM-1024, X25519MLKEM768, X448MLKEM1024 Negotiation outcomes: strong srv cipher + weak cli cipher -> use strongest weak srv cipher + strong cli cipher -> use strongest srv encryption + cli none -> server rejects srv none + cli encryption -> use client's strong srv KEX + weak cli KEX -> server rejects weak srv KEX + strong cli KEX -> succeeds wire tamper to weaker cipher -> client rejects Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Fix SSM PUP creation on OS XDimitri Staessens2026-02-131-4/+9
| | | | | | | | | | OS X doesn't support chmod on shm files after creation. Since we already set the mode at creation, that call was redundant. Fixed the getpeereid() function was not accessible because of the guards. Fixed some differences between macOS and Linux with gid_t vs int usage. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* build: Refactor CMake back to in-tree CMakeListsDimitri Staessens2026-02-133-68/+98
| | | | | | | | | | | | | | | | | | | | | | This moves the build definitions back to src/ subdirectories (CMakeLists.txt per component). Configuration and dependencies are kept out of tree. Configuration options are bundled into cmake/config/ modules. Dependencies are grouped by component (system/, crypt/, eth/, coverage/, etc.). It now consistently uses target-based commands (target_include_directories, target_link_libraries) instead of global include_directories(). Proper PRIVATE/PUBLIC visibility for executable link libraries. CONFIG_OUROBOROS_DEBUG now properly set based on being a valid debug config (not just checking the string name). It also adds OuroborosTargets export for find_package() support and CMake package config files (OuroborosConfig.cmake) for easier integration with CMake projects. The build logic now follows more idiomatic CMake practices with configuration separated from target definitions. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Add per-user packet poolsDimitri Staessens2026-02-1312-46/+375
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | The IRMd will now check the user UID and GID for privileged access, avoiding unprivileged users being able to disrupt all IPC (e.g. by shm_open the single pool and corrupting its metadata). Non-privileged users are now limited to a PUP (per-user pool) for sending/receiving packets. It is still created by the IRMd, but owned by the user (uid) with 600 permissions. It does not add additional copies for local IPC between their own processes (i.e. over the local IPCP), but packets between processes owned by a different user or destined over the network (other IPCPs) will incur a copy when crossing the PUP / PUP or the PUP / GSPP boundary. Privileged users and users in the ouroboros group still have direct access to the GSPP (globally shared private pool) for packet transfer that will avoid additional copies when processing packets between processes owned by different users and to the network. This aligns the security model with UNIX trust domains defined by UID and GID by leveraging file permission on the pools in shared memory. ┌─────────────────────────────────────────────────────────────┐ │ Source Pool │ Dest Pool │ Operation │ Copies │ ├─────────────────────────────────────────────────────────────┤ │ GSPP │ GSPP │ Zero-copy │ 0 │ │ PUP.uid │ PUP.uid │ Zero-copy │ 0 │ │ PUP.uid1 │ PUP.uid2 │ memcpy() │ 1 │ │ PUP.uid │ GSPP │ memcpy() │ 1 │ │ GSPP │ PUP.uid │ memcpy() │ 1 │ └─────────────────────────────────────────────────────────────┘ This also renames the struct ai ("application instance") in dev.c to struct proc (process). Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Fix OpenSSL includes and explicit_bzero on OSXDimitri Staessens2026-02-132-4/+4
| | | | | | | | | | | | | | | | | The include headers and NIDs are different on macOS X. It also doesn't have explicit_bzero. The crypt.h includes are now guarded to work on OS X (trying to avoid the includes by defining the OpenSSL mac header guard led to a whole list of other issues). The explicit zero'ing of buffers temporarily holding secrets has now been abstracted in a crypt_secure_clear() function defaulting to OpenSSL_cleanse, explicit_bzero (if present) or a best-effort option using a volatile pointer. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Replace rdrbuff with a proper slab allocatorDimitri Staessens2026-01-267-39/+42
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | This is a first step towards the Secure Shared Memory (SSM) infrastructure for Ouroboros, which will allow proper resource separation for non-privileged processes. This replaces the rdrbuff (random-deletion ring buffer) PoC allocator with a sharded slab allocator for the packet buffer pool to avoid the head-of-line blocking behaviour of the rdrb and reduce lock contention in multi-process scenarios. Each size class contains multiple independent shards, allowing parallel allocations without blocking. - Configurable shard count per size class (default: 4, set via SSM_POOL_SHARDS in CMake). The configured number of blocks are spread over the number of shards. As an example: SSM_POOL_512_BLOCKS = 768 blocks total These 768 blocks are shared among 4 shards (not 768 × 4 = 3072 blocks) - Lazy block distribution: all blocks initially reside in shard 0 and naturally migrate to process-local shards upon first allocation and subsequent free operations - Fallback with work stealing: processes attempt allocation from their local shard (pid % SSM_POOL_SHARDS) first, then steal from other shards if local is exhausted, eliminating fragmentation while maintaining low contention - Round-robin condvar signaling: blocking allocations cycle through all shard condition variables to ensure fairness - Blocks freed to allocator's shard: uses allocator_pid to determine target shard, enabling natural load balancing as process allocation patterns stabilize over time Maintains existing robust mutex semantics including EOWNERDEAD handling for dead process recovery. Internal structures exposed in ssm.h for testing purposes. Adds some tests (pool_test, pool_sharding_test.c. etc) verifying lazy distribution, migration, fallback stealing, and multiprocess behavior. Updates the ring buffer (rbuff) to use relaxed/acquire/release ordering on atomic indices. The ring buffer requires the (robust) mutex to ensure cross-structure synchronization between pool buffer writes and ring buffer index publication. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Fix cleanup on timeouts after reg_testsDimitri Staessens2026-01-261-6/+29
| | | | | | | | The reg_tests didn't clean up on timeouts, now fixed. Increased timeouts to deal with slower CI/CD builds. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Remove duplicate OAP testDimitri Staessens2026-01-232-899/+4
| | | | | | | | | | There was a previous version of the authentication tests lingering in the irmd/test folder (it was moved to irmd/oap/tests/). Also enables the disabling of the Ouroboros logging in the oap tests. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Fix registry testsDimitri Staessens2026-01-232-3/+10
| | | | | | | | | | | | | | | | | | | | | | | | Noticed a test fail with SEGV on codeberg: 23/23 Test #23: irmd/reg/reg_test ................ Subprocess aborted***Exception: 1.11 sec Wait accept did not return a flow id: -110. test_wait_accepting_success failed. Root cause was a missing unbind_process call in the cleanup. The test can now wait for 10 seconds, and will take less on fast systems: 23/23 Test #23: irmd/reg/reg_test ................ Passed 0.01 sec The test_wait_ipcp_boot_fail was also wrong, a failed IPCP returns/sets state to IPCP_NULL. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Fix memleak in oap testsDimitri Staessens2026-01-231-6/+10
| | | | | | | | | | | | The test_oap_piggyback_data was not cleaning up the passed data correctly. Also, a FILE * was not properly closed in the openssl load_pubkey_raw_file_to_der() wrapper. Refactored some fail paths to make them easier to read. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Call mlock() on the shared memory buffersDimitri Staessens2026-01-192-0/+9
| | | | | | | | | This prevents them from swapping to disk and killing performance. It also enhances security a little bit by reducing the risk of sensitive (even encrypted) data being paged out and captured. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Add post-quantum cryptography supportDimitri Staessens2026-01-1928-1067/+5215
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | This adds initial support for runtime-configurable encryption and post-quantum Key Encapsulation Mechanisms (KEMs) and authentication (ML-DSA). Supported key exchange algorithms: ECDH: prime256v1, secp384r1, secp521r1, X25519, X448 Finite Field DH: ffdhe2048, ffdhe3072, ffdhe4096 ML-KEM (FIPS 203): ML-KEM-512, ML-KEM-768, ML-KEM-1024 Hybrid KEMs: X25519MLKEM768, X448MLKEM1024 Supported ciphers: AEAD: aes-128-gcm, aes-192-gcm, aes-256-gcm, chacha20-poly1305 CTR: aes-128-ctr, aes-192-ctr, aes-256-ctr Supported HKDFs: sha256, sha384, sha512, sha3-256, sha3-384, sha3-512, blake2b512, blake2s256 Supported Digests for DSA: sha256, sha384, sha512, sha3-256, sha3-384, sha3-512, blake2b512, blake2s256 PQC support requires OpenSSL 3.4.0+ and is detected automatically via CMake. A DISABLE_PQC option allows building without PQC even when available. KEMs differ from traditional DH in that they require asymmetric roles: one party encapsulates to the other's public key. This creates a coordination problem during simultaneous reconnection attempts. The kem_mode configuration parameter resolves this by pre-assigning roles: kem_mode=server # Server encapsulates (1-RTT, full forward secrecy) kem_mode=client # Client encapsulates (0-RTT, cached server key) The enc.conf file format supports: kex=<algorithm> # Key exchange algorithm cipher=<algorithm> # Symmetric cipher kdf=<KDF> # Key derivation function digest=<digest> # Digest for DSA kem_mode=<mode> # Server (default) or client none # Disable encryption The OAP protocol is extended to negotiate algorithms and exchange KEX data. All KEX messages are signed using existing authentication infrastructure for integrity and replay protection. Tests are split into base and _pqc variants to handle conditional PQC compilation (kex_test.c/kex_test_pqc.c, oap_test.c/oap_test_pqc.c). Bumped minimum required OpenSSL version for encryption to 3.0 (required for HKDF API). 1.1.1 is long time EOL. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* build: Add option to toggle Ouroboros logging in testsDimitri Staessens2026-01-191-0/+2
| | | | | | | | | | | | | | | | | | | | | | | | | | The test output is a bit polluted with logs originating fomr the logging system, e.g.: 23: test_bind_prog started. 23: ==16636== reg/name(DB): Add prog reg_test to name testname. 23: ==16636== reg(EE): Removing from names. 23: test_bind_prog succeeded. This adds a flag DISABLE_TEST_LOGGING that suppresses log_* output in tests to keep them clean: 23: test_bind_prog started. 23: test_bind_prog succeeded. The status is printed in CMake output: -- Ouroboros logging in test output disabled -- Ouroboros logging in test output enabled By default the flag is ON (clean test output). Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* build: Refactor CMake modulesDimitri Staessens2026-01-075-133/+12
| | | | | | | | | This moves the CMake build logic out of the source tree and splits it up into a more modular form. The tests now have a CMakeLists.txt file in their respective source directory. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* build: Copy empty config file on installThijs Paelman2025-12-221-2/+7
| | | | | | | | A valid but empty config file is placed at install. This does not overwrite old config files. It is also not removed on uninstall. Signed-off-by: Thijs Paelman <thijs@ouroboros.rocks> Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
* build: Extend CMake variable documentationThijs Paelman2025-11-241-1/+1
| | | | | | | | | Be clear that OUROBOROS_CONFIG_DIR should be an absolute path, since the systemd service file depends on this, and most defined variables in the code (security directory and derived). Signed-off-by: Thijs Paelman <thijs@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* build: Add build_tests targetThijs Paelman2025-11-212-2/+2
| | | | | | | | | This intermediate target only builds all the tests, it doesn't run them. It is added for clarifying the different steps involved in testing. The 'check' target still builds AND runs the tests as before. Signed-off-by: Thijs Paelman <thijs@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* build: Include tests in 'all' targetThijs Paelman2025-11-072-2/+2
| | | | | | | | | | | | When BUILD_TESTING=ON, then the default 'all' target will now build also the tests. This behaviour could be controlled by an extra variable (see https://stackoverflow.com/a/42235335), but this increases complexity without many benefits. Signed-off-by: Thijs Paelman <thijs@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* build: Only add tests when BUILD_TESTING is ONThijs Paelman2025-11-072-2/+6
| | | | | | | By default, BUILD_TESTING = ON due to the inclusion of the CTest module. Signed-off-by: Thijs Paelman <thijs@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Fix the flow_join operationDimitri Staessens2025-09-241-1/+5
| | | | | | | | | This fixes a regression in the code path for joining a broadcast Layer. It deprecates the qos parameter on flow_join, as the QoS is implied by the broadcast Layer itself. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Move encryption control from QoS to nameDimitri Staessens2025-09-107-52/+96
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | This removes the flow encryption option (cypher_s) from the qosspec. The configuration file is configured in the security options (default /etc/ouroboros/security/). For this poc, encryption can be disabled client or server side by putting an enc.cfg file. If that file is present in the client folder, the client will require encryption. If that file is present on the server side, the server will require encryption and reject non-encrypted flows. Encryption is now configured outside of any application control. Example: /etc/ouroboros/security/client/oping/enc.cfg exists: irmd(II): Encryption enabled for oping. irmd(DB): File /etc/ouroboros/security/client/oping/crt.pem does not exist. irmd(II): No security info for oping. irmd(DB): Generated ephemeral keys for 87474. irmd/oap(PP): OAP_HDR [caf203681d997941 @ 2025-09-02 17:08:05 (UTC) ] --> irmd/oap(PP): Certificate: <none> irmd/oap(PP): Ephemeral Public Key: [91 bytes] irmd/oap(PP): Data: <none> irmd/oap(PP): Signature: <none> Example: /etc/ouroboros/security/client/oping/enc.cfg does not exist: irmd(II): Allocating flow for 87506 to oping. irmd(DB): File /etc/ouroboros/security/client/oping/enc.cfg does not exist. irmd(DB): File /etc/ouroboros/security/client/oping/crt.pem does not exist. irmd(II): No security info for oping. irmd/oap(PP): OAP_HDR [e84bb9d7c3d9c002 @ 2025-09-02 17:08:30 (UTC) ] --> irmd/oap(PP): Certificate: <none> irmd/oap(PP): Ephemeral Public Key: <none> irmd/oap(PP): Data: <none> irmd/oap(PP): Signature: <none> Example: /etc/ouroboros/security/server/oping/enc.cfg exists: irmd(II): Flow request arrived for oping. irmd(DB): IPCP 88112 accepting flow 7 for oping. irmd(II): Encryption enabled for oping. irmd(DB): File /etc/ouroboros/security/server/oping/crt.pem does not exist. irmd(II): No security info for oping. irmd/oap(PP): OAP_HDR [3c717b3f31dff8df @ 2025-09-02 17:13:06 (UTC) ] <-- irmd/oap(PP): Certificate: <none> irmd/oap(PP): Ephemeral Public Key: <none> irmd/oap(PP): Data: <none> irmd/oap(PP): Signature: <none> irmd(WW): Encryption required but no key provided. The server side will pass the ECRYPT to the client: $ oping -l Ouroboros ping server started. Failed to accept flow: -1008 $ oping -n oping -c 1 Failed to allocate flow: -1008. Encryption on flows can now be changed at runtime without needing to touch/reconfigure/restart the process. Note: The ECRYPT result is passed on via the flow allocator responses through the IPCP (discovered/fixed some endianness issues), but the reason for rejecting the flow can be considered N+1 information... We may move that information up into the OAP header at some point. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* ipcpd: Add ipcpd over UDP/IPv6Dimitri Staessens2025-09-103-14/+75
| | | | | | | | | | This adds an IPCP that runs over UDP/IPv6. It's structured like the eth-dix and eth-llc in that it builds two separate binaries: ipcpd-udp4 and ipcpd-udp6. The IRM CLI is backwards compatible in that type 'udp' will resolve to type 'udp4'. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* irmd: Fix client certificatesDimitri Staessens2025-09-101-19/+21
| | | | | | | | | The server-side check of client certificates was checking the server side certificate instead. Now also allows client certificates with a different name than the application. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* ipcpd: Fix printf formatting in tests on raspbianDimitri Staessens2025-08-231-1/+1
| | | | | | Fixes some printf formatting, which was failing the build on raspbian. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
* ipcpd: Fix request handling at shutdownDimitri Staessens2025-08-236-37/+45
| | | | | | | | | | | | | | | | | The IPCP states were not entirely correct causing some operations to be serviced during shutdown. This caused some use-after-free in the pff. States in the IPCP are now correctly set. IRMd states updated to the same strategy. The IRMd registry tracks if the IPCP was ENROLLED or BOOTSTRAPPED, the IPCP just goes to OPERATIONAL. IPCP state diagram:: NULL -> init() -> INIT -> start() -> BOOT -> bootstrap/enroll() -> OPERATIONAL -> shutdown() -> SHUTDOWN -> stop_components() -> BOOT -> stop() -> INIT -> fini() -> NULL Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
* irmd: Fix memleak in security path configurationDimitri Staessens2025-08-231-70/+39
| | | | | | | | Moved the conversion and check to a small function to avoid code duplication. Also moved the checks to a small function in the irm name create tool. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
* irmd: Add flow authenticationDimitri Staessens2025-08-1813-231/+836
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | This adds initial implementation of peer authentication as part of flow allocation. If credentials are not provided, this will be accepted and logged as info that the flow is not authenticated. Certificates and keys are passed as .pem files. The key file should not be encrypted, else the IRMd will open a prompt for the password. The default location for these .pem files is in /etc/ouroboros/security. It is strongly recommended to make this directory only accessible to root. ├── security │ ├── cacert │ │ └── ca.root.o7s.crt.pem │ ├── client │ │ ├── <name> │ │ | ├── crt.pem │ │ | └── key.pem │ │ └── <name> | | ├──... | | │ ├── server │ │ ├── <name> │ │ | ├── crt.pem │ │ | └── key.pem │ │ └── <name> | | ├── ... | | │ └── untrusted │ └── sign.root.o7s.crt.pem Trusted root CA certificates go in the /cacert directory, untrusted certificates for signature verification go in the /untrusted directory. The IRMd will load these certificates at boot. The IRMd will look for certificates in the /client and /server directories. For each name a subdirectory can be added and the credentials in that directory are used to sign the OAP header for flows at flow_alloc() on the client side and flow_accept() on the server side. These defaults can be changed at build time using the following variables (in alphabetical order): OUROBOROS_CA_CRT_DIR /etc/ouroboros/security/cacert OUROBOROS_CLI_CRT_DIR /etc/ouroboros/security/client OUROBOROS_SECURITY_DIR /etc/ouroboros/security OUROBOROS_SRV_CRT_DIR /etc/ouroboros/security/server OUROBOROS_UNTRUSTED_DIR /etc/ouroboros/security/untrusted The directories for the names can also be configured at IRMd boot using the configuraton file and at runtime when a name is created using the "irm name create" CLI tool. The user needs to have permissions to access the keyfile and certificate when specifying the paths with the "irm name create" CLI tool. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
* ipcpd: Configure link-state at bootstrapDimitri Staessens2025-08-181-10/+51
| | | | | | | | | The link-state component had some values defined in the source such as link-state advertisement interval, link timeout period and the PFF recalculation time. These can now be configured from the config file or via "irm ipcp bootstrap" on the command line. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
* irmd: Fix bad assertion in registryDimitri Staessens2025-08-151-2/+0
| | | | | | | | The registry wait_flow_accepted asserts the flow state to be pending, but this is not always true as the thread that accepts can be faster in execution than the one waiting for it. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
* lib: Fix memory leaks in testsDimitri Staessens2025-08-151-0/+2
| | | | | | Fixes memory leaks in the tests that had escaped detection. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>
* irmd: Resolve name when flow request arrivesDimitri Staessens2025-08-155-94/+84
| | | | | | | | | | | This makes the IRMd a bit simpler, and we only need to do the lookup to resolve the name for a hash on the server side only once. The logging is also symmetric now: irmd(II): Allocating flow for 93317 to unicast.1. irmd(II): Flow request arrived for unicast.1. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks>