mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-11 00:47:39 +00:00
Fix rekey dual-initiation, parent selection, peer reconnect, and control socket
Rekey dual-initiation race (FMP + FSP): When both sides' rekey timers fire simultaneously on high-latency links (Tor ~700ms RTT), both msg1s cross in flight before dampening can suppress the second initiation. Each node acts as both initiator and responder, with set_pending_session() from the responder path destroying the initiator's in-progress state. Each side ends up with a pending_new_session from a different Noise handshake, causing AEAD verification failure after cutover and link death. Fix: deterministic tie-breaker in both FMP msg1 handler and FSP SessionSetup handler (smaller NodeAddr wins as initiator). Also guard against pending session overwrite from retransmitted msg1s. Parent selection SRTT gate: Peers without MMP RTT data are excluded from parent candidacy via has_srtt() filter, preventing freshly reconnected peers from appearing artificially attractive. First-RTT triggers immediate parent re-eval. The gate in evaluate_parent() skips unmeasured candidates when any peer has cost data, but falls back to default cost 1.0 during cold start when no peer has MMP data yet. Auto-reconnect on all peer removal paths: The excessive-decryption-failure and peer-restart epoch-mismatch removal paths were missing schedule_reconnect() calls, causing auto-connect peers to be permanently abandoned after those events. Control socket accessibility: Socket and parent directory chowned to root:fips with mode 0770/0750 so group members can use fipsctl/fipstop without root. Log level changes: Default RUST_LOG changed to debug in systemd service files. "Unknown session index" log reduced from debug to trace.
This commit is contained in:
+14
-3
@@ -340,7 +340,15 @@ impl TreeState {
|
||||
if coords.contains(&self.my_node_addr) {
|
||||
continue;
|
||||
}
|
||||
let cost = peer_costs.get(peer_id).copied().unwrap_or(1.0);
|
||||
// If any peer has MMP cost data, only consider measured peers.
|
||||
// This prevents freshly connected peers (no SRTT, default cost 1.0)
|
||||
// from appearing artificially cheap. During cold start (no peer has
|
||||
// MMP data, peer_costs is empty), fall back to default cost 1.0.
|
||||
let cost = match peer_costs.get(peer_id) {
|
||||
Some(&c) => c,
|
||||
None if peer_costs.is_empty() => 1.0,
|
||||
None => continue,
|
||||
};
|
||||
let eff_depth = coords.depth() as f64 + cost;
|
||||
match &best_peer {
|
||||
None => best_peer = Some((*peer_id, eff_depth)),
|
||||
@@ -396,11 +404,14 @@ impl TreeState {
|
||||
|
||||
// --- Same root, cost-aware comparison with hysteresis ---
|
||||
|
||||
// Current parent's effective_depth
|
||||
// Current parent's effective_depth.
|
||||
// If peer_costs is non-empty but current parent has no entry,
|
||||
// treat as maximally expensive so any measured candidate can win.
|
||||
// If peer_costs is empty (cold start), use default cost 1.0.
|
||||
let current_parent_cost = peer_costs
|
||||
.get(self.my_declaration.parent_id())
|
||||
.copied()
|
||||
.unwrap_or(1.0);
|
||||
.unwrap_or(if peer_costs.is_empty() { 1.0 } else { f64::INFINITY });
|
||||
let current_parent_coords = self.peer_ancestry.get(self.my_declaration.parent_id());
|
||||
let current_parent_eff = match current_parent_coords {
|
||||
Some(coords) => coords.depth() as f64 + current_parent_cost,
|
||||
|
||||
Reference in New Issue
Block a user