2022-03-25 15:41:38 +00:00
|
|
|
use crate::IpAddr;
|
|
|
|
use std::{collections::HashMap, time::Instant};
|
2020-05-16 14:04:08 +00:00
|
|
|
use strum::IntoEnumIterator;
|
2021-11-23 12:16:47 +00:00
|
|
|
use tracing::debug;
|
2020-04-19 22:08:25 +00:00
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
2020-11-16 15:44:04 +00:00
|
|
|
struct RateLimitBucket {
|
2022-03-25 15:41:38 +00:00
|
|
|
last_checked: Instant,
|
2020-04-19 22:08:25 +00:00
|
|
|
allowance: f64,
|
|
|
|
}
|
|
|
|
|
2020-07-01 22:54:46 +00:00
|
|
|
#[derive(Eq, PartialEq, Hash, Debug, EnumIter, Copy, Clone, AsRefStr)]
|
2020-11-16 15:44:04 +00:00
|
|
|
pub(crate) enum RateLimitType {
|
2020-04-19 22:08:25 +00:00
|
|
|
Message,
|
|
|
|
Register,
|
|
|
|
Post,
|
2020-08-05 16:00:00 +00:00
|
|
|
Image,
|
2021-11-11 20:40:25 +00:00
|
|
|
Comment,
|
2020-04-19 22:08:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Rate limiting based on rate type and IP addr
|
2021-09-17 15:44:20 +00:00
|
|
|
#[derive(Debug, Clone, Default)]
|
2020-04-19 22:08:25 +00:00
|
|
|
pub struct RateLimiter {
|
2021-02-22 18:04:32 +00:00
|
|
|
buckets: HashMap<RateLimitType, HashMap<IpAddr, RateLimitBucket>>,
|
2020-04-19 22:08:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl RateLimiter {
|
2021-03-18 20:25:21 +00:00
|
|
|
fn insert_ip(&mut self, ip: &IpAddr) {
|
2020-04-19 22:08:25 +00:00
|
|
|
for rate_limit_type in RateLimitType::iter() {
|
|
|
|
if self.buckets.get(&rate_limit_type).is_none() {
|
|
|
|
self.buckets.insert(rate_limit_type, HashMap::new());
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(bucket) = self.buckets.get_mut(&rate_limit_type) {
|
|
|
|
if bucket.get(ip).is_none() {
|
|
|
|
bucket.insert(
|
2021-03-18 20:25:21 +00:00
|
|
|
ip.clone(),
|
2020-04-19 22:08:25 +00:00
|
|
|
RateLimitBucket {
|
2022-03-25 15:41:38 +00:00
|
|
|
last_checked: Instant::now(),
|
2020-04-19 22:08:25 +00:00
|
|
|
allowance: -2f64,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-18 15:26:16 +00:00
|
|
|
/// Rate limiting Algorithm described here: https://stackoverflow.com/a/668327/1655478
|
2022-03-25 15:41:38 +00:00
|
|
|
///
|
|
|
|
/// Returns true if the request passed the rate limit, false if it failed and should be rejected.
|
2020-04-19 22:08:25 +00:00
|
|
|
#[allow(clippy::float_cmp)]
|
2020-04-20 03:59:07 +00:00
|
|
|
pub(super) fn check_rate_limit_full(
|
2020-04-19 22:08:25 +00:00
|
|
|
&mut self,
|
|
|
|
type_: RateLimitType,
|
2021-03-18 20:25:21 +00:00
|
|
|
ip: &IpAddr,
|
2020-04-19 22:08:25 +00:00
|
|
|
rate: i32,
|
|
|
|
per: i32,
|
2022-03-25 15:41:38 +00:00
|
|
|
) -> bool {
|
2020-04-19 22:08:25 +00:00
|
|
|
self.insert_ip(ip);
|
|
|
|
if let Some(bucket) = self.buckets.get_mut(&type_) {
|
|
|
|
if let Some(rate_limit) = bucket.get_mut(ip) {
|
2022-03-25 15:41:38 +00:00
|
|
|
let current = Instant::now();
|
|
|
|
let time_passed = current.duration_since(rate_limit.last_checked).as_secs() as f64;
|
2020-04-19 22:08:25 +00:00
|
|
|
|
|
|
|
// The initial value
|
|
|
|
if rate_limit.allowance == -2f64 {
|
|
|
|
rate_limit.allowance = rate as f64;
|
|
|
|
};
|
|
|
|
|
|
|
|
rate_limit.last_checked = current;
|
|
|
|
rate_limit.allowance += time_passed * (rate as f64 / per as f64);
|
2022-03-24 20:29:08 +00:00
|
|
|
if rate_limit.allowance > rate as f64 {
|
2020-04-19 22:08:25 +00:00
|
|
|
rate_limit.allowance = rate as f64;
|
|
|
|
}
|
|
|
|
|
|
|
|
if rate_limit.allowance < 1.0 {
|
2020-04-20 14:45:14 +00:00
|
|
|
debug!(
|
2020-07-01 22:54:46 +00:00
|
|
|
"Rate limited type: {}, IP: {}, time_passed: {}, allowance: {}",
|
|
|
|
type_.as_ref(),
|
|
|
|
ip,
|
|
|
|
time_passed,
|
|
|
|
rate_limit.allowance
|
2020-04-19 22:08:25 +00:00
|
|
|
);
|
2022-03-25 15:41:38 +00:00
|
|
|
false
|
2020-04-19 22:08:25 +00:00
|
|
|
} else {
|
2022-03-24 20:29:08 +00:00
|
|
|
rate_limit.allowance -= 1.0;
|
2022-03-25 15:41:38 +00:00
|
|
|
true
|
2020-04-19 22:08:25 +00:00
|
|
|
}
|
|
|
|
} else {
|
2022-03-25 15:41:38 +00:00
|
|
|
true
|
2020-04-19 22:08:25 +00:00
|
|
|
}
|
|
|
|
} else {
|
2022-03-25 15:41:38 +00:00
|
|
|
true
|
2020-04-19 22:08:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|