Skip to content

Commit 2acbb45

Browse files
authored
swarm/: Limit negotiating inbound substreams per connection (#2697)
This limit is shared across all `ConnectionHandler`s on a single connection. It only enforces a limit on the number of negotiating substreams. Once negotiated a `ConnectionHandler` manages the lifecycle of the substream and has to enforce limits themselves.
1 parent 59a74b4 commit 2acbb45

File tree

6 files changed

+62
-2
lines changed

6 files changed

+62
-2
lines changed

swarm/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# 0.36.1 - unreleased
2+
3+
- Limit negotiating inbound substreams per connection. See [PR 2697].
4+
5+
[PR 2697]: https://github.com/libp2p/rust-libp2p/pull/2697
6+
17
# 0.36.0
28

39
- Don't require `Transport` to be `Clone`. See [PR 2529].

swarm/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "libp2p-swarm"
33
edition = "2021"
44
rust-version = "1.56.1"
55
description = "The libp2p swarm"
6-
version = "0.36.0"
6+
version = "0.36.1"
77
authors = ["Parity Technologies <[email protected]>"]
88
license = "MIT"
99
repository = "https://github.com/libp2p/rust-libp2p"

swarm/src/connection.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,13 @@ where
9797
muxer: StreamMuxerBox,
9898
handler: THandler,
9999
substream_upgrade_protocol_override: Option<upgrade::Version>,
100+
max_negotiating_inbound_streams: usize,
100101
) -> Self {
101-
let wrapped_handler = HandlerWrapper::new(handler, substream_upgrade_protocol_override);
102+
let wrapped_handler = HandlerWrapper::new(
103+
handler,
104+
substream_upgrade_protocol_override,
105+
max_negotiating_inbound_streams,
106+
);
102107
Connection {
103108
muxing: Muxing::new(muxer),
104109
handler: wrapped_handler,

swarm/src/connection/handler_wrapper.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,16 @@ where
7777
shutdown: Shutdown,
7878
/// The substream upgrade protocol override, if any.
7979
substream_upgrade_protocol_override: Option<upgrade::Version>,
80+
/// The maximum number of inbound streams concurrently negotiating on a
81+
/// connection. New inbound streams exceeding the limit are dropped and thus
82+
/// reset.
83+
///
84+
/// Note: This only enforces a limit on the number of concurrently
85+
/// negotiating inbound streams. The total number of inbound streams on a
86+
/// connection is the sum of negotiating and negotiated streams. A limit on
87+
/// the total number of streams can be enforced at the [`StreamMuxerBox`]
88+
/// level.
89+
max_negotiating_inbound_streams: usize,
8090
}
8191

8292
impl<TConnectionHandler: ConnectionHandler> std::fmt::Debug for HandlerWrapper<TConnectionHandler> {
@@ -98,6 +108,7 @@ impl<TConnectionHandler: ConnectionHandler> HandlerWrapper<TConnectionHandler> {
98108
pub(crate) fn new(
99109
handler: TConnectionHandler,
100110
substream_upgrade_protocol_override: Option<upgrade::Version>,
111+
max_negotiating_inbound_streams: usize,
101112
) -> Self {
102113
Self {
103114
handler,
@@ -107,6 +118,7 @@ impl<TConnectionHandler: ConnectionHandler> HandlerWrapper<TConnectionHandler> {
107118
unique_dial_upgrade_id: 0,
108119
shutdown: Shutdown::None,
109120
substream_upgrade_protocol_override,
121+
max_negotiating_inbound_streams,
110122
}
111123
}
112124

@@ -243,6 +255,14 @@ where
243255
) {
244256
match endpoint {
245257
SubstreamEndpoint::Listener => {
258+
if self.negotiating_in.len() == self.max_negotiating_inbound_streams {
259+
log::warn!(
260+
"Incoming substream exceeding maximum number of \
261+
negotiating inbound streams. Dropping."
262+
);
263+
return;
264+
}
265+
246266
let protocol = self.handler.listen_protocol();
247267
let timeout = *protocol.timeout();
248268
let (upgrade, user_data) = protocol.into_upgrade();

swarm/src/connection/pool.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ where
8787
/// The configured override for substream protocol upgrades, if any.
8888
substream_upgrade_protocol_override: Option<libp2p_core::upgrade::Version>,
8989

90+
/// The maximum number of inbound streams concurrently negotiating on a connection.
91+
///
92+
/// See [`super::handler_wrapper::HandlerWrapper::max_negotiating_inbound_streams`].
93+
max_negotiating_inbound_streams: usize,
94+
9095
/// The executor to use for running the background tasks. If `None`,
9196
/// the tasks are kept in `local_spawns` instead and polled on the
9297
/// current thread when the [`Pool`] is polled for new events.
@@ -263,6 +268,7 @@ where
263268
task_command_buffer_size: config.task_command_buffer_size,
264269
dial_concurrency_factor: config.dial_concurrency_factor,
265270
substream_upgrade_protocol_override: config.substream_upgrade_protocol_override,
271+
max_negotiating_inbound_streams: config.max_negotiating_inbound_streams,
266272
executor: config.executor,
267273
local_spawns: FuturesUnordered::new(),
268274
pending_connection_events_tx,
@@ -744,6 +750,7 @@ where
744750
muxer,
745751
handler.into_handler(&obtained_peer_id, &endpoint),
746752
self.substream_upgrade_protocol_override,
753+
self.max_negotiating_inbound_streams,
747754
);
748755
self.spawn(
749756
task::new_for_established_connection(
@@ -1153,6 +1160,11 @@ pub struct PoolConfig {
11531160

11541161
/// The configured override for substream protocol upgrades, if any.
11551162
substream_upgrade_protocol_override: Option<libp2p_core::upgrade::Version>,
1163+
1164+
/// The maximum number of inbound streams concurrently negotiating on a connection.
1165+
///
1166+
/// See [super::handler_wrapper::HandlerWrapper::max_negotiating_inbound_streams].
1167+
max_negotiating_inbound_streams: usize,
11561168
}
11571169

11581170
impl Default for PoolConfig {
@@ -1164,6 +1176,7 @@ impl Default for PoolConfig {
11641176
// By default, addresses of a single connection attempt are dialed in sequence.
11651177
dial_concurrency_factor: NonZeroU8::new(1).expect("1 > 0"),
11661178
substream_upgrade_protocol_override: None,
1179+
max_negotiating_inbound_streams: 128,
11671180
}
11681181
}
11691182
}
@@ -1222,6 +1235,14 @@ impl PoolConfig {
12221235
self.substream_upgrade_protocol_override = Some(v);
12231236
self
12241237
}
1238+
1239+
/// The maximum number of inbound streams concurrently negotiating on a connection.
1240+
///
1241+
/// See [`super::handler_wrapper::HandlerWrapper::max_negotiating_inbound_streams`].
1242+
pub fn with_max_negotiating_inbound_streams(mut self, v: usize) -> Self {
1243+
self.max_negotiating_inbound_streams = v;
1244+
self
1245+
}
12251246
}
12261247

12271248
trait EntryExt<'a, K, V> {

swarm/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1364,6 +1364,14 @@ where
13641364
self
13651365
}
13661366

1367+
/// The maximum number of inbound streams concurrently negotiating on a connection.
1368+
///
1369+
/// See [`PoolConfig::with_max_negotiating_inbound_streams`].
1370+
pub fn max_negotiating_inbound_streams(mut self, v: usize) -> Self {
1371+
self.pool_config = self.pool_config.with_max_negotiating_inbound_streams(v);
1372+
self
1373+
}
1374+
13671375
/// Builds a `Swarm` with the current configuration.
13681376
pub fn build(mut self) -> Swarm<TBehaviour> {
13691377
let supported_protocols = self

0 commit comments

Comments
 (0)