use {
solana_net_utils::token_bucket::{KeyedRateLimiter, TokenBucket},
std::net::IpAddr,
};
pub struct ConnectionRateLimiter {
limiter: KeyedRateLimiter<IpAddr>,
}
const CONNECTION_RATE_LIMITER_CLEANUP_SIZE_THRESHOLD: usize = 100_000;
impl ConnectionRateLimiter {
pub fn new(limit_per_minute: u64, max_burst: u64, num_shards: usize) -> Self {
Self {
limiter: KeyedRateLimiter::new(
CONNECTION_RATE_LIMITER_CLEANUP_SIZE_THRESHOLD,
TokenBucket::new(limit_per_minute, max_burst, limit_per_minute as f64 / 60.0),
num_shards,
),
}
}
pub fn is_allowed(&self, ip: &IpAddr) -> bool {
match self.limiter.current_tokens(ip) {
Some(r) => r > 0, None => true, }
}
pub fn register_connection(&self, ip: &IpAddr) -> bool {
if self.limiter.consume_tokens(*ip, 1).is_ok() {
debug!("Request from IP {ip:?} allowed");
true } else {
debug!("Request from IP {ip:?} blocked");
false }
}
}
#[cfg(test)]
pub mod test {
use {super::*, std::net::Ipv4Addr};
#[tokio::test]
async fn test_connection_rate_limiter() {
let limiter = ConnectionRateLimiter::new(3, 3, 4);
let ip1 = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1));
assert!(limiter.is_allowed(&ip1));
assert!(limiter.register_connection(&ip1));
assert!(limiter.register_connection(&ip1));
assert!(limiter.is_allowed(&ip1));
assert!(limiter.register_connection(&ip1));
assert!(!limiter.is_allowed(&ip1));
assert!(!limiter.register_connection(&ip1));
let ip2 = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 2));
for _ in 0..100 {
assert!(
limiter.is_allowed(&ip2),
"just checking should not mutate state"
);
}
assert!(limiter.register_connection(&ip2));
assert!(limiter.register_connection(&ip2));
assert!(limiter.is_allowed(&ip2));
assert!(limiter.register_connection(&ip2));
assert!(!limiter.is_allowed(&ip2));
}
}