|
| 1 | +use std::{ |
| 2 | + collections::VecDeque, |
| 3 | + task::{Context, Poll}, |
| 4 | +}; |
| 5 | + |
| 6 | +use libp2p_core::transport::PortUse; |
| 7 | +use libp2p_core::PeerId; |
| 8 | +use libp2p_swarm::{ |
| 9 | + behaviour::{ConnectionClosed, ConnectionEstablished, FromSwarm}, |
| 10 | + ConnectionDenied, ConnectionId, NetworkBehaviour, THandler, THandlerInEvent, THandlerOutEvent, |
| 11 | + ToSwarm, |
| 12 | +}; |
| 13 | +use tracing::{debug, trace}; |
| 14 | + |
| 15 | +use crate::{store::ConnectionStore, Event}; |
| 16 | + |
| 17 | +/// A [`NetworkBehaviour`] that tracks connected peers. |
| 18 | +/// |
| 19 | +/// This behaviour provides simple connection tracking without any advanced |
| 20 | +/// features like banning or scoring. It's designed to be lightweight and |
| 21 | +/// composable with other behaviours. |
| 22 | +/// |
| 23 | +/// # Example |
| 24 | +/// |
| 25 | +/// ```rust |
| 26 | +/// use libp2p_connection_tracker::Behaviour; |
| 27 | +/// use libp2p_swarm_derive::NetworkBehaviour; |
| 28 | +/// |
| 29 | +/// #[derive(NetworkBehaviour)] |
| 30 | +/// #[behaviour(prelude = "libp2p_swarm::derive_prelude")] |
| 31 | +/// struct MyBehaviour { |
| 32 | +/// connection_tracker: Behaviour, |
| 33 | +/// // ... other behaviours |
| 34 | +/// } |
| 35 | +/// |
| 36 | +/// let connection_tracker = Behaviour::new(); |
| 37 | +/// ``` |
| 38 | +pub struct Behaviour { |
| 39 | + /// Storage for connection state. |
| 40 | + store: ConnectionStore, |
| 41 | + |
| 42 | + /// Queue of events to emit. |
| 43 | + pending_events: VecDeque<Event>, |
| 44 | +} |
| 45 | + |
| 46 | +impl Behaviour { |
| 47 | + /// Create a new connection tracker behaviour. |
| 48 | + pub fn new() -> Self { |
| 49 | + Self { |
| 50 | + store: ConnectionStore::new(), |
| 51 | + pending_events: VecDeque::new(), |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | + /// Check if a peer is currently connected. |
| 56 | + pub fn is_connected(&self, peer_id: &PeerId) -> bool { |
| 57 | + self.store.is_connected(peer_id) |
| 58 | + } |
| 59 | + |
| 60 | + /// Get all currently connected peer IDs. |
| 61 | + pub fn connected_peers(&self) -> impl Iterator<Item = &PeerId> { |
| 62 | + self.store.connected_peers() |
| 63 | + } |
| 64 | + |
| 65 | + /// Get the number of currently connected peers. |
| 66 | + pub fn connected_count(&self) -> usize { |
| 67 | + self.store.connected_count() |
| 68 | + } |
| 69 | + |
| 70 | + /// Get the number of connections to a specific peer. |
| 71 | + pub fn connection_count(&self, peer_id: &PeerId) -> usize { |
| 72 | + self.store.connection_count(peer_id) |
| 73 | + } |
| 74 | +} |
| 75 | + |
| 76 | +impl NetworkBehaviour for Behaviour { |
| 77 | + type ConnectionHandler = libp2p_swarm::dummy::ConnectionHandler; |
| 78 | + type ToSwarm = Event; |
| 79 | + |
| 80 | + fn handle_pending_inbound_connection( |
| 81 | + &mut self, |
| 82 | + _connection_id: ConnectionId, |
| 83 | + _local_addr: &libp2p_core::Multiaddr, |
| 84 | + _remote_addr: &libp2p_core::Multiaddr, |
| 85 | + ) -> Result<(), ConnectionDenied> { |
| 86 | + // We don't interfere with connection establishment |
| 87 | + Ok(()) |
| 88 | + } |
| 89 | + |
| 90 | + fn handle_established_inbound_connection( |
| 91 | + &mut self, |
| 92 | + _connection_id: ConnectionId, |
| 93 | + _peer: PeerId, |
| 94 | + _local_addr: &libp2p_core::Multiaddr, |
| 95 | + _remote_addr: &libp2p_core::Multiaddr, |
| 96 | + ) -> Result<THandler<Self>, ConnectionDenied> { |
| 97 | + Ok(libp2p_swarm::dummy::ConnectionHandler) |
| 98 | + } |
| 99 | + |
| 100 | + fn handle_pending_outbound_connection( |
| 101 | + &mut self, |
| 102 | + _connection_id: ConnectionId, |
| 103 | + _maybe_peer: Option<PeerId>, |
| 104 | + _addresses: &[libp2p_core::Multiaddr], |
| 105 | + _effective_role: libp2p_core::Endpoint, |
| 106 | + ) -> Result<Vec<libp2p_core::Multiaddr>, ConnectionDenied> { |
| 107 | + // Don't modify addresses |
| 108 | + Ok(vec![]) |
| 109 | + } |
| 110 | + |
| 111 | + fn handle_established_outbound_connection( |
| 112 | + &mut self, |
| 113 | + _connection_id: ConnectionId, |
| 114 | + _peer: PeerId, |
| 115 | + _addr: &libp2p_core::Multiaddr, |
| 116 | + _role_override: libp2p_core::Endpoint, |
| 117 | + _port_use: PortUse, |
| 118 | + ) -> Result<THandler<Self>, ConnectionDenied> { |
| 119 | + Ok(libp2p_swarm::dummy::ConnectionHandler) |
| 120 | + } |
| 121 | + |
| 122 | + fn on_swarm_event(&mut self, event: FromSwarm) { |
| 123 | + match event { |
| 124 | + FromSwarm::ConnectionEstablished(ConnectionEstablished { |
| 125 | + peer_id, |
| 126 | + connection_id, |
| 127 | + endpoint, |
| 128 | + .. |
| 129 | + }) => { |
| 130 | + trace!(%peer_id, ?connection_id, "Connection established"); |
| 131 | + |
| 132 | + let is_first_connection = self.store.connection_established(peer_id, connection_id); |
| 133 | + |
| 134 | + if is_first_connection { |
| 135 | + debug!(?peer_id, "Peer connected"); |
| 136 | + self.pending_events.push_back(Event::PeerConnected { |
| 137 | + peer_id, |
| 138 | + connection_id, |
| 139 | + endpoint: endpoint.clone(), |
| 140 | + }); |
| 141 | + } |
| 142 | + } |
| 143 | + |
| 144 | + FromSwarm::ConnectionClosed(ConnectionClosed { |
| 145 | + peer_id, |
| 146 | + connection_id, |
| 147 | + remaining_established, |
| 148 | + .. |
| 149 | + }) => { |
| 150 | + trace!(%peer_id, ?connection_id, remaining_established, "Connection closed"); |
| 151 | + |
| 152 | + let is_last_connection = |
| 153 | + self.store |
| 154 | + .connection_closed(peer_id, connection_id, remaining_established); |
| 155 | + |
| 156 | + if is_last_connection { |
| 157 | + debug!(%peer_id, "Peer disconnected"); |
| 158 | + self.pending_events.push_back(Event::PeerDisconnected { |
| 159 | + peer_id, |
| 160 | + connection_id, |
| 161 | + }); |
| 162 | + } |
| 163 | + } |
| 164 | + |
| 165 | + _ => {} |
| 166 | + } |
| 167 | + } |
| 168 | + |
| 169 | + fn on_connection_handler_event( |
| 170 | + &mut self, |
| 171 | + _peer_id: PeerId, |
| 172 | + _connection_id: ConnectionId, |
| 173 | + event: THandlerOutEvent<Self>, |
| 174 | + ) { |
| 175 | + match event {} |
| 176 | + } |
| 177 | + |
| 178 | + fn poll( |
| 179 | + &mut self, |
| 180 | + _cx: &mut Context<'_>, |
| 181 | + ) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> { |
| 182 | + if let Some(event) = self.pending_events.pop_front() { |
| 183 | + return Poll::Ready(ToSwarm::GenerateEvent(event)); |
| 184 | + } |
| 185 | + |
| 186 | + Poll::Pending |
| 187 | + } |
| 188 | +} |
| 189 | + |
| 190 | +impl Default for Behaviour { |
| 191 | + fn default() -> Self { |
| 192 | + Self::new() |
| 193 | + } |
| 194 | +} |
0 commit comments