mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-12 17:43:20 +00:00
Cost-based parent selection: - Replace depth-only parent selection with effective_depth = depth + link_cost - link_cost computed from locally measured MMP metrics: etx * (1.0 + srtt_ms / 100.0) - Prevents bottleneck subtrees in heterogeneous networks where a LoRa link at depth 1 would otherwise always beat fiber at depth 2 - Configurable hysteresis (default 0.2) prevents marginal parent switches - Configurable hold-down timer (default 30s) suppresses re-evaluation after parent switch - Mandatory switches (parent lost, root change) bypass both safeguards - Link costs passed as HashMap parameter to keep TreeState pure Periodic re-evaluation: - evaluate_parent() was only called on TreeAnnounce receipt or parent loss; after tree stabilization, link degradation went undetected - Added timer-based re-evaluation (reeval_interval_secs, default 60s) that calls evaluate_parent() from the tick handler with current MMP link costs - Respects existing hold-down and hysteresis safeguards - Short-circuits when disabled or <2 peers Design documentation: - Update 7 design docs to reflect cost-based parent selection - Replace depth-only algorithm descriptions with effective_depth model - Replace rejected cumulative path cost spec with local-only design rationale - Rewrite Example 2 (heterogeneous links) for local-only cost model - Update config docs: parent_switch_threshold replaced by parent_hysteresis, hold_down_secs, reeval_interval_secs Chaos simulation enhancements: - fips_overrides with deep merge for per-scenario FIPS config customization - Explicit topology algorithm for deterministic test graphs - Control socket querying via fipsctl for tree/MMP snapshot collection - Edge existence validation in netem manager - Per-link netem policy overrides - 9 new chaos scenarios covering cost avoidance, depth-vs-cost tradeoffs, stability, mixed topologies, periodic re-evaluation, and bottleneck parent 12 new unit tests, 667 total passing, clippy clean.
407 lines
16 KiB
Markdown
407 lines
16 KiB
Markdown
# FIPS Configuration
|
||
|
||
FIPS uses YAML-based configuration with a cascading multi-file priority system.
|
||
All parameters have sensible defaults; a node can run with no configuration file
|
||
at all (it will generate an ephemeral identity and listen on default addresses).
|
||
|
||
## Configuration Loading
|
||
|
||
### Search Paths
|
||
|
||
When started without the `-c` flag, FIPS searches for `fips.yaml` in these
|
||
locations, lowest to highest priority:
|
||
|
||
| Priority | Path | Purpose |
|
||
|----------|------|---------|
|
||
| 1 (lowest) | `/etc/fips/fips.yaml` | System-wide defaults |
|
||
| 2 | `~/.config/fips/fips.yaml` | User preferences |
|
||
| 3 | `~/.fips.yaml` | Legacy user config |
|
||
| 4 (highest) | `./fips.yaml` | Deployment-specific overrides |
|
||
|
||
All found files are loaded and merged in priority order. Values from higher
|
||
priority files override those from lower priority files. This allows a system
|
||
administrator to set site-wide defaults in `/etc/fips/fips.yaml` while
|
||
individual deployments override specific values in `./fips.yaml`.
|
||
|
||
### CLI Option
|
||
|
||
```text
|
||
fips -c /path/to/config.yaml
|
||
```
|
||
|
||
When `-c` is specified, only that file is loaded (search paths are skipped).
|
||
|
||
### Partial Configuration
|
||
|
||
Every field has a built-in default. A configuration file only needs to specify
|
||
values that differ from defaults. For example, a minimal config might contain
|
||
only the identity and peer list, inheriting all other defaults.
|
||
|
||
## YAML Structure
|
||
|
||
The configuration is organized into five top-level sections:
|
||
|
||
```yaml
|
||
node: # Node behavior, protocol parameters, and tuning
|
||
tun: # TUN virtual interface
|
||
dns: # DNS responder for .fips domain
|
||
transports: # Network transports (UDP, future: TCP, Tor)
|
||
peers: # Static peer list
|
||
```
|
||
|
||
### Control Socket (`node.control.*`)
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `node.control.enabled` | bool | `true` | Enable the Unix domain control socket |
|
||
| `node.control.socket_path` | string | *(auto)* | Socket file path. Default: `$XDG_RUNTIME_DIR/fips/control.sock` if `XDG_RUNTIME_DIR` is set, otherwise `/tmp/fips-control.sock` |
|
||
|
||
The control socket provides read-only access to node state via the
|
||
`fipsctl` command-line tool. See
|
||
[fips-software-architecture.md](fips-software-architecture.md) for the
|
||
protocol and command list.
|
||
|
||
All tunable protocol parameters live under `node.*`, organized as sysctl-style
|
||
dotted paths. The top-level sections (`tun`, `dns`, `transports`, `peers`)
|
||
handle infrastructure concerns only.
|
||
|
||
## Node Parameters (`node.*`)
|
||
|
||
### Identity (`node.identity.*`)
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `node.identity.nsec` | string | *(generate random)* | Hex-encoded secret key. If omitted, an ephemeral identity is generated on each start. |
|
||
|
||
### General
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `node.leaf_only` | bool | `false` | Leaf-only mode: node does not forward traffic or participate in routing |
|
||
| `node.tick_interval_secs` | u64 | `1` | Periodic maintenance tick interval (retry checks, timeout cleanup, tree refresh) |
|
||
| `node.base_rtt_ms` | u64 | `100` | Initial RTT estimate for new links before measurements converge |
|
||
| `node.heartbeat_interval_secs` | u64 | `10` | Heartbeat send interval per peer for liveness detection |
|
||
| `node.link_dead_timeout_secs` | u64 | `30` | No-traffic timeout before a peer is declared dead and removed |
|
||
|
||
### Resource Limits (`node.limits.*`)
|
||
|
||
Controls capacity for connections, peers, and links.
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `node.limits.max_connections` | usize | `256` | Max handshake-phase connections |
|
||
| `node.limits.max_peers` | usize | `128` | Max authenticated peers |
|
||
| `node.limits.max_links` | usize | `256` | Max active links |
|
||
| `node.limits.max_pending_inbound` | usize | `1000` | Max pending inbound handshakes |
|
||
|
||
### Rate Limiting (`node.rate_limit.*`)
|
||
|
||
Handshake rate limiting protects against DoS on the Noise IK handshake path.
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `node.rate_limit.handshake_burst` | u32 | `100` | Token bucket burst capacity |
|
||
| `node.rate_limit.handshake_rate` | f64 | `10.0` | Tokens per second refill rate |
|
||
| `node.rate_limit.handshake_timeout_secs` | u64 | `30` | Stale handshake cleanup timeout |
|
||
| `node.rate_limit.handshake_resend_interval_ms` | u64 | `1000` | Initial handshake message resend interval |
|
||
| `node.rate_limit.handshake_resend_backoff` | f64 | `2.0` | Resend backoff multiplier (1s, 2s, 4s, 8s, 16s with defaults) |
|
||
| `node.rate_limit.handshake_max_resends` | u32 | `5` | Max resends per handshake attempt |
|
||
|
||
### Retry / Backoff (`node.retry.*`)
|
||
|
||
Connection retry with exponential backoff.
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `node.retry.max_retries` | u32 | `5` | Max connection retry attempts |
|
||
| `node.retry.base_interval_secs` | u64 | `5` | Base backoff interval |
|
||
| `node.retry.max_backoff_secs` | u64 | `300` | Cap on exponential backoff (5 minutes) |
|
||
|
||
Auto-reconnect (triggered by MMP link-dead removal) uses the same backoff
|
||
parameters but bypasses `max_retries`, retrying indefinitely. See
|
||
`peers[].auto_reconnect` below.
|
||
|
||
### Cache Parameters (`node.cache.*`)
|
||
|
||
Controls caching of tree coordinates and identity mappings.
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `node.cache.coord_size` | usize | `50000` | Max entries in coordinate cache |
|
||
| `node.cache.coord_ttl_secs` | u64 | `300` | Coordinate cache entry TTL (5 minutes) |
|
||
| `node.cache.identity_size` | usize | `10000` | Max entries in identity cache (LRU, no TTL) |
|
||
|
||
### Discovery Protocol (`node.discovery.*`)
|
||
|
||
Controls flood-based node discovery (LookupRequest/LookupResponse).
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `node.discovery.ttl` | u8 | `64` | Hop limit for LookupRequest flood |
|
||
| `node.discovery.timeout_secs` | u64 | `10` | Lookup completion timeout |
|
||
| `node.discovery.recent_expiry_secs` | u64 | `10` | Dedup cache expiry for recent request IDs |
|
||
|
||
### Spanning Tree (`node.tree.*`)
|
||
|
||
Controls tree construction and parent selection.
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|----------------------------------------|-------|---------|--------------------------------------------------|
|
||
| `node.tree.announce_min_interval_ms` | u64 | `500` | Per-peer TreeAnnounce rate limit |
|
||
| `node.tree.parent_hysteresis` | f64 | `0.2` | Cost improvement fraction required for same-root parent switch (0.0–1.0) |
|
||
| `node.tree.hold_down_secs` | u64 | `30` | Suppress non-mandatory re-evaluation after parent switch |
|
||
| `node.tree.reeval_interval_secs` | u64 | `60` | Periodic cost-based parent re-evaluation interval (0 = disabled) |
|
||
|
||
### Bloom Filter (`node.bloom.*`)
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `node.bloom.update_debounce_ms` | u64 | `500` | Debounce interval for filter update propagation |
|
||
|
||
Bloom filter size (1 KB), hash count (5), and size classes are protocol
|
||
constants and not configurable.
|
||
|
||
### Session / Data Plane (`node.session.*`)
|
||
|
||
Controls end-to-end session behavior and packet queuing.
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `node.session.default_ttl` | u8 | `64` | Default SessionDatagram TTL |
|
||
| `node.session.pending_packets_per_dest` | usize | `16` | Queue depth per destination during session establishment |
|
||
| `node.session.pending_max_destinations` | usize | `256` | Max destinations with pending packets |
|
||
| `node.session.idle_timeout_secs` | u64 | `90` | Idle session timeout; established sessions with no application data for this duration are removed. MMP reports (SenderReport, ReceiverReport, PathMtuNotification) do not count as activity |
|
||
| `node.session.coords_warmup_packets` | u8 | `5` | Number of initial data packets per session that include the CP flag for transit cache warmup; also the reset count on CoordsRequired/PathBroken receipt |
|
||
| `node.session.coords_response_interval_ms` | u64 | `2000` | Minimum interval (ms) between standalone CoordsWarmup responses to CoordsRequired/PathBroken signals per destination |
|
||
|
||
The anti-replay window size (2048 packets) is a compile-time constant and not
|
||
configurable.
|
||
|
||
### Link-Layer MMP (`node.mmp.*`)
|
||
|
||
Metrics Measurement Protocol for per-peer link measurement. See
|
||
[fips-mesh-layer.md](fips-mesh-layer.md) for behavioral details.
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `node.mmp.mode` | string | `"full"` | Operating mode: `full` (sender + receiver reports), `lightweight` (receiver reports only), or `minimal` (spin bit + CE echo only, no reports) |
|
||
| `node.mmp.log_interval_secs` | u64 | `30` | Periodic operator log interval for link metrics |
|
||
| `node.mmp.owd_window_size` | usize | `32` | One-way delay trend ring buffer size |
|
||
|
||
### Session-Layer MMP (`node.session_mmp.*`)
|
||
|
||
Metrics Measurement Protocol for end-to-end session measurement. Configured
|
||
independently from link-layer MMP because session reports are routed through
|
||
every transit link, consuming bandwidth proportional to path length.
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `node.session_mmp.mode` | string | `"full"` | Operating mode: `full`, `lightweight`, or `minimal` |
|
||
| `node.session_mmp.log_interval_secs` | u64 | `30` | Periodic operator log interval for session metrics |
|
||
| `node.session_mmp.owd_window_size` | usize | `32` | One-way delay trend ring buffer size |
|
||
|
||
### Internal Buffers (`node.buffers.*`)
|
||
|
||
Channel sizes affecting throughput and memory. Primarily useful for performance
|
||
tuning under high load or on memory-constrained devices.
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `node.buffers.packet_channel` | usize | `1024` | Transport to Node packet channel capacity |
|
||
| `node.buffers.tun_channel` | usize | `1024` | TUN to Node outbound channel capacity |
|
||
| `node.buffers.dns_channel` | usize | `64` | DNS to Node identity channel capacity |
|
||
|
||
## TUN Interface (`tun.*`)
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `tun.enabled` | bool | `false` | Enable TUN virtual interface |
|
||
| `tun.name` | string | `"fips0"` | Interface name |
|
||
| `tun.mtu` | u16 | `1280` | Interface MTU (IPv6 minimum) |
|
||
|
||
## DNS Responder (`dns.*`)
|
||
|
||
Resolves `<npub>.fips` queries to FIPS IPv6 addresses. Resolution is pure
|
||
computation (npub to public key to address); resolved identities are registered
|
||
with the node for routing.
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `dns.enabled` | bool | `false` | Enable DNS responder |
|
||
| `dns.bind_addr` | string | `"127.0.0.1"` | Bind address |
|
||
| `dns.port` | u16 | `5354` | Listen port |
|
||
| `dns.ttl` | u32 | `300` | AAAA record TTL in seconds |
|
||
|
||
The `dns.ttl` value should not exceed `node.cache.coord_ttl_secs` to avoid
|
||
stale address mappings.
|
||
|
||
## Transports (`transports.*`)
|
||
|
||
### UDP (`transports.udp.*`)
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `transports.udp.bind_addr` | string | `"0.0.0.0:4000"` | UDP bind address and port |
|
||
| `transports.udp.mtu` | u16 | `1280` | Transport MTU |
|
||
| `transports.udp.recv_buf_size` | usize | `2097152` | UDP socket receive buffer size in bytes (2 MB). Linux kernel doubles the requested value internally. Host `net.core.rmem_max` must be >= this value. |
|
||
| `transports.udp.send_buf_size` | usize | `2097152` | UDP socket send buffer size in bytes (2 MB). Host `net.core.wmem_max` must be >= this value. |
|
||
|
||
## Peers (`peers[]`)
|
||
|
||
Static peer list. Each entry defines a peer to connect to.
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|------|---------|-------------|
|
||
| `peers[].npub` | string | *(required)* | Peer's Nostr public key (npub-encoded) |
|
||
| `peers[].alias` | string | *(none)* | Human-readable name for logging |
|
||
| `peers[].addresses[].transport` | string | *(required)* | Transport type (`udp`) |
|
||
| `peers[].addresses[].addr` | string | *(required)* | Transport address (e.g., `"10.0.0.2:4000"`) |
|
||
| `peers[].addresses[].priority` | u8 | `100` | Address priority (lower = preferred) |
|
||
| `peers[].connect_policy` | string | `"auto_connect"` | Connection policy: `auto_connect`, `on_demand`, or `manual` |
|
||
| `peers[].auto_reconnect` | bool | `true` | Automatically reconnect after MMP link-dead removal (exponential backoff, unlimited retries) |
|
||
|
||
## Minimal Example
|
||
|
||
A typical node configuration enabling TUN, DNS, and a single peer:
|
||
|
||
```yaml
|
||
node:
|
||
identity:
|
||
nsec: "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"
|
||
|
||
tun:
|
||
enabled: true
|
||
name: fips0
|
||
mtu: 1280
|
||
|
||
dns:
|
||
enabled: true
|
||
bind_addr: "127.0.0.1"
|
||
port: 53
|
||
|
||
transports:
|
||
udp:
|
||
bind_addr: "0.0.0.0:4000"
|
||
mtu: 1197
|
||
|
||
peers:
|
||
- npub: "npub1tdwa4vjrjl33pcjdpf2t4p027nl86xrx24g4d3avg4vwvayr3g8qhd84le"
|
||
alias: "node-b"
|
||
addresses:
|
||
- transport: udp
|
||
addr: "172.20.0.11:4000"
|
||
connect_policy: auto_connect
|
||
```
|
||
|
||
All `node.*` parameters use their defaults. To override specific values, add
|
||
only the relevant sections:
|
||
|
||
```yaml
|
||
node:
|
||
identity:
|
||
nsec: "..."
|
||
limits:
|
||
max_peers: 64
|
||
retry:
|
||
max_retries: 10
|
||
max_backoff_secs: 600
|
||
cache:
|
||
coord_size: 100000
|
||
```
|
||
|
||
## Complete Reference
|
||
|
||
The full YAML structure with all defaults:
|
||
|
||
```yaml
|
||
node:
|
||
identity:
|
||
nsec: null # hex secret key (null = generate ephemeral)
|
||
leaf_only: false
|
||
tick_interval_secs: 1
|
||
base_rtt_ms: 100
|
||
heartbeat_interval_secs: 10
|
||
link_dead_timeout_secs: 30
|
||
limits:
|
||
max_connections: 256
|
||
max_peers: 128
|
||
max_links: 256
|
||
max_pending_inbound: 1000
|
||
rate_limit:
|
||
handshake_burst: 100
|
||
handshake_rate: 10.0
|
||
handshake_timeout_secs: 30
|
||
handshake_resend_interval_ms: 1000
|
||
handshake_resend_backoff: 2.0
|
||
handshake_max_resends: 5
|
||
retry:
|
||
max_retries: 5
|
||
base_interval_secs: 5
|
||
max_backoff_secs: 300
|
||
cache:
|
||
coord_size: 50000
|
||
coord_ttl_secs: 300
|
||
identity_size: 10000
|
||
discovery:
|
||
ttl: 64
|
||
timeout_secs: 10
|
||
recent_expiry_secs: 10
|
||
tree:
|
||
announce_min_interval_ms: 500
|
||
parent_hysteresis: 0.2 # cost improvement fraction for parent switch
|
||
hold_down_secs: 30 # suppress re-evaluation after switch
|
||
reeval_interval_secs: 60 # periodic cost-based re-evaluation (0 = disabled)
|
||
bloom:
|
||
update_debounce_ms: 500
|
||
session:
|
||
default_ttl: 64
|
||
pending_packets_per_dest: 16
|
||
pending_max_destinations: 256
|
||
idle_timeout_secs: 90
|
||
coords_warmup_packets: 5
|
||
coords_response_interval_ms: 2000
|
||
mmp:
|
||
mode: full # full | lightweight | minimal
|
||
log_interval_secs: 30
|
||
owd_window_size: 32
|
||
session_mmp:
|
||
mode: full # full | lightweight | minimal
|
||
log_interval_secs: 30
|
||
owd_window_size: 32
|
||
control:
|
||
enabled: true
|
||
socket_path: null # null = auto ($XDG_RUNTIME_DIR/fips/control.sock or /tmp/fips-control.sock)
|
||
buffers:
|
||
packet_channel: 1024
|
||
tun_channel: 1024
|
||
dns_channel: 64
|
||
|
||
tun:
|
||
enabled: false
|
||
name: "fips0"
|
||
mtu: 1280
|
||
|
||
dns:
|
||
enabled: false
|
||
bind_addr: "127.0.0.1"
|
||
port: 5354
|
||
ttl: 300
|
||
|
||
transports:
|
||
udp:
|
||
bind_addr: "0.0.0.0:4000"
|
||
mtu: 1280
|
||
recv_buf_size: 2097152 # 2 MB (kernel doubles to 4 MB actual)
|
||
send_buf_size: 2097152 # 2 MB
|
||
|
||
peers: # static peer list
|
||
# - npub: "npub1..."
|
||
# alias: "node-b"
|
||
# addresses:
|
||
# - transport: udp
|
||
# addr: "10.0.0.2:4000"
|
||
# priority: 100
|
||
# connect_policy: auto_connect
|
||
# auto_reconnect: true # reconnect after link-dead removal
|
||
```
|