diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dd021f..c441649 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,15 +14,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Docker sidecar deployment for containerized services - Comprehensive node and transport statistics via control socket - fipstop TUI monitoring tool with smoothed metrics and quality indices +- fipstop peers display: transport type, direction, and tree roles +- Estimated mesh size from bloom filter cardinality - ECN congestion signaling and transport congestion detection - Persistent identity with key file management (`fipsctl keygen`) - Periodic Noise rekey with fresh DH for forward secrecy (FMP + FSP) - Host-to-npub static mapping: resolve `hostname.fips` via host map populated from peer config aliases and `/etc/fips/hosts` file - DNS responder auto-reloads hosts file on modification (no restart needed) +- Debian/Ubuntu `.deb` packaging via cargo-deb - Systemd service packaging with tarball installer - Build version metadata: git commit hash, dirty flag, and target triple embedded in all binaries via `--version` +- Local CI runner script (`testing/ci-local.sh`) +- TCP transport node-level integration tests +- CI: expanded integration matrix, nextest JUnit reporting, workflow_dispatch - CHANGELOG.md following Keep a Changelog format ### Fixed @@ -32,6 +38,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Ethernet AEAD decryption failures caused by minimum-frame padding - Link-dead detection skipping peers that never send data - FMP version check added to TCP stream reader +- Control socket path mismatch between daemon and clients +- fips-dns.service pulling in systemd-resolved and hanging on missing fips0 ### Changed diff --git a/README.md b/README.md index cdd2e02..5e4d115 100644 --- a/README.md +++ b/README.md @@ -42,11 +42,11 @@ endpoints. - **Multi-transport** — UDP, TCP, and Ethernet today; designed for Bluetooth, serial, radio, and Tor - **Noise encryption** — hop-by-hop link encryption plus independent - end-to-end session encryption + end-to-end session encryption, with periodic rekey for forward secrecy - **Nostr-native identity** — secp256k1 keypairs as node addresses, no registration or central authority - **IPv6 adaptation** — TUN interface maps npubs to fd00::/8 addresses for - unmodified IP applications + unmodified IP applications; static hostname mapping (`/etc/fips/hosts`) - **Metrics Measurement Protocol** — per-link RTT, loss, jitter, and goodput measurement - **ECN congestion signaling** — hop-by-hop CE flag relay with RFC 3168 IPv6 @@ -248,8 +248,11 @@ UDP, TCP, and Ethernet but has not been tested beyond small meshes. - Spanning tree construction with greedy coordinate routing - Bloom filter discovery for finding nodes without global state - Noise IK (link layer) and Noise XK (session layer) encryption +- Periodic Noise rekey with forward secrecy (FMP + FSP) +- Persistent node identity with key file management - IPv6 TUN adapter with DNS resolution of `.fips` names -- Per-link metrics (RTT, loss, jitter, goodput) +- Static hostname mapping (`/etc/fips/hosts`) with auto-reload +- Per-link metrics (RTT, loss, jitter, goodput) and mesh size estimation - ECN congestion signaling (hop-by-hop CE relay, IPv6 CE marking, kernel drop detection) - UDP, TCP, and Ethernet transports - Runtime inspection via `fipsctl` and `fipstop` diff --git a/docs/design/fips-configuration.md b/docs/design/fips-configuration.md index 5b1e6fa..0b06f72 100644 --- a/docs/design/fips-configuration.md +++ b/docs/design/fips-configuration.md @@ -194,6 +194,20 @@ outgoing link ETX ≥ `etx_threshold`, or kernel receive buffer drops detected o any local transport. CE is relayed hop-by-hop: once set on any hop, the flag stays set for all subsequent hops to the destination. +### Rekey (`node.rekey.*`) + +Controls periodic Noise rekey for forward secrecy. When enabled, both FMP +(link-layer IK) and FSP (session-layer XK) sessions perform fresh Diffie-Hellman +key exchanges after a time or message count threshold, whichever comes first. +A 10-second drain window keeps the old session active for decryption during +cutover. + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `node.rekey.enabled` | bool | `true` | Enable periodic Noise rekey on all links and sessions | +| `node.rekey.after_secs` | u64 | `120` | Initiate rekey after this many seconds on a session | +| `node.rekey.after_messages` | u64 | `65536` | Initiate rekey after this many messages sent on a session | + ### Session / Data Plane (`node.session.*`) Controls end-to-end session behavior and packet queuing. @@ -268,6 +282,19 @@ with the node for routing. The `dns.ttl` value should not exceed `node.cache.coord_ttl_secs` to avoid stale address mappings. +### Host Mapping + +The DNS resolver checks a host map before falling back to direct npub +resolution, enabling names like `gateway.fips` instead of `npub1...fips`. +The host map is populated from two sources: + +1. **Peer aliases** — the `alias` field on configured peers in `peers:`. +2. **Hosts file** — `/etc/fips/hosts`, one `hostname npub1...` per line. + Blank lines and `#` comments are allowed. + +The hosts file is auto-reloaded on modification (mtime change) without +restarting the daemon. Hostnames are case-insensitive. + ## Transports (`transports.*`) ### UDP (`transports.udp.*`) @@ -288,7 +315,7 @@ Requires `CAP_NET_RAW` or running as root. Linux only. |-----------|------|---------|-------------| | `interface` | string | *(required)* | Network interface name (e.g., `"eth0"`, `"enp3s0"`) | | `ethertype` | u16 | `0x2121` | EtherType | -| `mtu` | u16 | *(auto)* | Override MTU. Default: interface MTU minus 1 (for frame type prefix) | +| `mtu` | u16 | *(auto)* | Override MTU. Default: interface MTU minus 3 (for frame type + length prefix) | | `recv_buf_size` | usize | `2097152` | Socket receive buffer size in bytes (2 MB) | | `send_buf_size` | usize | `2097152` | Socket send buffer size in bytes (2 MB) | | `discovery` | bool | `true` | Listen for discovery beacons from other nodes | @@ -324,7 +351,7 @@ overhead. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| -| `transports.tcp.bind_addr` | string | *(none)* | Listen address (e.g., `"0.0.0.0:443"`). If omitted, outbound-only mode. | +| `transports.tcp.bind_addr` | string | *(none)* | Listen address (e.g., `"0.0.0.0:8443"`). If omitted, outbound-only mode. | | `transports.tcp.mtu` | u16 | `1400` | Default MTU. Per-connection MTU derived from `TCP_MAXSEG` when available. | | `transports.tcp.connect_timeout_ms` | u64 | `5000` | Outbound connect timeout in milliseconds | | `transports.tcp.nodelay` | bool | `true` | `TCP_NODELAY` (disable Nagle for low latency) | @@ -514,6 +541,10 @@ node: enabled: true # ECN congestion signaling (CE flag relay) loss_threshold: 0.05 # MMP loss rate threshold for CE marking (5%) etx_threshold: 3.0 # MMP ETX threshold for CE marking + rekey: + enabled: true # periodic Noise rekey for forward secrecy + after_secs: 120 # rekey interval (seconds) + after_messages: 65536 # rekey after N messages sent control: enabled: true socket_path: null # null = auto ($XDG_RUNTIME_DIR → /run/fips → /tmp fallback) @@ -542,7 +573,7 @@ transports: # ethernet: # uncomment to enable (requires CAP_NET_RAW) # interface: "eth0" # required: network interface name # ethertype: 0x2121 # default EtherType - # mtu: null # null = interface MTU - 1 (typically 1499) + # mtu: null # null = interface MTU - 3 (typically 1497) # recv_buf_size: 2097152 # 2 MB # send_buf_size: 2097152 # 2 MB # discovery: true # listen for beacons @@ -551,7 +582,7 @@ transports: # accept_connections: false # accept inbound handshakes # beacon_interval_secs: 30 # beacon interval (min 10) # tcp: # uncomment to enable TCP transport - # bind_addr: "0.0.0.0:443" # listen address (omit for outbound-only) + # bind_addr: "0.0.0.0:8443" # listen address (omit for outbound-only) # mtu: 1400 # default MTU # connect_timeout_ms: 5000 # outbound connect timeout # nodelay: true # TCP_NODELAY diff --git a/docs/design/fips-mesh-layer.md b/docs/design/fips-mesh-layer.md index b7b13ff..fde7ffe 100644 --- a/docs/design/fips-mesh-layer.md +++ b/docs/design/fips-mesh-layer.md @@ -573,7 +573,7 @@ an attacker sends invalid packets to elicit responses. | Path MTU tracking (SessionDatagram) | **Implemented** | | Metrics Measurement Protocol (MMP) | **Implemented** | | ECN congestion signaling (CE relay, IPv6 marking) | **Implemented** | -| Rekey with index rotation | Planned | +| Rekey with index rotation | **Implemented** | | Allowlist/blocklist | Planned | ## References diff --git a/docs/design/fips-session-layer.md b/docs/design/fips-session-layer.md index ca4dd5f..ab9e015 100644 --- a/docs/design/fips-session-layer.md +++ b/docs/design/fips-session-layer.md @@ -538,7 +538,7 @@ MMP session metrics session=npub1tdwa...84le rtt=4.3ms loss=0.6% jitter=0.2ms go | PathBroken handling | **Implemented** | | Simultaneous initiation tie-breaker | **Implemented** | | Flush coord cache on parent change | **Implemented** | -| Rekey | Planned | +| Rekey | **Implemented** | | Path MTU tracking (FMP SessionDatagram field) | **Implemented** | | Path MTU notification (end-to-end echo) | **Implemented** | diff --git a/docs/design/fips-transport-layer.md b/docs/design/fips-transport-layer.md index a7a6feb..3d583f5 100644 --- a/docs/design/fips-transport-layer.md +++ b/docs/design/fips-transport-layer.md @@ -272,16 +272,20 @@ EtherType 0x2121. SOCK_DGRAM mode lets the kernel handle Ethernet header construction and parsing — the transport deals only with payloads and MAC addresses. -A 1-byte frame type prefix disambiguates data frames (0x00) from discovery -beacons (0x01) on the receive path. This costs one byte of MTU but allows -beacons and data to share the same EtherType and socket. +Data frames use a 3-byte header: a 1-byte frame type (`0x00`) followed by +a 2-byte little-endian payload length. The length field allows the receiver +to trim Ethernet minimum-frame padding that would otherwise corrupt AEAD +verification. Beacon frames (`0x01`) use only the 1-byte type prefix +(fixed 34-byte payload). Beacons and data share the same EtherType and +socket. | Property | Value | | -------- | ----- | | EtherType | 0x2121 | | Socket type | AF_PACKET SOCK_DGRAM | -| Frame type prefix | 0x00 = data, 0x01 = beacon | -| Effective MTU | Interface MTU - 1 (typically 1499) | +| Data frame header | `[type:1][length:2 LE][payload]` | +| Beacon frame header | `[type:1][payload]` (fixed 34 bytes) | +| Effective MTU | Interface MTU - 3 (typically 1497) | | Addressing | 6-byte MAC address | | Platform | Linux only (`CAP_NET_RAW` required) | @@ -396,7 +400,7 @@ removes it from the pool and aborts its receive task. ```yaml transports: tcp: - bind_addr: "0.0.0.0:443" # Listen address (omit for outbound-only) + bind_addr: "0.0.0.0:8443" # Listen address (omit for outbound-only) mtu: 1400 # Default MTU connect_timeout_ms: 5000 # Outbound connect timeout nodelay: true # TCP_NODELAY (disable Nagle) diff --git a/docs/design/fips-wire-formats.md b/docs/design/fips-wire-formats.md index c8a7c30..3e3c0d4 100644 --- a/docs/design/fips-wire-formats.md +++ b/docs/design/fips-wire-formats.md @@ -23,11 +23,14 @@ transports (TCP, WebSocket, Tor) must delineate FIPS packets within the byte stream; the common prefix `payload_len` field provides this framing directly. -**Ethernet frame type prefix.** The Ethernet transport prepends a 1-byte -frame type before the FMP payload: `0x00` for data frames and `0x01` for -beacon (discovery) frames. This byte is consumed by the transport layer -and is not visible to FMP. The effective MTU for FMP is the interface -MTU minus one byte (typically 1499). +**Ethernet data frame header.** The Ethernet transport prepends a 3-byte +header before the FMP payload on data frames: a 1-byte frame type +(`0x00`) followed by a 2-byte little-endian payload length. The length +field allows the receiver to trim Ethernet minimum-frame padding that +would otherwise corrupt AEAD verification. Beacon frames (`0x01`) have +no length field (fixed 34-byte payload). These bytes are consumed by the +transport layer and are not visible to FMP. The effective MTU for FMP is +the interface MTU minus three bytes (typically 1497). ## Link-Layer Formats diff --git a/testing/README.md b/testing/README.md index 4a563b1..dbbaffa 100644 --- a/testing/README.md +++ b/testing/README.md @@ -17,16 +17,18 @@ configurations. | mesh | 5 | UDP | Sparse mesh, 6 links, multi-hop | | chain | 5 | UDP | Linear chain, max 4-hop paths | | mesh-public | 5+1 | UDP | Mesh with external public node | -| tcp-chain | 3 | TCP | Linear chain over TCP (port 443) | +| tcp-chain | 3 | TCP | Linear chain over TCP (port 8443) | +| rekey | 5 | UDP | Rekey integration test topology | ### [chaos/](chaos/) -- Stochastic Simulation Automated network testing with configurable node counts, topology algorithms (random geometric, Erdos-Renyi, chain, explicit), and fault injection (netem mutation, link flaps, traffic generation, node -churn). 16 scenarios covering general stress testing, cost-based parent -selection, mixed link technologies (fiber/Bluetooth/WiFi), and -transport-specific validation (UDP, TCP, Ethernet). Scenarios are +churn). 20 scenarios covering general stress testing, cost-based parent +selection, mixed link technologies (fiber/Bluetooth/WiFi), +transport-specific validation (UDP, TCP, Ethernet), and ECN/congestion +testing. Scenarios are defined in YAML and executed via a Python harness that manages the full lifecycle: topology generation, Docker orchestration, fault scheduling, log collection, and analysis. diff --git a/testing/chaos/README.md b/testing/chaos/README.md index 1168dac..1a52191 100644 --- a/testing/chaos/README.md +++ b/testing/chaos/README.md @@ -96,7 +96,7 @@ Explicit topologies exercising non-UDP transports. via beacons, not static config. Minimal netem (1-5ms delay). - **ethernet-mesh**: Mirrors `tcp-mesh` topology but with Ethernet instead of TCP. UDP edges use static config; Ethernet edges use beacon discovery. -- **tcp-only**: 4-node ring using TCP on port 443. Tests connect-on-send, +- **tcp-only**: 4-node ring using TCP on port 8443. Tests connect-on-send, FMP framing over TCP, and reconnection. Netem enabled (1-10ms delay, 0-1% loss). - **tcp-chain**: 4-node linear chain, all TCP. Tests multi-hop routing over diff --git a/testing/static/README.md b/testing/static/README.md index 7ba9d20..5ee2296 100644 --- a/testing/static/README.md +++ b/testing/static/README.md @@ -90,14 +90,20 @@ appear in the topology file so that Docker nodes can peer with them. ### TCP Chain -Three nodes in a linear chain using TCP transport (port 443) instead of +Three nodes in a linear chain using TCP transport (port 8443) instead of UDP: A -- B -- C. Each node peers only with its immediate neighbors. Tests basic TCP transport connectivity and multi-hop routing over TCP. The topology file sets `default_transport: tcp`, which causes config -generation to use TCP peer addresses (port 443), inject the TCP transport +generation to use TCP peer addresses (port 8443), inject the TCP transport section, and remove the UDP transport section. +### Rekey + +Same sparse mesh as the mesh topology (5 nodes, 6 links). Configs are +post-processed to use aggressive rekey timers (35s) for CI testing. The +`rekey-test.sh` script handles config injection and multi-phase verification. + ## Configuration Management ### File Structure @@ -114,7 +120,8 @@ testing/static/ │ ├── mesh.yaml # Mesh topology definition │ ├── chain.yaml # Chain topology definition │ ├── mesh-public.yaml # Mesh + external public node -│ └── tcp-chain.yaml # TCP chain (3 nodes, port 443) +│ ├── tcp-chain.yaml # TCP chain (3 nodes, port 8443) +│ └── rekey.yaml # Rekey integration test (5 nodes) ├── generated-configs/ # Auto-generated (gitignored) │ ├── npubs.env # NPUB_A=..., NPUB_B=..., etc. │ ├── mesh/ @@ -319,7 +326,7 @@ removing rules first. - **Capabilities**: `CAP_NET_ADMIN` (for TUN device creation) - **Devices**: `/dev/net/tun` mapped into each container - **DNS**: FIPS built-in resolver on `127.0.0.1:53` -- **Transport**: UDP on port 2121 (MTU 1472) or TCP on port 443 +- **Transport**: UDP on port 2121 (MTU 1472) or TCP on port 8443 - **TUN**: `fips0` interface, MTU 1280 Each node resolves `.fips` DNS names to FIPS IPv6 addresses via its