Skip to content

Commit 82986ef

Browse files
authored
feat: add connection gater (#99)
* feat: add tracing * fix: linting, cargo-deny * feat: add p2p config * fix: remove comment * feat: add p2p metrics * fix: rename label in example * fix: linting * feat: [wip] add relay/ping * feat: add behaviours module, add relay client support * fix: linter warnings * fix: remove wildcard dependencies * feat: add connection gater * fix: linter warnings * fix: linter warnings
1 parent 1fd40f7 commit 82986ef

5 files changed

Lines changed: 317 additions & 5 deletions

File tree

crates/charon-p2p/examples/p2p.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ async fn main() -> Result<()> {
4040
let mut p2p: Node<_> = Node::new(
4141
P2PConfig::default(),
4242
key.clone(),
43-
ConnGater,
43+
ConnGater::new_open_gater(),
4444
false,
4545
NodeType::QUIC,
4646
PlutoMdnsBehaviour::new,

crates/charon-p2p/src/behaviours/pluto.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@ use std::time::Duration;
44

55
use libp2p::{identify, identity::Keypair, ping, relay, swarm::NetworkBehaviour};
66

7+
use crate::gater::ConnGater;
8+
79
#[derive(NetworkBehaviour)]
810
pub struct PlutoBehaviour {
11+
/// Connection gater behaviour.
12+
pub gater: ConnGater,
913
/// Relay client behaviour.
1014
pub relay: relay::client::Behaviour,
1115
/// Identify behaviour.
@@ -28,6 +32,7 @@ impl PlutoBehaviour {
2832
.with_interval(Duration::from_secs(1))
2933
.with_timeout(Duration::from_secs(2)),
3034
),
35+
gater: ConnGater::new_conn_gater(vec![], vec![]),
3136
}
3237
}
3338
}

crates/charon-p2p/src/gater.rs

Lines changed: 0 additions & 4 deletions
This file was deleted.
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
//! Connection handler for the gater behaviour.
2+
//!
3+
//! This is a dummy handler since the gater doesn't need to negotiate any
4+
//! protocols or handle any connection-level events. The actual gating logic
5+
//! happens at the connection establishment phase in the `NetworkBehaviour`
6+
//! implementation.
7+
8+
use std::{
9+
convert::Infallible,
10+
task::{Context, Poll},
11+
};
12+
13+
use libp2p::swarm::{
14+
ConnectionHandler, ConnectionHandlerEvent, Stream, SubstreamProtocol, handler::ConnectionEvent,
15+
};
16+
17+
/// Dummy connection handler for the gater.
18+
///
19+
/// This handler doesn't negotiate any protocols or handle any events.
20+
/// It exists only to satisfy the `NetworkBehaviour` trait requirements.
21+
#[derive(Debug, Clone, Default)]
22+
pub struct Handler {
23+
_private: (),
24+
}
25+
26+
impl Handler {
27+
/// Creates a new handler.
28+
pub fn new() -> Self {
29+
Self { _private: () }
30+
}
31+
}
32+
33+
impl ConnectionHandler for Handler {
34+
type FromBehaviour = Infallible;
35+
type InboundOpenInfo = ();
36+
type InboundProtocol = DeniedUpgrade;
37+
type OutboundOpenInfo = Infallible;
38+
type OutboundProtocol = DeniedUpgrade;
39+
type ToBehaviour = Infallible;
40+
41+
fn listen_protocol(&self) -> SubstreamProtocol<Self::InboundProtocol, Self::InboundOpenInfo> {
42+
SubstreamProtocol::new(DeniedUpgrade, ())
43+
}
44+
45+
fn poll(
46+
&mut self,
47+
_cx: &mut Context<'_>,
48+
) -> Poll<
49+
ConnectionHandlerEvent<Self::OutboundProtocol, Self::OutboundOpenInfo, Self::ToBehaviour>,
50+
> {
51+
Poll::Pending
52+
}
53+
54+
fn on_behaviour_event(&mut self, event: Self::FromBehaviour) {
55+
match event {}
56+
}
57+
58+
fn on_connection_event(
59+
&mut self,
60+
_event: ConnectionEvent<
61+
Self::InboundProtocol,
62+
Self::OutboundProtocol,
63+
Self::InboundOpenInfo,
64+
Self::OutboundOpenInfo,
65+
>,
66+
) {
67+
// No events to handle
68+
}
69+
}
70+
71+
/// A protocol upgrade that always denies the upgrade.
72+
///
73+
/// This is used because the gater doesn't need to negotiate any protocols.
74+
#[derive(Debug, Clone, Copy, Default)]
75+
pub struct DeniedUpgrade;
76+
77+
impl libp2p::core::UpgradeInfo for DeniedUpgrade {
78+
type Info = &'static str;
79+
type InfoIter = std::iter::Empty<Self::Info>;
80+
81+
fn protocol_info(&self) -> Self::InfoIter {
82+
std::iter::empty()
83+
}
84+
}
85+
86+
impl libp2p::core::upgrade::InboundUpgrade<Stream> for DeniedUpgrade {
87+
type Error = Infallible;
88+
type Future = std::future::Pending<Result<Self::Output, Self::Error>>;
89+
type Output = Infallible;
90+
91+
fn upgrade_inbound(self, _: Stream, _: Self::Info) -> Self::Future {
92+
std::future::pending()
93+
}
94+
}
95+
96+
impl libp2p::core::upgrade::OutboundUpgrade<Stream> for DeniedUpgrade {
97+
type Error = Infallible;
98+
type Future = std::future::Pending<Result<Self::Output, Self::Error>>;
99+
type Output = Infallible;
100+
101+
fn upgrade_outbound(self, _: Stream, _: Self::Info) -> Self::Future {
102+
std::future::pending()
103+
}
104+
}

crates/charon-p2p/src/gater/mod.rs

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
//! Gater is responsible for whitelisting / blacklisting peers.
2+
//!
3+
//! This module provides connection gating functionality that limits access to
4+
//! cluster peers and relays. In Rust libp2p, connection gating is implemented
5+
//! via the `NetworkBehaviour` trait, specifically through the
6+
//! `handle_established_inbound_connection` and
7+
//! `handle_established_outbound_connection` methods which can reject
8+
//! connections by returning `ConnectionDenied`.
9+
10+
use std::{
11+
collections::{HashSet, VecDeque},
12+
sync::Arc,
13+
task::{Context, Poll},
14+
};
15+
16+
use libp2p::{
17+
Multiaddr, PeerId,
18+
swarm::{
19+
ConnectionDenied, ConnectionId, FromSwarm, NetworkBehaviour, THandler, THandlerInEvent,
20+
THandlerOutEvent, ToSwarm,
21+
},
22+
};
23+
24+
use crate::peer::MutablePeer;
25+
26+
mod handler;
27+
28+
/// Configuration for the connection gater.
29+
#[derive(Clone, Default)]
30+
pub struct Config {
31+
peer_ids: HashSet<PeerId>,
32+
relays: Vec<Arc<MutablePeer>>,
33+
open: bool,
34+
}
35+
36+
impl Config {
37+
/// Creates a new open gater configuration that does not gate any
38+
/// connections.
39+
pub fn open() -> Self {
40+
Self {
41+
peer_ids: HashSet::new(),
42+
relays: Vec::new(),
43+
open: true,
44+
}
45+
}
46+
47+
/// Creates a new closed gater configuration that gates all connections
48+
/// except those explicitly allowed.
49+
pub fn closed() -> Self {
50+
Self {
51+
peer_ids: HashSet::new(),
52+
relays: Vec::new(),
53+
open: false,
54+
}
55+
}
56+
57+
/// Sets the allowed peer IDs.
58+
pub fn with_peer_ids(mut self, peer_ids: Vec<PeerId>) -> Self {
59+
self.peer_ids = peer_ids.into_iter().collect();
60+
self
61+
}
62+
63+
/// Sets the relay peers.
64+
pub fn with_relays(mut self, relays: Vec<Arc<MutablePeer>>) -> Self {
65+
self.relays = relays;
66+
self
67+
}
68+
}
69+
70+
/// ConnGater filters incoming and outgoing connections by the cluster peers.
71+
#[derive(Clone, Default)]
72+
pub struct ConnGater {
73+
config: Config,
74+
events: VecDeque<Event>,
75+
}
76+
77+
impl ConnGater {
78+
/// Creates a new connection gater with the given configuration.
79+
pub fn new(config: Config) -> Self {
80+
Self {
81+
config,
82+
events: VecDeque::new(),
83+
}
84+
}
85+
86+
/// Creates a new connection gater that limits access to the cluster peers
87+
/// and relays.
88+
pub fn new_conn_gater(peers: Vec<PeerId>, relays: Vec<Arc<MutablePeer>>) -> Self {
89+
Self {
90+
config: Config::closed().with_peer_ids(peers).with_relays(relays),
91+
events: VecDeque::new(),
92+
}
93+
}
94+
95+
/// Creates a new open gater that does not gate any connections.
96+
pub fn new_open_gater() -> Self {
97+
Self {
98+
config: Config::open(),
99+
events: VecDeque::new(),
100+
}
101+
}
102+
103+
/// Returns true if the gater is open (not gating any connections).
104+
pub fn is_open(&self) -> bool {
105+
self.config.open
106+
}
107+
108+
/// Checks if a peer is allowed to connect.
109+
fn is_peer_allowed(&self, peer_id: &PeerId) -> bool {
110+
if self.config.open {
111+
return true;
112+
}
113+
114+
// Check if peer is in the allowed set
115+
if self.config.peer_ids.contains(peer_id) {
116+
return true;
117+
}
118+
119+
// Check if peer is a relay
120+
for relay in &self.config.relays {
121+
if let Ok(Some(peer)) = relay.peer()
122+
&& peer.id == *peer_id
123+
{
124+
return true;
125+
}
126+
}
127+
128+
false
129+
}
130+
}
131+
132+
/// Event emitted by the connection gater behaviour.
133+
#[derive(Debug, Clone)]
134+
pub enum Event {
135+
/// A peer was blocked from connecting.
136+
PeerBlocked(PeerId),
137+
}
138+
139+
impl NetworkBehaviour for ConnGater {
140+
type ConnectionHandler = handler::Handler;
141+
type ToSwarm = Event;
142+
143+
fn handle_established_inbound_connection(
144+
&mut self,
145+
_connection_id: ConnectionId,
146+
peer: PeerId,
147+
_local_addr: &Multiaddr,
148+
_remote_addr: &Multiaddr,
149+
) -> Result<THandler<Self>, ConnectionDenied> {
150+
if self.is_peer_allowed(&peer) {
151+
Ok(handler::Handler::new())
152+
} else {
153+
self.events.push_back(Event::PeerBlocked(peer));
154+
Err(ConnectionDenied::new(PeerNotAllowed(peer)))
155+
}
156+
}
157+
158+
fn handle_established_outbound_connection(
159+
&mut self,
160+
_connection_id: ConnectionId,
161+
_peer: PeerId,
162+
_addr: &Multiaddr,
163+
_role_override: libp2p::core::Endpoint,
164+
_port_use: libp2p::core::transport::PortUse,
165+
) -> Result<THandler<Self>, ConnectionDenied> {
166+
// Allow all outbound connections
167+
Ok(handler::Handler::new())
168+
}
169+
170+
fn on_swarm_event(&mut self, _event: FromSwarm) {
171+
// No special handling needed for swarm events
172+
}
173+
174+
fn on_connection_handler_event(
175+
&mut self,
176+
_peer_id: PeerId,
177+
_connection_id: ConnectionId,
178+
_event: THandlerOutEvent<Self>,
179+
) {
180+
// Handler events are Void, so this is unreachable
181+
}
182+
183+
fn poll(
184+
&mut self,
185+
_cx: &mut Context<'_>,
186+
) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> {
187+
// Emit any blocked events
188+
if !self.events.is_empty() {
189+
let event = self.events.pop_front().expect("events is not empty");
190+
return Poll::Ready(ToSwarm::GenerateEvent(event));
191+
}
192+
193+
Poll::Pending
194+
}
195+
}
196+
197+
/// Error indicating a peer is not allowed to connect.
198+
#[derive(Debug, Clone)]
199+
pub struct PeerNotAllowed(pub PeerId);
200+
201+
impl std::fmt::Display for PeerNotAllowed {
202+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
203+
write!(f, "peer {} is not in the allowed list", self.0)
204+
}
205+
}
206+
207+
impl std::error::Error for PeerNotAllowed {}

0 commit comments

Comments
 (0)