Skip to content

Commit 9db2ea0

Browse files
authored
feat(connection-limit): allow specific peers to bypass limit
Add `bypass_peer_id` on `connection_limit::Behaviour` to allow peers to bypass limit. May close #5605 Pull-Request: #5720.
1 parent c78de35 commit 9db2ea0

File tree

5 files changed

+153
-13
lines changed

5 files changed

+153
-13
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ rust-version = "1.83.0"
7575
libp2p = { version = "0.55.1", path = "libp2p" }
7676
libp2p-allow-block-list = { version = "0.5.0", path = "misc/allow-block-list" }
7777
libp2p-autonat = { version = "0.14.1", path = "protocols/autonat" }
78-
libp2p-connection-limits = { version = "0.5.0", path = "misc/connection-limits" }
78+
libp2p-connection-limits = { version = "0.5.1", path = "misc/connection-limits" }
7979
libp2p-core = { version = "0.43.0", path = "core" }
8080
libp2p-dcutr = { version = "0.13.0", path = "protocols/dcutr" }
8181
libp2p-dns = { version = "0.43.0", path = "transports/dns" }

misc/connection-limits/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 0.5.1
2+
3+
- Allow setting Peer IDs for bypassing limit check.
4+
Connections to the specified peers won't be counted toward limits.
5+
See [PR 5720](https://github.com/libp2p/rust-libp2p/pull/5720).
6+
17
## 0.5.0
28

39
- Deprecate `void` crate.

misc/connection-limits/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name = "libp2p-connection-limits"
33
edition = "2021"
44
rust-version = { workspace = true }
55
description = "Connection limits for libp2p."
6-
version = "0.5.0"
6+
version = "0.5.1"
77
license = "MIT"
88
repository = "https://github.com/libp2p/rust-libp2p"
99
keywords = ["peer-to-peer", "libp2p", "networking"]

misc/connection-limits/src/lib.rs

Lines changed: 144 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ use libp2p_swarm::{
4646
/// contain a [`ConnectionDenied`] type that can be downcast to [`Exceeded`] error if (and only if)
4747
/// **this** behaviour denied the connection.
4848
///
49+
/// You can also set Peer IDs that bypass the said limit. Connections that
50+
/// match the bypass rules will not be checked against or counted for limits.
51+
///
4952
/// If you employ multiple [`NetworkBehaviour`]s that manage connections,
5053
/// it may also be a different error.
5154
///
@@ -67,6 +70,8 @@ use libp2p_swarm::{
6770
/// ```
6871
pub struct Behaviour {
6972
limits: ConnectionLimits,
73+
/// Peer IDs that bypass limit check, regardless of inbound or outbound.
74+
bypass_peer_id: HashSet<PeerId>,
7075

7176
pending_inbound_connections: HashSet<ConnectionId>,
7277
pending_outbound_connections: HashSet<ConnectionId>,
@@ -79,6 +84,7 @@ impl Behaviour {
7984
pub fn new(limits: ConnectionLimits) -> Self {
8085
Self {
8186
limits,
87+
bypass_peer_id: Default::default(),
8288
pending_inbound_connections: Default::default(),
8389
pending_outbound_connections: Default::default(),
8490
established_inbound_connections: Default::default(),
@@ -92,6 +98,19 @@ impl Behaviour {
9298
pub fn limits_mut(&mut self) -> &mut ConnectionLimits {
9399
&mut self.limits
94100
}
101+
102+
/// Add the peer to bypass list.
103+
pub fn bypass_peer_id(&mut self, peer_id: &PeerId) {
104+
self.bypass_peer_id.insert(*peer_id);
105+
}
106+
/// Remove the peer from bypass list.
107+
pub fn remove_peer_id(&mut self, peer_id: &PeerId) {
108+
self.bypass_peer_id.remove(peer_id);
109+
}
110+
/// Whether the connection is bypassed.
111+
pub fn is_bypassed(&self, remote_peer: &PeerId) -> bool {
112+
self.bypass_peer_id.contains(remote_peer)
113+
}
95114
}
96115

97116
fn check_limit(limit: Option<u32>, current: usize, kind: Kind) -> Result<(), ConnectionDenied> {
@@ -238,6 +257,9 @@ impl NetworkBehaviour for Behaviour {
238257
) -> Result<THandler<Self>, ConnectionDenied> {
239258
self.pending_inbound_connections.remove(&connection_id);
240259

260+
if self.is_bypassed(&peer) {
261+
return Ok(dummy::ConnectionHandler);
262+
}
241263
check_limit(
242264
self.limits.max_established_incoming,
243265
self.established_inbound_connections.len(),
@@ -264,10 +286,13 @@ impl NetworkBehaviour for Behaviour {
264286
fn handle_pending_outbound_connection(
265287
&mut self,
266288
connection_id: ConnectionId,
267-
_: Option<PeerId>,
289+
maybe_peer: Option<PeerId>,
268290
_: &[Multiaddr],
269291
_: Endpoint,
270292
) -> Result<Vec<Multiaddr>, ConnectionDenied> {
293+
if maybe_peer.is_some_and(|peer| self.is_bypassed(&peer)) {
294+
return Ok(vec![]);
295+
}
271296
check_limit(
272297
self.limits.max_pending_outgoing,
273298
self.pending_outbound_connections.len(),
@@ -288,6 +313,9 @@ impl NetworkBehaviour for Behaviour {
288313
_: PortUse,
289314
) -> Result<THandler<Self>, ConnectionDenied> {
290315
self.pending_outbound_connections.remove(&connection_id);
316+
if self.is_bypassed(&peer) {
317+
return Ok(dummy::ConnectionHandler);
318+
}
291319

292320
check_limit(
293321
self.limits.max_established_outgoing,
@@ -385,8 +413,7 @@ mod tests {
385413

386414
use super::*;
387415

388-
#[test]
389-
fn max_outgoing() {
416+
fn fill_outgoing() -> (Swarm<Behaviour>, Multiaddr, u32) {
390417
use rand::Rng;
391418

392419
let outgoing_limit = rand::thread_rng().gen_range(1..10);
@@ -411,10 +438,15 @@ mod tests {
411438
)
412439
.expect("Unexpected connection limit.");
413440
}
441+
(network, addr, outgoing_limit)
442+
}
414443

444+
#[test]
445+
fn max_outgoing() {
446+
let (mut network, addr, outgoing_limit) = fill_outgoing();
415447
match network
416448
.dial(
417-
DialOpts::peer_id(target)
449+
DialOpts::peer_id(PeerId::random())
418450
.condition(PeerCondition::Always)
419451
.addresses(vec![addr])
420452
.build(),
@@ -439,6 +471,47 @@ mod tests {
439471
);
440472
}
441473

474+
#[test]
475+
fn outgoing_limit_bypass() {
476+
let (mut network, addr, _) = fill_outgoing();
477+
let bypassed_peer = PeerId::random();
478+
network
479+
.behaviour_mut()
480+
.limits
481+
.bypass_peer_id(&bypassed_peer);
482+
assert!(network.behaviour().limits.is_bypassed(&bypassed_peer));
483+
if let Err(DialError::Denied { cause }) = network.dial(
484+
DialOpts::peer_id(bypassed_peer)
485+
.addresses(vec![addr.clone()])
486+
.build(),
487+
) {
488+
cause
489+
.downcast::<Exceeded>()
490+
.expect_err("Unexpected connection denied because of limit");
491+
}
492+
let not_bypassed_peer = loop {
493+
let new_peer = PeerId::random();
494+
if new_peer != bypassed_peer {
495+
break new_peer;
496+
}
497+
};
498+
match network
499+
.dial(
500+
DialOpts::peer_id(not_bypassed_peer)
501+
.addresses(vec![addr])
502+
.build(),
503+
)
504+
.expect_err("Unexpected dialing success.")
505+
{
506+
DialError::Denied { cause } => {
507+
cause
508+
.downcast::<Exceeded>()
509+
.expect("connection denied because of limit");
510+
}
511+
e => panic!("Unexpected error: {e:?}"),
512+
}
513+
}
514+
442515
#[test]
443516
fn max_established_incoming() {
444517
fn prop(Limit(limit): Limit) {
@@ -479,13 +552,65 @@ mod tests {
479552
});
480553
}
481554

482-
#[derive(Debug, Clone)]
483-
struct Limit(u32);
555+
quickcheck(prop as fn(_));
556+
}
484557

485-
impl Arbitrary for Limit {
486-
fn arbitrary(g: &mut Gen) -> Self {
487-
Self(g.gen_range(1..10))
488-
}
558+
#[test]
559+
fn bypass_established_incoming() {
560+
fn prop(Limit(limit): Limit) {
561+
let mut swarm1 = Swarm::new_ephemeral(|_| {
562+
Behaviour::new(
563+
ConnectionLimits::default().with_max_established_incoming(Some(limit)),
564+
)
565+
});
566+
let mut swarm2 = Swarm::new_ephemeral(|_| {
567+
Behaviour::new(
568+
ConnectionLimits::default().with_max_established_incoming(Some(limit)),
569+
)
570+
});
571+
let mut swarm3 = Swarm::new_ephemeral(|_| {
572+
Behaviour::new(
573+
ConnectionLimits::default().with_max_established_incoming(Some(limit)),
574+
)
575+
});
576+
577+
let rt = Runtime::new().unwrap();
578+
let bypassed_peer_id = *swarm3.local_peer_id();
579+
swarm1
580+
.behaviour_mut()
581+
.limits
582+
.bypass_peer_id(&bypassed_peer_id);
583+
584+
rt.block_on(async {
585+
let (listen_addr, _) = swarm1.listen().with_memory_addr_external().await;
586+
587+
for _ in 0..limit {
588+
swarm2.connect(&mut swarm1).await;
589+
}
590+
591+
swarm3.dial(listen_addr.clone()).unwrap();
592+
593+
tokio::spawn(swarm2.loop_on_next());
594+
tokio::spawn(swarm3.loop_on_next());
595+
596+
swarm1
597+
.wait(|event| match event {
598+
SwarmEvent::ConnectionEstablished { peer_id, .. } => {
599+
(peer_id == bypassed_peer_id).then_some(())
600+
}
601+
SwarmEvent::IncomingConnectionError {
602+
error: ListenError::Denied { cause },
603+
..
604+
} => {
605+
cause
606+
.downcast::<Exceeded>()
607+
.expect_err("Unexpected connection denied because of limit");
608+
None
609+
}
610+
_ => None,
611+
})
612+
.await;
613+
});
489614
}
490615

491616
quickcheck(prop as fn(_));
@@ -609,4 +734,13 @@ mod tests {
609734
Poll::Pending
610735
}
611736
}
737+
738+
#[derive(Debug, Clone)]
739+
struct Limit(u32);
740+
741+
impl Arbitrary for Limit {
742+
fn arbitrary(g: &mut Gen) -> Self {
743+
Self(g.gen_range(1..10))
744+
}
745+
}
612746
}

0 commit comments

Comments
 (0)