mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-10 16:43:12 +00:00
Fix secp256k1 parity in Noise IK, add disconnect protocol, cross-connection handling, timeout cleanup
Noise IK parity fix: - Pre-message hash normalizes responder static key to even parity (0x02) so initiator and responder hash chains match regardless of actual parity - ECDH uses shared_secret_point() + SHA-256(x-only) instead of SharedSecret::new() which includes a parity-dependent version byte - Fixes handshake failure for ~50% of keys when initiator has only npub Graceful disconnect protocol (link message 0x50): - DisconnectReason enum with 8 reason codes - Disconnect struct with encode/decode - send_encrypted_link_message() reusable helper - handle_disconnect() with immediate peer removal - send_disconnect_to_all_peers() called during Node::stop() Cross-connection fix in handle_msg1(): - addr_to_link check now distinguishes inbound duplicates (reject) from outbound links (cross-connection, allow and resolve via tie-breaker) - remove_link() only clears addr_to_link if entry maps to same link_id - Link cleanup and addr_to_link restoration in cross-connection branches Handshake timeout cleanup: - RX loop uses tokio::select! with 1-second interval tick - check_timeouts() scans for stale (>30s) and failed connections - cleanup_stale_connection() removes all associated state Tests: 279 passing (4 new: cross-connection, stale cleanup, failed cleanup, odd-parity handshake)
This commit is contained in:
+44
-1
@@ -1,6 +1,7 @@
|
||||
//! Node lifecycle management: start, stop, and peer connection initiation.
|
||||
|
||||
use super::*;
|
||||
use crate::protocol::{Disconnect, DisconnectReason};
|
||||
|
||||
impl Node {
|
||||
/// Initiate connections to configured static peers.
|
||||
@@ -316,7 +317,10 @@ impl Node {
|
||||
self.state = NodeState::Stopping;
|
||||
info!(state = %self.state, "Node stopping");
|
||||
|
||||
// Shutdown transports first (they're packet producers)
|
||||
// Send disconnect notifications to all active peers before closing transports
|
||||
self.send_disconnect_to_all_peers(DisconnectReason::Shutdown).await;
|
||||
|
||||
// Shutdown transports (they're packet producers)
|
||||
let transport_ids: Vec<_> = self.transports.keys().cloned().collect();
|
||||
for transport_id in transport_ids {
|
||||
if let Some(mut handle) = self.transports.remove(&transport_id) {
|
||||
@@ -368,4 +372,43 @@ impl Node {
|
||||
info!(state = %self.state, "Node stopped");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send disconnect notifications to all active peers.
|
||||
///
|
||||
/// Best-effort: send failures are logged and ignored since the transport
|
||||
/// may already be degraded. This runs before transports are shut down.
|
||||
async fn send_disconnect_to_all_peers(&mut self, reason: DisconnectReason) {
|
||||
let disconnect = Disconnect::new(reason);
|
||||
let plaintext = disconnect.encode();
|
||||
|
||||
// Collect node_addrs to avoid borrow conflict with send helper
|
||||
let peer_addrs: Vec<NodeAddr> = self.peers.iter()
|
||||
.filter(|(_, peer)| peer.can_send() && peer.has_session())
|
||||
.map(|(addr, _)| *addr)
|
||||
.collect();
|
||||
|
||||
if peer_addrs.is_empty() {
|
||||
debug!(
|
||||
total_peers = self.peers.len(),
|
||||
"No sendable peers for disconnect notification"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut sent = 0usize;
|
||||
for node_addr in &peer_addrs {
|
||||
match self.send_encrypted_link_message(node_addr, &plaintext).await {
|
||||
Ok(()) => sent += 1,
|
||||
Err(e) => {
|
||||
debug!(
|
||||
node_addr = %node_addr,
|
||||
error = %e,
|
||||
"Failed to send disconnect (transport may be down)"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!(sent, total = peer_addrs.len(), reason = %reason, "Sent disconnect notifications");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user