Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ parking_lot = "0.12"
once_cell = "1.21"

# Networking
saorsa-transport = "0.31.0"
saorsa-transport = { git = "https://github.com/saorsa-labs/saorsa-transport.git", branch = "rc-2026.4.1" }

# Core-specific dependencies
dirs = "6.0"
Expand Down
15 changes: 10 additions & 5 deletions src/dht_network_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,18 +51,18 @@ const MAX_CANDIDATE_NODES: usize = 200;
/// Messages larger than this are rejected before deserialization
const MAX_MESSAGE_SIZE: usize = 64 * 1024;

/// Request timeout for DHT message handlers (30 seconds)
/// Request timeout for DHT message handlers (10 seconds)
/// Prevents long-running handlers from starving the semaphore permit pool
/// SEC-001: DoS mitigation via timeout enforcement on concurrent operations
const REQUEST_TIMEOUT: Duration = Duration::from_secs(30);
const REQUEST_TIMEOUT: Duration = Duration::from_secs(10);

/// Reliability score assigned to the local node in K-closest results.
/// The local node is always considered fully reliable for its own lookups.
const SELF_RELIABILITY_SCORE: f64 = 1.0;

/// Maximum time to wait for the identity-exchange handshake after dialling
/// a peer. The actual timeout is `min(request_timeout, this)`.
const IDENTITY_EXCHANGE_TIMEOUT: Duration = Duration::from_secs(10);
const IDENTITY_EXCHANGE_TIMEOUT: Duration = Duration::from_secs(5);

/// Maximum time to wait for a stale peer's ping response during admission contention.
const STALE_REVALIDATION_TIMEOUT: Duration = Duration::from_secs(1);
Expand Down Expand Up @@ -2536,8 +2536,13 @@ impl DhtNetworkManager {
}
}

/// Default request timeout for DHT operations (seconds)
const DEFAULT_REQUEST_TIMEOUT_SECS: u64 = 30;
/// Default request timeout for outbound DHT operations (seconds).
///
/// Governs `wait_for_response` and the upper bound of `dial_candidate`'s
/// dial timeout (`min(connection_timeout, request_timeout)`). Must stay
/// above the relay stage (~10s) so it never truncates the NAT traversal
/// cascade.
const DEFAULT_REQUEST_TIMEOUT_SECS: u64 = 15;

/// Default maximum concurrent DHT operations
const DEFAULT_MAX_CONCURRENT_OPS: usize = 100;
Expand Down
12 changes: 6 additions & 6 deletions src/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,16 +124,16 @@ const DEFAULT_MAX_CONNECTIONS: usize = 10_000;

/// Default connection timeout in seconds.
///
/// Must accommodate the full NAT traversal flow: direct (5s) → hole-punch
/// (15s) → relay (30s) = ~50s. With 90s we have headroom for retries and
/// slow handshakes.
const DEFAULT_CONNECTION_TIMEOUT_SECS: u64 = 90;
/// Derived from the sum of connection strategy stages: direct (2s) +
/// 2 × hole-punch rounds (3s + 1s retry each) + relay (10s) = ~20s.
/// 25s provides margin for handshake jitter.
const DEFAULT_CONNECTION_TIMEOUT_SECS: u64 = 25;
Comment thread
mickvandijke marked this conversation as resolved.

/// Number of cached bootstrap peers to retrieve.
const BOOTSTRAP_PEER_BATCH_SIZE: usize = 20;

/// Timeout in seconds for waiting on a bootstrap peer's identity exchange.
const BOOTSTRAP_IDENTITY_TIMEOUT_SECS: u64 = 10;
const BOOTSTRAP_IDENTITY_TIMEOUT_SECS: u64 = 5;

/// Serde helper — returns `true`.
const fn default_true() -> bool {
Expand Down Expand Up @@ -2271,7 +2271,7 @@ mod tests {

assert_eq!(config.listen_addrs().len(), 2); // IPv4 + IPv6
assert_eq!(config.max_connections, 10000);
assert_eq!(config.connection_timeout, Duration::from_secs(90));
assert_eq!(config.connection_timeout, Duration::from_secs(25));
}

#[tokio::test]
Expand Down
20 changes: 7 additions & 13 deletions src/transport/saorsa_transport_adapter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -437,11 +437,10 @@ impl<T: LinkTransport + Send + Sync + 'static> P2PNetworkNode<T> {

/// Connect to a peer by address
pub async fn connect_to_peer(&self, peer_addr: SocketAddr) -> Result<SocketAddr> {
// The full NAT traversal flow is: direct (5s) → hole-punch (15s) →
// relay (30s). The outer timeout must accommodate the entire flow,
// otherwise the connection attempt is killed mid-hole-punch and the
// NAT'd peer is never reached.
const DIAL_TIMEOUT: Duration = Duration::from_secs(90);
// The full NAT traversal flow is: direct (2s) + 2 × hole-punch
// rounds (3s + 1s retry each) + relay (10s) = ~20s. 25s provides
// margin for handshake jitter.
const DIAL_TIMEOUT: Duration = Duration::from_secs(25);

let conn = tokio::time::timeout(
DIAL_TIMEOUT,
Expand Down Expand Up @@ -519,14 +518,9 @@ impl<T: LinkTransport + Send + Sync + 'static> P2PNetworkNode<T> {
/// Dials the peer by address, opens a typed unidirectional stream,
/// writes the data, and finishes the stream.
pub async fn send_to_peer_raw(&self, addr: &SocketAddr, data: &[u8]) -> Result<()> {
// Wrap the entire send path in a 15-second timeout.
//
// For chunk storage, the client should already have the correct
// address (relay or direct) from the DHT. Direct connections
// complete in <5s, so 15s is generous. A longer timeout (e.g. 60s)
// causes ~60s delays per unreachable peer when the DHT has stale
// NATted addresses, dominating upload time.
const SEND_TIMEOUT: Duration = Duration::from_secs(15);
// Budget must cover dial (up to ~20s for full NAT traversal cascade)
// plus the data transfer. Matches DIAL_TIMEOUT in connect_to_peer.
const SEND_TIMEOUT: Duration = Duration::from_secs(25);

tokio::time::timeout(SEND_TIMEOUT, async {
let conn = self
Expand Down
Loading