use core::fmt::Debug;
use core::hash::Hash;
use std::net::IpAddr;
use rama_core::error::BoxError;
use rama_core::extensions::ExtensionsRef;
use crate::address::ip::ipnet::IpNet;
pub trait RateKey: Hash + Eq + Clone + Debug + Send + Sync + 'static {
#[cfg(feature = "opentelemetry")]
fn attributes(
&self,
) -> impl Iterator<Item = rama_core::telemetry::opentelemetry::KeyValue> + '_ {
core::iter::empty()
}
}
impl RateKey for IpAddr {
#[cfg(feature = "opentelemetry")]
fn attributes(
&self,
) -> impl Iterator<Item = rama_core::telemetry::opentelemetry::KeyValue> + '_ {
use rama_core::telemetry::opentelemetry::{KeyValue, semantic_conventions};
core::iter::once(KeyValue::new(
semantic_conventions::attribute::CLIENT_ADDRESS,
self.to_string(),
))
}
}
impl RateKey for String {}
impl RateKey for u64 {}
#[cfg(test)]
mod tests {
use super::*;
use rama_core::extensions::Extensions;
fn ip(s: &str) -> IpAddr {
s.parse().unwrap()
}
#[test]
fn ipv4_mapped_ipv6_collapses_to_ipv4() {
let key = ClientIpRateKey::new();
assert_eq!(key.key_for(ip("::ffff:203.0.113.5")), ip("203.0.113.5"));
assert_eq!(
key.key_for(ip("::ffff:203.0.113.5")),
key.key_for(ip("203.0.113.5"))
);
}
#[test]
fn ipv6_aggregates_to_prefix() {
let key = ClientIpRateKey::new(); assert_eq!(
key.key_for(ip("2001:db8:1:2::1")),
key.key_for(ip("2001:db8:1:2:ffff:ffff:ffff:ffff"))
);
assert_eq!(key.key_for(ip("2001:db8:1:2::1")), ip("2001:db8:1:2::"));
assert_ne!(
key.key_for(ip("2001:db8:1:2::1")),
key.key_for(ip("2001:db8:1:3::1"))
);
}
#[test]
fn ipv6_prefix_128_keys_exact_address() {
let key = ClientIpRateKey::new().with_ipv6_prefix(128);
assert_eq!(key.key_for(ip("2001:db8:1:2::1")), ip("2001:db8:1:2::1"));
assert_ne!(
key.key_for(ip("2001:db8:1:2::1")),
key.key_for(ip("2001:db8:1:2::2"))
);
}
#[test]
fn ipv4_is_never_aggregated() {
let key = ClientIpRateKey::new().with_ipv6_prefix(1);
assert_eq!(key.key_for(ip("203.0.113.5")), ip("203.0.113.5"));
}
#[test]
fn ipv6_prefix_is_clamped() {
assert_eq!(ClientIpRateKey::new().with_ipv6_prefix(0).ipv6_prefix, 1);
assert_eq!(
ClientIpRateKey::new().with_ipv6_prefix(200).ipv6_prefix,
128
);
}
#[test]
fn extractor_reads_and_canonicalises_client_ip() {
use crate::address::SocketAddress;
use crate::stream::SocketInfo;
let ext = Extensions::new();
ext.insert(SocketInfo::new(
None,
SocketAddress::new(ip("::ffff:203.0.113.5"), 0),
));
let got = ClientIpRateKey::new().rate_key(&ext).unwrap();
assert_eq!(got, Some(ip("203.0.113.5")));
}
}
pub trait InputToRateKey<Input>: Send + Sync + 'static {
type Key: RateKey;
fn rate_key(&self, input: &Input) -> Result<Option<Self::Key>, BoxError>;
}
impl<Input, K, F> InputToRateKey<Input> for F
where
F: Fn(&Input) -> Result<Option<K>, BoxError> + Send + Sync + 'static,
K: RateKey,
{
type Key = K;
fn rate_key(&self, input: &Input) -> Result<Option<Self::Key>, BoxError> {
(self)(input)
}
}
const DEFAULT_IPV6_PREFIX: u8 = 64;
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct ClientIpRateKey {
ipv6_prefix: u8,
}
impl ClientIpRateKey {
#[must_use]
pub const fn new() -> Self {
Self {
ipv6_prefix: DEFAULT_IPV6_PREFIX,
}
}
rama_utils::macros::generate_set_and_with! {
pub fn ipv6_prefix(mut self, prefix: u8) -> Self {
self.ipv6_prefix = prefix.clamp(1, 128);
self
}
}
fn key_for(self, ip: IpAddr) -> IpAddr {
match ip.to_canonical() {
IpAddr::V6(v6) if self.ipv6_prefix < 128 => {
IpNet::new(IpAddr::V6(v6), self.ipv6_prefix)
.map(|net| net.trunc().addr())
.unwrap_or(IpAddr::V6(v6))
}
canonical => canonical,
}
}
}
impl Default for ClientIpRateKey {
fn default() -> Self {
Self::new()
}
}
impl<Input> InputToRateKey<Input> for ClientIpRateKey
where
Input: ExtensionsRef + Send + Sync + 'static,
{
type Key = IpAddr;
fn rate_key(&self, input: &Input) -> Result<Option<Self::Key>, BoxError> {
Ok(crate::client_ip::client_ip(input).map(|ip| self.key_for(ip)))
}
}