use crate::auth::resolve_client_ip;
use crate::config::{RateLimitConfig, TrustedProxies};
use std::net::IpAddr;
use tower_governor::errors::GovernorError;
use tower_governor::governor::GovernorConfigBuilder;
use tower_governor::key_extractor::{KeyExtractor, SmartIpKeyExtractor};
fn rps_to_period(rps: u64) -> u64 {
debug_assert!(rps > 0, "rate limit rps must be > 0");
(1000 / rps.max(1)).max(1)
}
#[derive(Debug, Clone)]
pub struct TrustedProxyKeyExtractor {
trusted_proxies: TrustedProxies,
}
impl TrustedProxyKeyExtractor {
pub fn new(trusted_proxies: TrustedProxies) -> Self {
Self { trusted_proxies }
}
}
impl KeyExtractor for TrustedProxyKeyExtractor {
type Key = IpAddr;
fn extract<T>(&self, req: &axum::http::Request<T>) -> Result<Self::Key, GovernorError> {
let peer_ip = req
.extensions()
.get::<axum::extract::ConnectInfo<std::net::SocketAddr>>()
.map(|ci| ci.0.ip())
.ok_or(GovernorError::UnableToExtractKey)?;
let client_ip = resolve_client_ip(peer_ip, req.headers(), &self.trusted_proxies);
debug_assert!(
client_ip.is_ipv4() || client_ip.is_ipv6(),
"resolved IP must be valid"
);
Ok(client_ip)
}
}
pub fn auth_rate_limiter(
config: &RateLimitConfig,
trusted_proxies: TrustedProxies,
) -> tower_governor::GovernorLayer<
TrustedProxyKeyExtractor,
governor::middleware::StateInformationMiddleware,
axum::body::Body,
> {
let gov_config = GovernorConfigBuilder::default()
.key_extractor(TrustedProxyKeyExtractor::new(trusted_proxies))
.per_millisecond(rps_to_period(config.auth_rps))
.burst_size(config.auth_burst)
.use_headers()
.finish()
.expect("failed to build auth rate limiter: invalid RateLimitConfig");
tower_governor::GovernorLayer::new(gov_config)
}
pub fn upload_rate_limiter(
config: &RateLimitConfig,
) -> tower_governor::GovernorLayer<
SmartIpKeyExtractor,
governor::middleware::StateInformationMiddleware,
axum::body::Body,
> {
let gov_config = GovernorConfigBuilder::default()
.key_extractor(SmartIpKeyExtractor)
.per_millisecond(rps_to_period(config.upload_rps))
.burst_size(config.upload_burst)
.use_headers()
.finish()
.expect("failed to build upload rate limiter: invalid RateLimitConfig");
tower_governor::GovernorLayer::new(gov_config)
}
pub fn general_rate_limiter(
config: &RateLimitConfig,
) -> tower_governor::GovernorLayer<
SmartIpKeyExtractor,
governor::middleware::StateInformationMiddleware,
axum::body::Body,
> {
let gov_config = GovernorConfigBuilder::default()
.key_extractor(SmartIpKeyExtractor)
.per_millisecond(rps_to_period(config.general_rps))
.burst_size(config.general_burst)
.use_headers()
.finish()
.expect("failed to build general rate limiter: invalid RateLimitConfig");
tower_governor::GovernorLayer::new(gov_config)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::RateLimitConfig;
use std::net::SocketAddr;
#[test]
fn test_default_config() {
let config = RateLimitConfig::default();
assert_eq!(config.auth_rps, 1);
assert_eq!(config.auth_burst, 5);
assert_eq!(config.upload_rps, 200);
assert_eq!(config.general_rps, 100);
}
#[test]
fn test_auth_rate_limiter_creation() {
let config = RateLimitConfig::default();
let _limiter = auth_rate_limiter(&config, TrustedProxies::default_loopback());
}
#[test]
fn test_upload_rate_limiter_creation() {
let config = RateLimitConfig::default();
let _limiter = upload_rate_limiter(&config);
}
#[test]
fn test_general_rate_limiter_creation() {
let config = RateLimitConfig::default();
let _limiter = general_rate_limiter(&config);
}
#[test]
fn test_custom_config() {
let config = RateLimitConfig {
enabled: true,
auth_rps: 10,
auth_burst: 20,
upload_rps: 500,
upload_burst: 1000,
general_rps: 200,
general_burst: 400,
};
let _auth = auth_rate_limiter(&config, TrustedProxies::default_loopback());
let _upload = upload_rate_limiter(&config);
let _general = general_rate_limiter(&config);
}
#[test]
fn test_rps_to_period() {
assert_eq!(rps_to_period(1), 1000);
assert_eq!(rps_to_period(200), 5);
assert_eq!(rps_to_period(100), 10);
assert_eq!(rps_to_period(10), 100);
assert_eq!(rps_to_period(1000), 1);
assert_eq!(rps_to_period(2000), 1);
assert_eq!(rps_to_period(10000), 1);
}
#[test]
fn test_trusted_proxy_extractor_uses_peer_for_untrusted() {
use axum::http::Request;
let proxies = TrustedProxies::parse("10.0.0.0/8");
let extractor = TrustedProxyKeyExtractor::new(proxies);
let mut req = Request::builder().body(()).unwrap();
req.extensions_mut()
.insert(axum::extract::ConnectInfo(SocketAddr::from((
[192, 168, 1, 1],
1234,
))));
req.headers_mut()
.insert("x-forwarded-for", "1.2.3.4".parse().unwrap());
let key = extractor.extract(&req).unwrap();
assert_eq!(key, IpAddr::from([192, 168, 1, 1]));
}
#[test]
fn test_trusted_proxy_extractor_uses_xff_for_trusted() {
use axum::http::Request;
let proxies = TrustedProxies::parse("10.0.0.0/8");
let extractor = TrustedProxyKeyExtractor::new(proxies);
let mut req = Request::builder().body(()).unwrap();
req.extensions_mut()
.insert(axum::extract::ConnectInfo(SocketAddr::from((
[10, 0, 0, 1],
1234,
))));
req.headers_mut()
.insert("x-forwarded-for", "5.6.7.8".parse().unwrap());
let key = extractor.extract(&req).unwrap();
assert_eq!(key, IpAddr::from([5, 6, 7, 8]));
}
#[test]
fn test_trusted_proxy_extractor_no_connect_info() {
use axum::http::Request;
let proxies = TrustedProxies::default_loopback();
let extractor = TrustedProxyKeyExtractor::new(proxies);
let req = Request::builder().body(()).unwrap();
let result = extractor.extract(&req);
assert!(result.is_err());
}
}