summaryrefslogtreecommitdiff
path: root/src/lib
Commit message (Collapse)AuthorAgeFilesLines
* lib: Allow up to two TLPs per recovery episodeDimitri Staessens37 hours1-8/+22
| | | | | | | | | | | RFC 8985 §7.3 permits up to two tail loss probes before falling back to RTO. Previously FRCP allowed exactly one TLP per episode, making tail-loss recovery dependent on a successful first probe; if that probe was lost the sender fell straight to slow RTO with exponential backoff. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Further align FRCP with TCP RFCsDimitri Staessens37 hours1-43/+89
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Only the HoL slot retransmits on RTO; non-HoL slots defer and rely on SACK/RACK fast-rxm for recovery. Matches RFC 6298 §5.4 + RFC 8985 §3 and Linux tcp_retransmit_skb(head, 1). Eliminates the spurious-RTO storm where ~50-66% of retransmits arrived as duplicates at the peer. Co-fixes for three latent state-machine bugs that the previous spurious-retx mask was hiding: - recovery_enter: seal recovery_high at the false→true edge only (RFC 6582 §3.2). Previously extended on every gap-SACK, which trapped the sender in NewReno indefinitely once any cum-ACK fell behind the moving recovery_high. - rtt_sample_eligible: drop the in_recovery super-gate. Karn at the per-slot SND_RTX|SND_TLP level is already correct (matches Linux). - rxm_due defer interval: use base RTO, not rto<<rto_mul. Inheriting HoL's backoff parked deferred slots tens of seconds in the future. Bring RACK reorder-window scaling into RFC 8985 §7.2 compliance: reo_wnd_mult widens at most once per RTT, gated via a srtt-elapsed check in reo_wnd_on_dsack. Stat refactor for clearer attribution: rename rxm_snd → rxm_rto (now RTO-driven sends only), add rxm_nack and rxm_due_defer, split rxm_rcv into total FRCT_RXM arrivals and rxm_dup_rcv (duplicates). Expose rx/tx ring queue depths as RIB stats. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Set a timeout on FRCT control packetsDimitri Staessens37 hours2-17/+130
| | | | | | | | | | | | | Time out frct_tx for control packets at 250us so a full tx ring cannot stall the timer wheel (and with it KA, TLP, RXM fires). DATA frames (fresh, RXM, TLP, FIN) keep blocking - dropping them would lose recovery progress. Add inact_drop, drf_rebase, rq_released, tlp_snd, sdu_snd_alloc, sdu_snd_tx, sdu_sole, rxm_tx_dead, and per-type tx_drop counters. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Add tail loss probe (TLP) to FRCPDimitri Staessens37 hours1-21/+162
| | | | | | | | | | | | | | | | | | | | | | | | The previous bugfixes that fixed a false-positive inactivity timer check unmasked the need for the tail loss probe (TLP) to quickly recover from loss of the HoL. When the sender finishes its app data with packets still inflight, RACK fast-rxm cannot fire (no ACK arrives), pre-DRF NACK is gated on rcv_cr.inact, and rto_mul climbs into seconds before the next RXM fire — recv-side deadline expires first. This adds the RFC 8985 §7.2 TLP: PTO = 2*SRTT + max_ack_delay (t_a), capped by the HoL slot's current RTO deadline. Probe fires from frcti_snd and after frcti_ack_rcv; per §7.3 at most one TLP per recovery episode, tracked via tlp_high_seq. On fire, re-emit HoL via fast_rxm_send and hand off to RTO — no PTO self re-arm. A new SND_TLP slot flag is Karn-skipped for RTT sampling, cleared by RTO retransmit or NACK-driven fast-rxm, and clears rto_mul on its TLP-ACK (rto_mul carries no CC meaning in FRCP). Also caps MAX_RTO_MUL at 8 instead of 20. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Fix underrun in activity timerDimitri Staessens37 hours1-22/+50
| | | | | | | | | | | The inactivity timers are updated under rdlock with atomics on the time stamp. Inactivity is measured by comparison between the act stamp against the thread's own current time (now_ns). A different thread could already have updated the act stamp, causing undderun in the signed comparison and causing a false-positive on the inactive test. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Skip DRF rebase on same-epoch retransmitDimitri Staessens4 days1-2/+24
| | | | | | | | | | | | | | | | | | | | On some stalls the receiver can go inactive and NACK-driven HoL retransmits and brief snd-window drains can deliver a DRF-flagged packet that's still part of the current receive epoch (rxm_pkt_prepare preserves the original flags). This treats in-window DRF and RXM-flagged DRF arrivals as same-epoch once the window is seeded and skip the release_rq + lwe/rwe rebase that would otherwise wipe valid OOO fragments from rq[]. Bootstrap (lwe == rwe) still rebases unconditionally so a NACK-driven retransmit of a lost initial DRF can seed the receiver. Harden seqno_rotate to redraw the ISN until it falls outside the peer's current rcv window, closing the ~RQ_SIZE / 2^32 collision where a true new epoch would be misclassified as same-epoch. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Free secure memory on process exitDimitri Staessens4 days2-4/+9
| | | | | | | | | | | There was a missing crypt_secure_malloc_fini() in the process init/fini path. Also fixes a 0 return from OpenSSL RAND_bytes() being interpreted as succes instead of failure. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Update FRCP implementationDimitri Staessens4 days9-1363/+4188
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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>
* lib: Use push/pop for ssm_pk_buff opsDimitri Staessens4 days4-23/+23
| | | | | | | | | Renames the allocation for head/tail to push/pop instead of alloc/release as it's simpler and shorter. Took this approach insted of adopting the kernel's push/pull/put/trim. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* build: Remove deprecated cmake optionsDimitri Staessens4 days2-2/+0
| | | | | | | RXM_BUFFER_ON_HEAP and SSM_POOL_BLOCKS were no longer used. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Use const for ssm_pk_buff gettersDimitri Staessens4 days1-6/+6
| | | | | | | | | Mark ssm_pk_buff_get_off, _head, _tail, and _len as taking a const struct ssm_pk_buff *. Cast through the flex array in _head and _tail since the buffer view they return remains mutable. 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 days2-0/+3
| | | | | | | | | 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>
* lib: Add generic hierarchical timing wheelDimitri Staessens4 days4-0/+972
| | | | | | | | | Introduce a generic 3-level / 256-slot deadline-ordered callback queue (1 ms / 16 ms / 256 ms per-slot resolution at levels 0/1/2). Will replace the existing timerwheel.c. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Rename PROG_* config to PROC_*Dimitri Staessens4 days4-23/+23
| | | | | | | | | Per-process flow / fd / fqueue limits are properties of a process, not a program; align the naming. Mechanical rename of PROG_MAX_FLOWS, PROG_RES_FDS, and PROG_MAX_FQUEUES to PROC_*. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Use tpm.h for TPM testDimitri Staessens4 days1-1/+1
| | | | | | | | The threadpool manager (TPM) test unnecessarily included the source instead of the header. 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-2/+2
| | | | | Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* include: Centralise atomic helpers in atomics.hDimitri Staessens4 days1-20/+1
| | | | | | | | Moves the atomics macros that were defined between eth and ssm_pool to their own header. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Fix pool_sharding_testDimitri Staessens4 days3-71/+39
| | | | | | | | The test was not correctly taking the correct size class. Moved the select_size_class to the common header so tests can use it. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Drain rbuff before closeDimitri Staessens4 days1-4/+48
| | | | | | | | | | | | | | | | | ssm_rbuff_close used to unmap the SHM page immediately, leaving any in-flight peer-process thread that was inside pthread_mutex_lock or pthread_cond_wait on the SHM-resident sync primitives reading freed memory. Adds an n_users counter, bumped on entry and dropped on exit of every function that touches the mutex / cond vars (write, write_b, read, read_b, fini), and have ssm_rbuff_close poll-spin until the counter drains before tearing down. ssm_rbuff_read now re-checks IS_EMPTY after taking the mutex, plugging a TOCTOU where two readers could both pass a lock-free fast path and the loser would read a stale TAIL. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Drop RIB lock before component callbackDimitri Staessens4 days1-6/+12
| | | | | | | | The RIB lock only needs to protect operations on the components list. This avoids holding the lock on longer RIB reads. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Rename ssm_pk_buff_get_idx to ssm_pk_buff_get_offDimitri Staessens2026-05-066-43/+44
| | | | | | | | | | The shared memory pool is now offset based instead of block index-based like the old shm_rdrbuff allocator. This renames the API more consistently. Also changes variables names to off instead of idx for consistency. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Fix pool_copy_spb and np1_flow_writeDimitri Staessens2026-05-061-15/+19
| | | | | | | | The pool_copy_spb function was consuming the packet buffer on error, causing double free errors. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Fix and clean up crc implementationDimitri Staessens2026-05-063-49/+38
| | | | | | | | | Fixes detection of PMULL on aarch64 without crypto extensions. Adds a crc64_nvme_step helper function in CRC64 to avoid code duplication and cleans up the comments. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Move CRC implementations to a subfolderDimitri Staessens2026-05-0611-8/+26
| | | | | Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Add CRC-8 and CRC-16 checksumsDimitri Staessens2026-05-068-0/+358
| | | | | | | These checksum will be handy for header checksums. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Add CRC-64/NVMe checksumDimitri Staessens2026-05-066-0/+545
| | | | | | | | | | | | | | | | | Add CRC-64/NVMe implementation with compile-time hardware backend selection: x86 PCLMUL+SSE4.1 fold-by-16 (HAVE_PCLMUL) aarch64 PMULL fold-by-16 when (HAVE_PMULL) and a byte-table fallback. It's added as HASH_CRC64 to enum hash_algo (in the internal-use-only section after HASH_MD5). Both mem_hash() and hash_len() early-return for HASH_CRC64 because libgcrypt has no CRC-64/NVMe. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* cmake: Add CPU feature detection helperDimitri Staessens2026-05-061-0/+2
| | | | | | | | | | | | Add cmake/utils/CPUUtils.cmake providing detect_cpu_feature() plus detect_pclmul() and detect_pmull() that compile-test for x86 PCLMULQDQ+SSE4.1 and aarch64 FEAT_PMULL respectively. This will be useful for hardware accelerated CRC64/NVMe integrity checks. 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-144-37/+37
| | | | | | | | | | | 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>
* lib: Fix initialization when listing namesDimitri Staessens2026-03-142-2/+2
| | | | | | | | Now uses calloc to zero the previously uninitialized security path fields. Also fixes a check in protobuf.c 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-143-0/+13
| | | | | | | | | | | | | 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/+55
| | | | | | | | | 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>
* lib: Fix chown of GSPP to ouroboros groupDimitri Staessens2026-03-141-1/+3
| | | | | | | | The Global Shared Packet Pool (GSPP) was not correctly chowned to the ouroboros group. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Fix ssm pool double freeDimitri Staessens2026-02-222-2/+5
| | | | | | | | | | Remove double-free in ssm_pool_destroy — ssm_pool_close already frees the pool. The pool sharding test had a free spbs/ptrs on partial malloc failure. Now initializes children array to -1 to prevent reading uninitialized values. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Fix invalid malloc pointer typeDimitri Staessens2026-02-221-1/+1
| | | | | | | | | The static analyzer complained about the struct in6_addr malloc being converted to uint8_t *. Fixed by casting IN6_LEN to a size_t numeric value instead of the direct sizeof. 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-182-79/+0
| | | | | | | | | | | | | 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-1852-52/+52
| | | | | 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-188-16/+386
| | | | | | | | | | | | | | | | | 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: Add strength-based crypto negotiationDimitri Staessens2026-02-182-12/+60
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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-133-23/+53
| | | | | | | | | | 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>
* lib: Check fchown result when creating ssm_poolDimitri Staessens2026-02-134-53/+39
| | | | | | | | | | The result of fchown and fchmod weren't checked, causing some compilers to complain. Updated the test to create a PUP instead of "non-root" version of the GSPP. Removed the environment variable for the test suffix as this is not needed. 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-134-30/+167
| | | | | | | | | | | | | | | | | | | | | | 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-1313-455/+684
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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-136-4/+35
| | | | | | | | | | | | | | | | | 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: Fix crypt test and secure memory initializationDimitri Staessens2026-01-301-2/+7
| | | | | | | | | The crypt_test had a HAVE_OPENSSL guard missing and was trying to execute tests that required OpenSSL without it being installed. The SECMEM values need to be set by CMake without OpenSSL installed. 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-2618-1118/+3902
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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>
* lib: Speed up key rotation testsDimitri Staessens2026-01-265-19/+21
| | | | | | | | | | The tests were not correct as the library was compiled with the default 1 << 20 epoch. Added a parametere to the sk configuration that specifies the epoch size. Set to 1 << KEY_ROTATION_BIT in dev.c, but lowered to 7 in unit tests. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Fix authentication testDimitri Staessens2026-01-261-98/+0
| | | | | | | | The hardcoded certificates were re-introduced in ea52d5275, breaking the build. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Add automatic key rotation for encryptionDimitri Staessens2026-01-234-39/+385
| | | | | | | | | | | | | | | | | | | | | | | | | | Implement forward-secret key rotation using HKDF key derivation. The operation is based on QUIC RFC 9001 and wireguard. Keys rotate every 2^KEY_ROTATION_BIT packets, with the current phase (P) signaled via controlling a bit in the IV (bit 7, first bit on the wire). Default 20 (1M packets). The wire format, after the DT header is: [ P | random IV ][ encrypted blob ][ AEAD tag ] Works with and without retransmission, and the FRCT header is fully contained in the encrypted blob if used. The receiver detects phase changes and rotates accordingly, keeping the previous key valid during a grace period. This handles packet reordering in unreliable flows: the 3/4 period protection window prevents premature rotation when late packets arrive, while the 1/2 period grace window ensures the old key remains available for decryption. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Fix allocation of IV and tagsDimitri Staessens2026-01-234-7/+63
| | | | | | | | | | | The packet buffer was allocating a fixed header for the IV, but did not account for the tag at all (remnant of the old hardcoded CBC mode-only proof-of-concept). Never ran into issues because we always reserved ample space. But it now properly reserves the correct space for IV and tag. Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>
* lib: Fix getting text certificatesDimitri Staessens2026-01-232-7/+108
| | | | | | | | | | | | | | | The openssl_crt_str function was using BIO_get_mem_data() but this is not guaranteed to be NULL-terminated, causing buffer overruns. This was the root cause of ASan tests with certificates running for minutes and eventually getting killed on the CI/CD pipeline: Start 1: lib/auth_test 1/26 Test #1: lib/auth_test ......................***Skipped 312.75 sec Start 16: irmd/oap/oap_test 16/26 Test #16: irmd/oap/oap_test ..................***Skipped 345.87 sec Signed-off-by: Dimitri Staessens <dimitri@ouroboros.rocks> Signed-off-by: Sander Vrijders <sander@ouroboros.rocks>