Skip to content
Closed
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
1 change: 0 additions & 1 deletion external/devp2p
Submodule devp2p deleted from bc76b9
13 changes: 9 additions & 4 deletions src/config/nat_timeouts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,15 @@ impl Default for RelayTimeouts {
}

/// Default time to wait for the peer to acknowledge stream data after a send.
const DEFAULT_SEND_ACK_TIMEOUT: Duration = Duration::from_secs(1);

/// Fast-network send ACK timeout.
const FAST_SEND_ACK_TIMEOUT: Duration = Duration::from_millis(500);
///
/// The actual timeout used on the send path is *adaptive*: it is computed as
/// `max(DEFAULT_SEND_ACK_TIMEOUT, data_len / 100_000)` seconds so that large
/// payloads on slow or newly-opened NAT-traversed connections are not
/// prematurely timed out.
const DEFAULT_SEND_ACK_TIMEOUT: Duration = Duration::from_secs(5);

/// Fast-network send ACK timeout (for local/LAN networks).
const FAST_SEND_ACK_TIMEOUT: Duration = Duration::from_secs(2);

/// Master timeout configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
Expand Down
47 changes: 24 additions & 23 deletions src/connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4802,10 +4802,13 @@ impl Connection {
punch_me_now.round, punch_me_now.paired_with_sequence_number, punch_me_now.address
);

// v0.13.0: All nodes can coordinate - try coordination first
// v0.13.0: All nodes can coordinate - try coordination first.
// Only enter coordinator path if target_peer_id is present, meaning
// the sender wants us to relay to a target. When target_peer_id is None,
// this is a relayed frame and we are the target — fall through to the
// regular peer path below.
if let Some(nat_state) = &self.nat_traversal {
// All nodes have bootstrap_coordinator now (v0.13.0)
if nat_state.bootstrap_coordinator.is_some() {
if nat_state.bootstrap_coordinator.is_some() && punch_me_now.target_peer_id.is_some() {
// Process coordination request
let from_peer_id = self.derive_peer_id_from_connection();

Expand All @@ -4832,6 +4835,7 @@ impl Connection {
crate::shared::EndpointEventInner::RelayPunchMeNow(
target_peer_id,
coordination_frame,
self.path.remote, // sender's address for diagnostics
),
);
}
Expand All @@ -4851,6 +4855,10 @@ impl Connection {
}

// We're a regular peer receiving coordination from bootstrap
info!(
"Received PUNCH_ME_NOW coordination: round={}, address={}, from={}",
punch_me_now.round, punch_me_now.address, self.path.remote
);
let nat_state = self.nat_traversal.as_mut().ok_or_else(|| {
TransportError::PROTOCOL_VIOLATION("PunchMeNow frame without NAT traversal negotiation")
})?;
Expand All @@ -4862,29 +4870,22 @@ impl Connection {
TransportError::PROTOCOL_VIOLATION("Failed to handle peer punch request")
})?
{
trace!("Coordination synchronized for round {}", punch_me_now.round);
info!(
"Coordination synchronized for round {}, starting hole-punch to {}",
punch_me_now.round, punch_me_now.address
);

// Create punch targets based on the received information
// The peer's address tells us where they'll be listening
let _local_addr = self
.local_ip
.map(|ip| SocketAddr::new(ip, 0))
.unwrap_or_else(|| {
SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED), 0)
// Emit an endpoint event to send NAT binding packets to the
// peer's address. This creates a bidirectional NAT binding so
// the peer's incoming QUIC connection can reach us.
self.endpoint_events
.push_back(crate::shared::EndpointEventInner::InitiateHolePunch {
peer_address: punch_me_now.address,
});

let target = nat_traversal::PunchTarget {
remote_addr: punch_me_now.address,
remote_sequence: punch_me_now.paired_with_sequence_number,
challenge: self.rng.r#gen(),
};

// Start coordination with this target
let _ = nat_state.start_coordination_round(vec![target], now);
} else {
debug!(
"Failed to synchronize coordination for round {}",
punch_me_now.round
info!(
"Failed to synchronize coordination for round {} (peer: {})",
punch_me_now.round, self.path.remote
);
}

Expand Down
48 changes: 46 additions & 2 deletions src/connection/nat_traversal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2679,6 +2679,30 @@ impl NatTraversalState {
);
return Err(NatTraversalError::SuspiciousCoordination);
}
// If there's an existing coordination that's stale (not in an active
// negotiation phase), reset it so a new PUNCH_ME_NOW can be processed.
let should_reset = if let Some(coord) = &self.coordination {
let stale = !matches!(
coord.state,
CoordinationPhase::Coordinating | CoordinationPhase::Requesting
) || coord.round != peer_round;
// Active coordination for the same round — don't reset
if !stale && coord.round == peer_round {
false
} else {
stale
}
} else {
false
};
if should_reset {
info!(
"Resetting stale coordination for new PUNCH_ME_NOW round {}",
peer_round
);
self.coordination = None;
}

if let Some(coord) = &mut self.coordination {
if coord.round == peer_round {
match coord.state {
Expand Down Expand Up @@ -2731,8 +2755,28 @@ impl NatTraversalState {
Ok(false)
}
} else {
debug!("Received peer coordination but no active round");
Ok(false)
// No active coordination round — this is a relayed PUNCH_ME_NOW
// from a coordinator, targeting us. Start a new coordination round
// to initiate hole-punching toward the requesting peer.
info!(
"Received peer coordination with no active round — starting new round {}",
peer_round
);
self.coordination = Some(CoordinationState {
round: peer_round,
punch_targets: Vec::new(),
round_start: now,
punch_start: now + Duration::from_millis(150),
round_duration: self.coordination_timeout,
state: CoordinationPhase::Preparing,
punch_request_sent: false,
peer_punch_received: true,
retry_count: 0,
max_retries: 3,
timeout_state: AdaptiveTimeoutState::new(),
last_retry_at: None,
});
Ok(true)
}
}

Expand Down
Loading
Loading