1use core::fmt::Debug;
2use core::hash::Hash;
3use std::net::IpAddr;
4
5use rama_core::error::BoxError;
6use rama_core::extensions::ExtensionsRef;
7
8use crate::address::ip::ipnet::IpNet;
9
10pub trait RateKey: Hash + Eq + Clone + Debug + Send + Sync + 'static {
13 #[cfg(feature = "opentelemetry")]
15 fn attributes(
16 &self,
17 ) -> impl Iterator<Item = rama_core::telemetry::opentelemetry::KeyValue> + '_ {
18 core::iter::empty()
19 }
20}
21
22impl RateKey for IpAddr {
23 #[cfg(feature = "opentelemetry")]
24 fn attributes(
25 &self,
26 ) -> impl Iterator<Item = rama_core::telemetry::opentelemetry::KeyValue> + '_ {
27 use rama_core::telemetry::opentelemetry::{KeyValue, semantic_conventions};
28 core::iter::once(KeyValue::new(
29 semantic_conventions::attribute::CLIENT_ADDRESS,
30 self.to_string(),
31 ))
32 }
33}
34
35impl RateKey for String {}
36impl RateKey for u64 {}
37
38#[cfg(test)]
39mod tests {
40 use super::*;
41 use rama_core::extensions::Extensions;
42
43 fn ip(s: &str) -> IpAddr {
44 s.parse().unwrap()
45 }
46
47 #[test]
48 fn ipv4_mapped_ipv6_collapses_to_ipv4() {
49 let key = ClientIpRateKey::new();
50 assert_eq!(key.key_for(ip("::ffff:203.0.113.5")), ip("203.0.113.5"));
52 assert_eq!(
53 key.key_for(ip("::ffff:203.0.113.5")),
54 key.key_for(ip("203.0.113.5"))
55 );
56 }
57
58 #[test]
59 fn ipv6_aggregates_to_prefix() {
60 let key = ClientIpRateKey::new(); assert_eq!(
63 key.key_for(ip("2001:db8:1:2::1")),
64 key.key_for(ip("2001:db8:1:2:ffff:ffff:ffff:ffff"))
65 );
66 assert_eq!(key.key_for(ip("2001:db8:1:2::1")), ip("2001:db8:1:2::"));
67 assert_ne!(
69 key.key_for(ip("2001:db8:1:2::1")),
70 key.key_for(ip("2001:db8:1:3::1"))
71 );
72 }
73
74 #[test]
75 fn ipv6_prefix_128_keys_exact_address() {
76 let key = ClientIpRateKey::new().with_ipv6_prefix(128);
77 assert_eq!(key.key_for(ip("2001:db8:1:2::1")), ip("2001:db8:1:2::1"));
78 assert_ne!(
79 key.key_for(ip("2001:db8:1:2::1")),
80 key.key_for(ip("2001:db8:1:2::2"))
81 );
82 }
83
84 #[test]
85 fn ipv4_is_never_aggregated() {
86 let key = ClientIpRateKey::new().with_ipv6_prefix(1);
87 assert_eq!(key.key_for(ip("203.0.113.5")), ip("203.0.113.5"));
88 }
89
90 #[test]
91 fn ipv6_prefix_is_clamped() {
92 assert_eq!(ClientIpRateKey::new().with_ipv6_prefix(0).ipv6_prefix, 1);
93 assert_eq!(
94 ClientIpRateKey::new().with_ipv6_prefix(200).ipv6_prefix,
95 128
96 );
97 }
98
99 #[test]
100 fn extractor_reads_and_canonicalises_client_ip() {
101 use crate::address::SocketAddress;
102 use crate::stream::SocketInfo;
103
104 let ext = Extensions::new();
105 ext.insert(SocketInfo::new(
106 None,
107 SocketAddress::new(ip("::ffff:203.0.113.5"), 0),
108 ));
109 let got = ClientIpRateKey::new().rate_key(&ext).unwrap();
110 assert_eq!(got, Some(ip("203.0.113.5")));
111 }
112}
113
114pub trait InputToRateKey<Input>: Send + Sync + 'static {
123 type Key: RateKey;
125
126 fn rate_key(&self, input: &Input) -> Result<Option<Self::Key>, BoxError>;
128}
129
130impl<Input, K, F> InputToRateKey<Input> for F
131where
132 F: Fn(&Input) -> Result<Option<K>, BoxError> + Send + Sync + 'static,
133 K: RateKey,
134{
135 type Key = K;
136
137 fn rate_key(&self, input: &Input) -> Result<Option<Self::Key>, BoxError> {
138 (self)(input)
139 }
140}
141
142const DEFAULT_IPV6_PREFIX: u8 = 64;
145
146#[derive(Debug, Clone, Copy)]
167#[non_exhaustive]
168pub struct ClientIpRateKey {
169 ipv6_prefix: u8,
170}
171
172impl ClientIpRateKey {
173 #[must_use]
175 pub const fn new() -> Self {
176 Self {
177 ipv6_prefix: DEFAULT_IPV6_PREFIX,
178 }
179 }
180
181 rama_utils::macros::generate_set_and_with! {
182 pub fn ipv6_prefix(mut self, prefix: u8) -> Self {
189 self.ipv6_prefix = prefix.clamp(1, 128);
190 self
191 }
192 }
193
194 fn key_for(self, ip: IpAddr) -> IpAddr {
196 match ip.to_canonical() {
197 IpAddr::V6(v6) if self.ipv6_prefix < 128 => {
198 IpNet::new(IpAddr::V6(v6), self.ipv6_prefix)
199 .map(|net| net.trunc().addr())
200 .unwrap_or(IpAddr::V6(v6))
201 }
202 canonical => canonical,
203 }
204 }
205}
206
207impl Default for ClientIpRateKey {
208 fn default() -> Self {
209 Self::new()
210 }
211}
212
213impl<Input> InputToRateKey<Input> for ClientIpRateKey
214where
215 Input: ExtensionsRef + Send + Sync + 'static,
216{
217 type Key = IpAddr;
218
219 fn rate_key(&self, input: &Input) -> Result<Option<Self::Key>, BoxError> {
220 Ok(crate::client_ip::client_ip(input).map(|ip| self.key_for(ip)))
221 }
222}