diff --git a/docs/design/fips-configuration.md b/docs/design/fips-configuration.md index 3e36389..6dcfe61 100644 --- a/docs/design/fips-configuration.md +++ b/docs/design/fips-configuration.md @@ -151,6 +151,9 @@ Controls tree construction and parent selection. | `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) | +| `node.tree.flap_threshold` | u32 | `4` | Parent switches in window before dampening engages | +| `node.tree.flap_window_secs` | u64 | `60` | Sliding window for counting parent switches | +| `node.tree.flap_dampening_secs` | u64 | `120` | Extended hold-down duration when flap threshold exceeded | ### Bloom Filter (`node.bloom.*`) @@ -351,6 +354,9 @@ node: 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) + flap_threshold: 4 # parent switches before dampening + flap_window_secs: 60 # sliding window for flap detection + flap_dampening_secs: 120 # extended hold-down on flap bloom: update_debounce_ms: 500 session: diff --git a/docs/design/fips-mesh-layer.md b/docs/design/fips-mesh-layer.md index 7e64637..4dcc13f 100644 --- a/docs/design/fips-mesh-layer.md +++ b/docs/design/fips-mesh-layer.md @@ -100,6 +100,7 @@ Every peer connection begins with a Noise IK handshake that mutually authenticates both parties and establishes symmetric keys for link encryption. The IK pattern is chosen because: + - The **initiator** knows the responder's static public key from configuration or discovery, and sends their own static key encrypted in the first message - The **responder** learns the initiator's identity from the first message, @@ -241,6 +242,7 @@ receiver index — a random 32-bit value chosen by the receiver during the handshake. This enables O(1) lookup without relying on source addresses. Each party in a link has two indices: + - **our_index**: Chosen by us, included by the peer in packets sent to us - **their_index**: Chosen by them, included by us in packets sent to them @@ -307,6 +309,12 @@ than the window are rejected. The replay check is performed before decryption to prevent CPU exhaustion from replayed packets that would pass the index lookup but fail decryption. +During link transitions, stale packets from a previous Noise session arrive +encrypted with old counters and are correctly rejected. To avoid excessive log +volume from these benign bursts, replay detection logging is suppressed after +the first 3 occurrences per peer. A summary is emitted when the peer's session +is re-established or the peer is removed. + ## Rate Limiting Handshake initiation (msg1) is the primary attack surface for unauthenticated @@ -314,6 +322,7 @@ traffic. Each msg1 requires Noise DH operations (~200µs on modern CPUs), state allocation, and response generation. FMP uses a global token bucket rate limiter: + - **Burst capacity**: Handles legitimate connection storms (e.g., node restart with many configured peers) - **Sustained rate**: Limits steady-state new connections per second @@ -321,6 +330,7 @@ FMP uses a global token bucket rate limiter: source addresses are trivially spoofable Additional protections: + - **Connection limit**: Maximum number of pending inbound handshakes, capping memory usage - **Handshake timeout**: Stale pending handshakes are cleaned up after a diff --git a/docs/design/fips-session-layer.md b/docs/design/fips-session-layer.md index 6cd4ca1..87a83fa 100644 --- a/docs/design/fips-session-layer.md +++ b/docs/design/fips-session-layer.md @@ -497,6 +497,20 @@ feedback: 5. Notifications are sent on first measurement, on any decrease, and periodically at `max(10s, 5 × SRTT)`. +### Send Failure Backoff + +When a session MMP report cannot be delivered (destination unreachable, no +route), the sender applies exponential backoff to the probe interval: + +- Each consecutive failure doubles the interval: 2x, 4x, 8x, 16x, 32x +- Backoff caps at 32x the base interval (5 consecutive failures) +- A successful send resets to the normal SRTT-based interval +- Debug logging is suppressed after 3 consecutive failures; a summary is + logged when the destination becomes reachable again + +This prevents wasted CPU and log noise when a session's remote endpoint has +departed the network but the local session has not yet timed out. + ### Idle Timeout Interaction MMP reports (SenderReport, ReceiverReport) and PathMtuNotification do **not** @@ -525,7 +539,7 @@ MMP session metrics session=npub1tdwa...84le rtt=4.3ms loss=0.6% jitter=0.2ms go | Explicit counter replay protection | **Implemented** | | Hybrid coordinate warmup (CP + CoordsWarmup) | **Implemented** | | FSP wire format (prefix, AAD, inner header) | **Implemented** | -| Session-layer MMP | **Implemented** | +| Session-layer MMP (with send-failure backoff) | **Implemented** | | Identity cache (LRU-only) | **Implemented** | | Coordinate cache (unified, TTL + refresh) | **Implemented** | | Session idle timeout | **Implemented** | diff --git a/docs/design/fips-spanning-tree.md b/docs/design/fips-spanning-tree.md index ef7ebeb..2e3bd0d 100644 --- a/docs/design/fips-spanning-tree.md +++ b/docs/design/fips-spanning-tree.md @@ -71,6 +71,13 @@ immediate parent reselection: Re-evaluates parent selection using current MMP link costs, independent of TreeAnnounce traffic. This catches link degradation after the tree has stabilized and TreeAnnounce gossip has stopped. +- **Flap dampening** (`flap_threshold` / `flap_window_secs` / + `flap_dampening_secs`): If a node switches parents more than + `flap_threshold` times (default 4) within `flap_window_secs` (default + 60s), an extended hold-down of `flap_dampening_secs` (default 120s) is + imposed. This reduces TreeAnnounce storms from link flapping without + delaying legitimate reconvergence. Mandatory switches (parent loss, + root change) bypass dampening. - **Local-only metrics**: Link costs use only locally measured MMP data (ETX and SRTT). No cumulative path costs are propagated and no wire format changes are required. This avoids the trust problems inherent @@ -230,11 +237,13 @@ direct peers and D is the tree depth. This is NOT O(N) where N is the network size. What a node stores: + - Its own declaration (coordinates, sequence, timestamp, signature) - Each peer's declaration and ancestry chain (P entries, each with D ancestry entries) What a node does NOT know: + - Other subtrees branching off its ancestors - Siblings of ancestors - Nodes in distant parts of the network @@ -249,6 +258,9 @@ Example: In a 1000-node network with depth 10 and 5 peers, a node stores | PARENT_HYSTERESIS | 0.2 (20%) | Fractional improvement in effective depth required for same-root switch | | HOLD_DOWN_SECS | 30s | Suppress non-mandatory re-evaluation after parent switch | | REEVAL_INTERVAL_SECS | 60s | Periodic cost-based parent re-evaluation interval | +| FLAP_THRESHOLD | 4 | Parent switches in window before dampening engages | +| FLAP_WINDOW_SECS | 60s | Sliding window for counting parent switches | +| FLAP_DAMPENING_SECS | 120s | Extended hold-down duration when flap threshold exceeded | | ANNOUNCE_MIN_INTERVAL | 500ms | Minimum between announcements to same peer | | ROOT_TIMEOUT | 60 min | Root declaration considered stale (not yet enforced; heartbeat cascading covers common case) | | TREE_ENTRY_TTL | 5–10 min | Individual entry expiration | @@ -267,6 +279,7 @@ Example: In a 1000-node network with depth 10 and 5 peers, a node stores | Sequence-based freshness | **Implemented** | | Rate limiting (500ms per peer) | **Implemented** | | Coord cache flush on parent change | **Implemented** | +| Flap dampening (extended hold-down on rapid switches) | **Implemented** | | Root timeout enforcement | Planned | | Tree entry TTL enforcement | Planned | | Per-ancestry-entry signatures | Future direction | diff --git a/docs/design/spanning-tree-dynamics.md b/docs/design/spanning-tree-dynamics.md index 9e7b2a2..44700a1 100644 --- a/docs/design/spanning-tree-dynamics.md +++ b/docs/design/spanning-tree-dynamics.md @@ -1104,17 +1104,22 @@ The primary stability mechanisms are implemented: re-evaluation after a parent switch, allowing MMP metrics to stabilize - **Periodic re-evaluation** (`reeval_interval_secs = 60`): catches link degradation after tree stabilization independent of TreeAnnounce traffic +- **Flap dampening** (`flap_threshold = 4`, `flap_window_secs = 60`, + `flap_dampening_secs = 120`): if a node switches parents more than 4 + times within 60s, an extended 120s hold-down is imposed. Mandatory + switches (parent loss, root change) bypass dampening. The flap counter + resets when the window expires naturally. Remaining gaps: - No sequence number advancement rate limiting - No minimum stable state duration before re-announcing -**Impact**: A rapidly flapping link could still cause moderate announcement -traffic. The hold-down timer limits the rate of parent switches (at most -one non-mandatory switch per 30s), and per-peer rate limiting (500ms) -bounds announcement frequency, but the source node's announcement rate is -not independently throttled. +**Impact**: These mechanisms compose to bound announcement traffic even +under rapid link flapping. The hold-down timer limits the rate of parent +switches (at most one non-mandatory switch per 30s), flap dampening +catches pathological patterns that persist beyond the hold-down window, +and per-peer rate limiting (500ms) bounds announcement frequency. ### Known Limitation: Integration Test Gaps @@ -1126,7 +1131,8 @@ propagation. However, the following failure scenarios lack test coverage: - Network partition formation and independent operation - Partition healing and root convergence - Stale entry cleanup (depends on TTL implementation) -- Parent flapping under rapid topology changes +- Parent flapping under rapid topology changes (flap dampening is + implemented; integration test coverage is the remaining gap) These tests are blocked on or related to the limitations above and should be added as each limitation is resolved. @@ -1144,7 +1150,7 @@ through: 4. **Bounded state** - O(peers × depth) entries per node, not O(network size) 5. **Depth-proportional convergence** - Scales with tree height, not node count 6. **Traffic-based failure detection** - No dedicated keepalive overhead -7. **Stability thresholds** - Hysteresis prevents flapping on similar-cost paths +7. **Stability thresholds** - Hysteresis, hold-down, and flap dampening prevent flapping on similar-cost paths Each node maintains only its own ancestry and direct peer information—not global topology. Reachability to arbitrary destinations is provided by Bloom-guided