-
Notifications
You must be signed in to change notification settings - Fork 1.1k
fix(kad): enforce a timeout for inbound substreams #6009
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,7 @@ name = "libp2p-kad" | |
edition.workspace = true | ||
rust-version = { workspace = true } | ||
description = "Kademlia protocol for libp2p" | ||
version = "0.49.0" | ||
version = "0.49.1" | ||
authors = ["Parity Technologies <[email protected]>"] | ||
license = "MIT" | ||
repository = "https://github.com/libp2p/rust-libp2p" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,7 +27,8 @@ use std::{ | |
}; | ||
|
||
use either::Either; | ||
use futures::{channel::oneshot, prelude::*, stream::SelectAll}; | ||
use futures::{channel::oneshot, prelude::*}; | ||
use futures_bounded::{Delay, StreamSet}; | ||
use libp2p_core::{upgrade, ConnectedPoint}; | ||
use libp2p_identity::PeerId; | ||
use libp2p_swarm::{ | ||
|
@@ -77,7 +78,8 @@ pub struct Handler { | |
pending_messages: VecDeque<(KadRequestMsg, QueryId)>, | ||
|
||
/// List of active inbound substreams with the state they are in. | ||
inbound_substreams: SelectAll<InboundSubstreamState>, | ||
/// The streams are typed `InboundSubstreamState`, but the set uses the item type. | ||
inbound_substreams: StreamSet<ConnectionHandlerEvent<ProtocolConfig, (), HandlerEvent>>, | ||
|
||
/// The connected endpoint of the connection that the handler | ||
/// is associated with. | ||
|
@@ -119,8 +121,6 @@ enum InboundSubstreamState { | |
PendingFlush(UniqueConnecId, KadInStreamSink<Stream>), | ||
/// The substream is being closed. | ||
Closing(KadInStreamSink<Stream>), | ||
/// The substream was cancelled in favor of a new one. | ||
Cancelled, | ||
|
||
Poisoned { | ||
phantom: PhantomData<QueryId>, | ||
|
@@ -173,9 +173,6 @@ impl InboundSubstreamState { | |
| InboundSubstreamState::Closing(substream) => { | ||
*self = InboundSubstreamState::Closing(substream); | ||
} | ||
InboundSubstreamState::Cancelled => { | ||
*self = InboundSubstreamState::Cancelled; | ||
} | ||
InboundSubstreamState::Poisoned { .. } => unreachable!(), | ||
} | ||
} | ||
|
@@ -461,9 +458,12 @@ impl Handler { | |
endpoint, | ||
remote_peer_id, | ||
next_connec_unique_id: UniqueConnecId(0), | ||
inbound_substreams: Default::default(), | ||
inbound_substreams: StreamSet::new( | ||
move || Delay::futures_timer(substreams_timeout), | ||
MAX_NUM_STREAMS, | ||
), | ||
outbound_substreams: futures_bounded::FuturesTupleSet::new( | ||
substreams_timeout, | ||
move || Delay::futures_timer(substreams_timeout), | ||
MAX_NUM_STREAMS, | ||
), | ||
pending_streams: Default::default(), | ||
|
@@ -518,38 +518,45 @@ impl Handler { | |
}); | ||
} | ||
|
||
if self.inbound_substreams.len() == MAX_NUM_STREAMS { | ||
if let Some(s) = self.inbound_substreams.iter_mut().find(|s| { | ||
matches!( | ||
s, | ||
// An inbound substream waiting to be reused. | ||
InboundSubstreamState::WaitingMessage { first: false, .. } | ||
) | ||
}) { | ||
*s = InboundSubstreamState::Cancelled; | ||
let connec_unique_id = self.next_connec_unique_id; | ||
self.next_connec_unique_id.0 += 1; | ||
let new_substream = InboundSubstreamState::WaitingMessage { | ||
first: true, | ||
connection_id: connec_unique_id, | ||
substream: protocol, | ||
}; | ||
|
||
if self.inbound_substreams.len() >= MAX_NUM_STREAMS { | ||
if let Some(s) = self | ||
.inbound_substreams | ||
.iter_mut_of_type::<InboundSubstreamState>() | ||
.find(|s| { | ||
matches!( | ||
**s, | ||
// An inbound substream waiting to be reused. | ||
InboundSubstreamState::WaitingMessage { first: false, .. } | ||
) | ||
}) | ||
{ | ||
*s.get_mut() = new_substream; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because This code is more robust, because old code could have subtle bugs. For example, if we added two streams over the limit in a row (so we had 33 streams), the equality check ( |
||
tracing::debug!( | ||
peer=?self.remote_peer_id, | ||
"New inbound substream to peer exceeds inbound substream limit. \ | ||
Removed older substream waiting to be reused." | ||
Replacing older substream that was waiting to be reused." | ||
) | ||
} else { | ||
tracing::warn!( | ||
peer=?self.remote_peer_id, | ||
"New inbound substream to peer exceeds inbound substream limit. \ | ||
No older substream waiting to be reused. Dropping new substream." | ||
); | ||
return; | ||
} | ||
} else { | ||
self.inbound_substreams | ||
.try_push(new_substream) | ||
.map_err(|_| ()) | ||
.expect("Just checked that stream set is not full; qed"); | ||
} | ||
|
||
let connec_unique_id = self.next_connec_unique_id; | ||
self.next_connec_unique_id.0 += 1; | ||
self.inbound_substreams | ||
.push(InboundSubstreamState::WaitingMessage { | ||
first: true, | ||
connection_id: connec_unique_id, | ||
substream: protocol, | ||
}); | ||
} | ||
|
||
/// Takes the given [`KadRequestMsg`] and composes it into an outbound request-response protocol | ||
|
@@ -616,15 +623,15 @@ impl ConnectionHandler for Handler { | |
HandlerIn::Reset(request_id) => { | ||
if let Some(state) = self | ||
.inbound_substreams | ||
.iter_mut() | ||
.find(|state| match state { | ||
.iter_mut_of_type::<InboundSubstreamState>() | ||
.find(|state| match **state { | ||
InboundSubstreamState::WaitingBehaviour(conn_id, _, _) => { | ||
conn_id == &request_id.connec_unique_id | ||
conn_id == request_id.connec_unique_id | ||
} | ||
_ => false, | ||
}) | ||
{ | ||
state.close(); | ||
state.get_mut().close(); | ||
} | ||
} | ||
HandlerIn::FindNodeReq { key, query_id } => { | ||
|
@@ -763,8 +770,16 @@ impl ConnectionHandler for Handler { | |
Poll::Pending => {} | ||
} | ||
|
||
if let Poll::Ready(Some(event)) = self.inbound_substreams.poll_next_unpin(cx) { | ||
return Poll::Ready(event); | ||
if let Poll::Ready(Some(event_result)) = self.inbound_substreams.poll_next_unpin(cx) { | ||
match event_result { | ||
Ok(event) => return Poll::Ready(event), | ||
Err(_stream_set_timeout) => { | ||
tracing::trace!( | ||
"Inbound substream timed out waiting for peer, send, or close" | ||
); | ||
continue; | ||
} | ||
} | ||
} | ||
Comment on lines
-766
to
783
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wasn't sure what to do with an inbound substream timeout.
|
||
|
||
if self.outbound_substreams.len() < MAX_NUM_STREAMS { | ||
|
@@ -848,8 +863,11 @@ fn compute_new_protocol_status( | |
|
||
impl Handler { | ||
fn answer_pending_request(&mut self, request_id: RequestId, mut msg: KadResponseMsg) { | ||
for state in self.inbound_substreams.iter_mut() { | ||
match state.try_answer_with(request_id, msg) { | ||
for state in self | ||
.inbound_substreams | ||
.iter_mut_of_type::<InboundSubstreamState>() | ||
{ | ||
match state.get_mut().try_answer_with(request_id, msg) { | ||
Ok(()) => return, | ||
Err(m) => { | ||
msg = m; | ||
|
@@ -1006,7 +1024,6 @@ impl futures::Stream for InboundSubstreamState { | |
} | ||
}, | ||
InboundSubstreamState::Poisoned { .. } => unreachable!(), | ||
InboundSubstreamState::Cancelled => return Poll::Ready(None), | ||
} | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This weird construct is required because the underlying API (in
SelectAll
) doesn't allow typed iteration.