use std::collections::HashMap;
use std::net::IpAddr;
use std::task::{Context, Poll};
use libp2p::core::Endpoint;
use libp2p::swarm::{
ConnectionDenied, ConnectionId, FromSwarm, NetworkBehaviour, THandler, THandlerInEvent,
THandlerOutEvent, ToSwarm,
};
use libp2p::{Multiaddr, PeerId};
use tracing::debug;
pub struct Behaviour {
connection_ips: HashMap<ConnectionId, IpAddr>,
connections_per_ip: HashMap<IpAddr, usize>,
max_connections_per_ip: usize,
}
impl Behaviour {
pub fn new(max_connections_per_ip: usize) -> Self {
Self {
connection_ips: HashMap::new(),
connections_per_ip: HashMap::new(),
max_connections_per_ip,
}
}
fn track_connection(&mut self, connection_id: ConnectionId, ip: IpAddr) {
self.connection_ips.insert(connection_id, ip);
*self.connections_per_ip.entry(ip).or_insert(0) += 1;
}
fn untrack_connection(&mut self, connection_id: ConnectionId) {
if let Some(ip) = self.connection_ips.remove(&connection_id) {
if let Some(count) = self.connections_per_ip.get_mut(&ip) {
*count = count.saturating_sub(1);
if *count == 0 {
self.connections_per_ip.remove(&ip);
}
}
}
}
}
impl NetworkBehaviour for Behaviour {
type ConnectionHandler = libp2p::swarm::dummy::ConnectionHandler;
type ToSwarm = std::convert::Infallible;
fn handle_pending_inbound_connection(
&mut self,
connection_id: ConnectionId,
_local_addr: &Multiaddr,
remote_addr: &Multiaddr,
) -> Result<(), ConnectionDenied> {
if let Some(ip) = extract_ip(remote_addr) {
let count = self.connections_per_ip.get(&ip).copied().unwrap_or(0);
if count >= self.max_connections_per_ip {
debug!(
%ip,
count,
max = self.max_connections_per_ip,
"Rejecting inbound connection: per-IP limit exceeded"
);
return Err(ConnectionDenied::new(IpLimitExceeded { ip, count }));
}
self.track_connection(connection_id, ip);
}
Ok(())
}
fn handle_established_inbound_connection(
&mut self,
_connection_id: ConnectionId,
_peer: PeerId,
_local_addr: &Multiaddr,
_remote_addr: &Multiaddr,
) -> Result<THandler<Self>, ConnectionDenied> {
Ok(libp2p::swarm::dummy::ConnectionHandler)
}
fn handle_established_outbound_connection(
&mut self,
_connection_id: ConnectionId,
_peer: PeerId,
_addr: &Multiaddr,
_role_override: Endpoint,
_port_use: libp2p::core::transport::PortUse,
) -> Result<THandler<Self>, ConnectionDenied> {
Ok(libp2p::swarm::dummy::ConnectionHandler)
}
fn on_swarm_event(&mut self, event: FromSwarm<'_>) {
if let FromSwarm::ConnectionClosed(info) = event {
self.untrack_connection(info.connection_id);
}
}
fn on_connection_handler_event(
&mut self,
_peer_id: PeerId,
_connection_id: ConnectionId,
event: THandlerOutEvent<Self>,
) {
match event {}
}
fn poll(
&mut self,
_cx: &mut Context<'_>,
) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> {
Poll::Pending
}
}
#[derive(Debug)]
struct IpLimitExceeded {
ip: IpAddr,
count: usize,
}
impl std::fmt::Display for IpLimitExceeded {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"per-IP connection limit exceeded for {}: {} connections",
self.ip, self.count
)
}
}
impl std::error::Error for IpLimitExceeded {}
fn extract_ip(addr: &Multiaddr) -> Option<IpAddr> {
use libp2p::multiaddr::Protocol;
for proto in addr.iter() {
match proto {
Protocol::Ip4(ip) => return Some(IpAddr::V4(ip)),
Protocol::Ip6(ip) => return Some(IpAddr::V6(ip)),
_ => continue,
}
}
None
}