Update design docs for replay suppression, MMP backoff, and flap dampening

- fips-configuration.md: add flap_threshold, flap_window_secs,
  flap_dampening_secs to tree config table and YAML reference
- fips-spanning-tree.md: add flap dampening to Stability Mechanisms,
  Timing Parameters, and Implementation Status tables
- spanning-tree-dynamics.md: update Known Limitations to reflect flap
  dampening implementation, revise impact text
- fips-session-layer.md: add Send Failure Backoff subsection under
  Session-Layer MMP
- fips-mesh-layer.md: add log suppression note to Replay Protection
This commit is contained in:
Johnathan Corgan
2026-02-23 20:28:17 +00:00
parent 94c0e19951
commit c1820e689d
5 changed files with 57 additions and 8 deletions

View File

@@ -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.01.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:

View File

@@ -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

View File

@@ -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** |

View File

@@ -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 | 510 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 |

View File

@@ -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