|
| 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