use std::{
collections::HashMap,
net::IpAddr,
sync::Arc,
time::{Duration, Instant},
};
use dashmap::DashMap;
use tokio::sync::RwLock;
use tracing::{debug, warn};
#[derive(Clone)]
pub struct RateLimiter {
max_tokens: u64,
window_duration: Duration,
tokens: Arc<RwLock<HashMap<IpAddr, Vec<Instant>>>>,
}
impl RateLimiter {
pub fn new(max_tokens: u64, window_duration_secs: u64) -> Self {
Self {
max_tokens,
window_duration: Duration::from_secs(window_duration_secs),
tokens: Arc::new(RwLock::new(HashMap::new())),
}
}
pub async fn check_rate_limit(&self, ip: IpAddr) -> bool {
if self.max_tokens == 0 {
return true; }
let mut tokens = self.tokens.write().await;
let now = Instant::now();
let window_start = now - self.window_duration;
let timestamps = tokens.entry(ip).or_insert_with(Vec::new);
timestamps.retain(|&ts| ts > window_start);
if timestamps.len() < self.max_tokens as usize {
timestamps.push(now);
debug!(
"Rate limit check passed for IP: {}, count: {}/{}",
ip,
timestamps.len(),
self.max_tokens
);
true
} else {
warn!(
"Rate limit exceeded for IP: {}, count: {}/{}",
ip,
timestamps.len(),
self.max_tokens
);
false
}
}
pub async fn cleanup_ip(&self, ip: IpAddr) {
let mut tokens = self.tokens.write().await;
tokens.remove(&ip);
}
pub async fn periodic_cleanup(&self) {
let mut tokens = self.tokens.write().await;
let now = Instant::now();
let window_start = now - self.window_duration;
for timestamps in tokens.values_mut() {
timestamps.retain(|&ts| ts > window_start);
}
tokens.retain(|_, timestamps| !timestamps.is_empty());
}
}
#[derive(Clone)]
pub struct IpConnectionLimiter {
max_connections_per_ip: usize,
ip_connections: Arc<DashMap<IpAddr, usize>>,
}
impl IpConnectionLimiter {
pub fn new(max_connections_per_ip: usize) -> Self {
Self {
max_connections_per_ip,
ip_connections: Arc::new(DashMap::new()),
}
}
pub fn check_connection_limit(&self, ip: IpAddr) -> bool {
let mut count = self.ip_connections.entry(ip).or_insert(0);
if *count < self.max_connections_per_ip {
*count += 1;
debug!(
"Connection limit check passed for IP: {}, count: {}/{}",
ip, *count, self.max_connections_per_ip
);
true
} else {
warn!(
"Connection limit exceeded for IP: {}, count: {}/{}",
ip, *count, self.max_connections_per_ip
);
false
}
}
pub fn release_connection(&self, ip: IpAddr) {
if let Some(mut count) = self.ip_connections.get_mut(&ip) {
if *count > 0 {
*count -= 1;
}
if *count == 0 {
self.ip_connections.remove(&ip);
}
debug!(
"Connection released for IP: {}, remaining count: {}",
ip, *count
);
}
}
pub fn get_connection_count(&self, ip: IpAddr) -> usize {
self.ip_connections.get(&ip).map(|v| *v).unwrap_or(0)
}
}
pub struct ConnectionLimiter {
rate_limiter: Option<RateLimiter>,
ip_limiter: Option<IpConnectionLimiter>,
}
impl ConnectionLimiter {
pub fn new(rate_limit: u64, max_connections_per_ip: usize) -> Self {
let rate_limiter = if rate_limit > 0 {
Some(RateLimiter::new(rate_limit, 1))
} else {
None
};
let ip_limiter = if max_connections_per_ip > 0 {
Some(IpConnectionLimiter::new(max_connections_per_ip))
} else {
None
};
Self {
rate_limiter,
ip_limiter,
}
}
pub async fn check_connection(&self, ip: IpAddr) -> bool {
if let Some(rate_limiter) = &self.rate_limiter {
if !rate_limiter.check_rate_limit(ip).await {
return false;
}
}
if let Some(ip_limiter) = &self.ip_limiter {
if !ip_limiter.check_connection_limit(ip) {
return false;
}
}
true
}
pub fn release_connection(&self, ip: IpAddr) {
if let Some(ip_limiter) = &self.ip_limiter {
ip_limiter.release_connection(ip);
}
}
pub fn spawn_cleanup_task(self: Arc<Self>) {
if let Some(rate_limiter) = &self.rate_limiter {
let rate_limiter = rate_limiter.clone();
tokio::spawn(async move {
let mut interval = tokio::time::interval(Duration::from_secs(60));
loop {
interval.tick().await;
rate_limiter.periodic_cleanup().await;
}
});
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::{Ipv4Addr, Ipv6Addr};
#[tokio::test]
async fn test_rate_limiter() {
let limiter = RateLimiter::new(5, 1);
let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
for i in 0..5 {
assert!(
limiter.check_rate_limit(ip).await,
"Connection {} should succeed",
i + 1
);
}
assert!(
!limiter.check_rate_limit(ip).await,
"6th connection should fail"
);
tokio::time::sleep(Duration::from_secs(2)).await;
assert!(
limiter.check_rate_limit(ip).await,
"Connection after window should succeed"
);
}
#[tokio::test]
async fn test_ip_connection_limiter() {
let limiter = IpConnectionLimiter::new(3);
let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
for i in 0..3 {
assert!(
limiter.check_connection_limit(ip),
"Connection {} should succeed",
i + 1
);
}
assert!(
!limiter.check_connection_limit(ip),
"4th connection should fail"
);
limiter.release_connection(ip);
assert!(
limiter.check_connection_limit(ip),
"Connection after release should succeed"
);
}
#[tokio::test]
async fn test_connection_limiter_combined() {
let limiter = Arc::new(ConnectionLimiter::new(5, 3));
let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
for i in 0..3 {
assert!(
limiter.check_connection(ip).await,
"Connection {} should succeed",
i + 1
);
}
assert!(
!limiter.check_connection(ip).await,
"4th connection should fail (per-IP limit)"
);
}
#[tokio::test]
async fn test_rate_limit_disabled() {
let limiter = RateLimiter::new(0, 1);
let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
for i in 0..100 {
assert!(
limiter.check_rate_limit(ip).await,
"Connection {} should succeed",
i + 1
);
}
}
#[tokio::test]
async fn test_ipv6_rate_limiting() {
let limiter = RateLimiter::new(2, 1);
let ip = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1));
assert!(limiter.check_rate_limit(ip).await);
assert!(limiter.check_rate_limit(ip).await);
assert!(!limiter.check_rate_limit(ip).await);
}
}