use std::collections::HashMap;
use std::net::SocketAddr;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use std::time::Instant;
use parking_lot::RwLock;
use tracing::{debug, warn};
#[derive(Debug, Clone)]
pub struct ConnectionLimits {
pub max_connections: u64,
pub max_per_ip: u64,
pub max_streams_per_connection: u32,
}
impl Default for ConnectionLimits {
fn default() -> Self {
Self {
max_connections: 10_000,
max_per_ip: 100,
max_streams_per_connection: 100,
}
}
}
impl ConnectionLimits {
pub fn new(max_connections: u64, max_per_ip: u64) -> Self {
Self {
max_connections,
max_per_ip,
..Default::default()
}
}
pub fn with_max_streams(mut self, max: u32) -> Self {
self.max_streams_per_connection = max;
self
}
}
#[derive(Debug, Clone)]
pub struct ConnectionInfo {
pub id: u64,
pub remote_addr: Option<SocketAddr>,
pub connected_at: Instant,
pub active_streams: u32,
}
#[derive(Debug)]
pub struct ConnectionTracker {
inner: Arc<ConnectionTrackerInner>,
}
#[derive(Debug)]
struct ConnectionTrackerInner {
limits: ConnectionLimits,
next_id: AtomicU64,
active: AtomicU64,
per_ip: RwLock<HashMap<std::net::IpAddr, u64>>,
connections: RwLock<HashMap<u64, ConnectionInfo>>,
}
impl Clone for ConnectionTracker {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
}
}
}
impl Default for ConnectionTracker {
fn default() -> Self {
Self::new(ConnectionLimits::default())
}
}
impl ConnectionTracker {
pub fn new(limits: ConnectionLimits) -> Self {
Self {
inner: Arc::new(ConnectionTrackerInner {
limits,
next_id: AtomicU64::new(1),
active: AtomicU64::new(0),
per_ip: RwLock::new(HashMap::new()),
connections: RwLock::new(HashMap::new()),
}),
}
}
pub fn limits(&self) -> &ConnectionLimits {
&self.inner.limits
}
pub fn active_connections(&self) -> u64 {
self.inner.active.load(Ordering::Relaxed)
}
pub fn connections_for_ip(&self, ip: std::net::IpAddr) -> u64 {
self.inner
.per_ip
.read()
.get(&ip)
.copied()
.unwrap_or(0)
}
pub fn try_acquire(&self, remote_addr: Option<SocketAddr>) -> Option<ConnectionGuard> {
let current = self.inner.active.load(Ordering::Relaxed);
if current >= self.inner.limits.max_connections {
warn!(
current = current,
limit = self.inner.limits.max_connections,
"connection rejected: at max connections"
);
return None;
}
if let Some(addr) = remote_addr {
let ip = addr.ip();
let mut per_ip = self.inner.per_ip.write();
let ip_count = per_ip.get(&ip).copied().unwrap_or(0);
if ip_count >= self.inner.limits.max_per_ip {
warn!(
ip = %ip,
current = ip_count,
limit = self.inner.limits.max_per_ip,
"connection rejected: at max per-IP limit"
);
return None;
}
*per_ip.entry(ip).or_insert(0) += 1;
}
self.inner.active.fetch_add(1, Ordering::Relaxed);
let id = self.inner.next_id.fetch_add(1, Ordering::Relaxed);
let info = ConnectionInfo {
id,
remote_addr,
connected_at: Instant::now(),
active_streams: 0,
};
self.inner.connections.write().insert(id, info);
debug!(
id = id,
remote_addr = ?remote_addr,
active = self.active_connections(),
"connection accepted"
);
Some(ConnectionGuard {
tracker: self.clone(),
id,
remote_addr,
})
}
pub fn list_connections(&self) -> Vec<ConnectionInfo> {
self.inner
.connections
.read()
.values()
.cloned()
.collect()
}
pub fn get_connection(&self, id: u64) -> Option<ConnectionInfo> {
self.inner.connections.read().get(&id).cloned()
}
fn release(&self, id: u64, remote_addr: Option<SocketAddr>) {
self.inner.active.fetch_sub(1, Ordering::Relaxed);
if let Some(addr) = remote_addr {
let ip = addr.ip();
let mut per_ip = self.inner.per_ip.write();
if let Some(count) = per_ip.get_mut(&ip) {
*count = count.saturating_sub(1);
if *count == 0 {
per_ip.remove(&ip);
}
}
}
self.inner.connections.write().remove(&id);
debug!(
id = id,
remote_addr = ?remote_addr,
active = self.active_connections(),
"connection released"
);
}
}
#[derive(Debug)]
pub struct ConnectionGuard {
tracker: ConnectionTracker,
id: u64,
remote_addr: Option<SocketAddr>,
}
impl ConnectionGuard {
pub fn id(&self) -> u64 {
self.id
}
pub fn remote_addr(&self) -> Option<SocketAddr> {
self.remote_addr
}
pub fn add_stream(&self) -> bool {
let mut connections = self.tracker.inner.connections.write();
if let Some(info) = connections.get_mut(&self.id) {
if info.active_streams >= self.tracker.inner.limits.max_streams_per_connection {
return false;
}
info.active_streams += 1;
true
} else {
false
}
}
pub fn remove_stream(&self) {
let mut connections = self.tracker.inner.connections.write();
if let Some(info) = connections.get_mut(&self.id) {
info.active_streams = info.active_streams.saturating_sub(1);
}
}
}
impl Drop for ConnectionGuard {
fn drop(&mut self) {
self.tracker.release(self.id, self.remote_addr);
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::{IpAddr, Ipv4Addr};
#[test]
fn connection_limits_default() {
let limits = ConnectionLimits::default();
assert_eq!(limits.max_connections, 10_000);
assert_eq!(limits.max_per_ip, 100);
}
#[test]
fn tracker_basic() {
let tracker = ConnectionTracker::new(ConnectionLimits::new(10, 5));
assert_eq!(tracker.active_connections(), 0);
let guard = tracker.try_acquire(None).expect("should acquire connection");
assert_eq!(tracker.active_connections(), 1);
assert!(guard.id() > 0);
drop(guard);
assert_eq!(tracker.active_connections(), 0);
}
#[test]
fn tracker_max_connections() {
let tracker = ConnectionTracker::new(ConnectionLimits::new(2, 10));
let _g1 = tracker.try_acquire(None).expect("should acquire first connection");
let _g2 = tracker.try_acquire(None).expect("should acquire second connection");
assert!(tracker.try_acquire(None).is_none());
}
#[test]
fn tracker_per_ip_limit() {
let tracker = ConnectionTracker::new(ConnectionLimits::new(100, 2));
let addr1 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)), 8080);
let addr2 = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 2)), 8080);
let _g1 = tracker.try_acquire(Some(addr1)).expect("should acquire first connection");
let _g2 = tracker.try_acquire(Some(addr1)).expect("should acquire second connection");
assert!(tracker.try_acquire(Some(addr1)).is_none());
let _g3 = tracker.try_acquire(Some(addr2)).expect("should acquire from different IP");
}
#[test]
fn tracker_per_ip_release() {
let tracker = ConnectionTracker::new(ConnectionLimits::new(100, 2));
let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)), 8080);
{
let _g1 = tracker.try_acquire(Some(addr)).expect("should acquire first");
let _g2 = tracker.try_acquire(Some(addr)).expect("should acquire second");
assert!(tracker.try_acquire(Some(addr)).is_none());
}
let _g = tracker.try_acquire(Some(addr)).expect("should acquire after release");
assert_eq!(tracker.connections_for_ip(addr.ip()), 1);
}
#[test]
fn tracker_stream_counting() {
let tracker = ConnectionTracker::new(ConnectionLimits::new(10, 10).with_max_streams(2));
let guard = tracker.try_acquire(None).expect("should acquire connection");
assert!(guard.add_stream());
assert!(guard.add_stream());
assert!(!guard.add_stream());
guard.remove_stream();
assert!(guard.add_stream()); }
#[test]
fn tracker_list_connections() {
let tracker = ConnectionTracker::new(ConnectionLimits::default());
let _g1 = tracker.try_acquire(None).expect("should acquire first");
let _g2 = tracker.try_acquire(None).expect("should acquire second");
let connections = tracker.list_connections();
assert_eq!(connections.len(), 2);
}
}