1use std::net::IpAddr;
10use std::str::FromStr;
11use std::sync::Once;
12
13use axum::http::HeaderMap;
14use ipnet::IpNet;
15use thiserror::Error;
16use tracing::warn;
17
18#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
33pub enum ClientIpHeader {
34 #[default]
36 XForwardedFor,
37 XRealIp,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq, Error)]
53#[error("invalid client IP header {0:?}: expected x-forwarded-for or x-real-ip")]
54pub struct InvalidClientIpHeader(pub String);
55
56impl FromStr for ClientIpHeader {
57 type Err = InvalidClientIpHeader;
58
59 fn from_str(s: &str) -> Result<Self, Self::Err> {
61 match s.trim().to_ascii_lowercase().as_str() {
62 "x-forwarded-for" => Ok(Self::XForwardedFor),
63 "x-real-ip" => Ok(Self::XRealIp),
64 _ => Err(InvalidClientIpHeader(s.to_string())),
65 }
66 }
67}
68
69#[derive(Debug, Clone, Default, PartialEq, Eq)]
92pub struct TrustedProxies {
93 nets: Vec<IpNet>,
94 header: ClientIpHeader,
95}
96
97impl TrustedProxies {
98 pub fn contains(&self, ip: IpAddr) -> bool {
112 let ip = ip.to_canonical();
113 self.nets.iter().any(|net| net.contains(&ip))
114 }
115
116 pub fn is_empty(&self) -> bool {
126 self.nets.is_empty()
127 }
128
129 pub fn with_header(mut self, header: ClientIpHeader) -> Self {
144 self.header = header;
145 self
146 }
147
148 pub fn header(&self) -> ClientIpHeader {
158 self.header
159 }
160}
161
162#[derive(Debug, Clone, PartialEq, Eq, Error)]
173#[error("invalid trusted proxy {0:?}: expected an IP address or a CIDR range")]
174pub struct InvalidTrustedProxy(pub String);
175
176impl FromStr for TrustedProxies {
177 type Err = InvalidTrustedProxy;
178
179 fn from_str(s: &str) -> Result<Self, Self::Err> {
182 s.split(',')
183 .map(str::trim)
184 .filter(|entry| !entry.is_empty())
185 .map(|entry| {
186 entry
187 .parse::<IpNet>()
188 .or_else(|_| entry.parse::<IpAddr>().map(IpNet::from))
189 .map_err(|_| InvalidTrustedProxy(entry.to_string()))
190 })
191 .collect::<Result<Vec<_>, _>>()
192 .map(|nets| TrustedProxies {
193 nets,
194 header: ClientIpHeader::default(),
195 })
196 }
197}
198
199static MISSING_PEER_WARNING: Once = Once::new();
200
201pub(super) fn client_ip(
209 peer: Option<IpAddr>,
210 headers: &HeaderMap,
211 trusted: &TrustedProxies,
212) -> Option<IpAddr> {
213 let Some(peer) = peer.map(|ip| ip.to_canonical()) else {
214 MISSING_PEER_WARNING.call_once(|| {
215 warn!(
216 "rate limiter cannot see the client address: the IP limit is skipped \
217 until the router is served with \
218 into_make_service_with_connect_info::<SocketAddr>()"
219 );
220 });
221 return None;
222 };
223 if !trusted.contains(peer) {
224 return Some(peer);
225 }
226
227 let client = match trusted.header() {
228 ClientIpHeader::XForwardedFor => forwarded_for_client(peer, headers, trusted),
229 ClientIpHeader::XRealIp => headers
230 .get("x-real-ip")
231 .and_then(|value| value.to_str().ok())
232 .and_then(|value| value.trim().parse::<IpAddr>().ok())
233 .map_or(peer, |ip| ip.to_canonical()),
234 };
235 Some(client)
236}
237
238fn forwarded_for_client(peer: IpAddr, headers: &HeaderMap, trusted: &TrustedProxies) -> IpAddr {
241 let hops: Vec<&str> = headers
242 .get_all("x-forwarded-for")
243 .iter()
244 .filter_map(|value| value.to_str().ok())
245 .flat_map(|value| value.split(','))
246 .map(str::trim)
247 .collect();
248
249 let mut client = peer;
250 for hop in hops.iter().rev() {
251 let Ok(ip) = hop.parse::<IpAddr>() else {
254 return client;
255 };
256 let ip = ip.to_canonical();
257 if !trusted.contains(ip) {
258 return ip;
259 }
260 client = ip;
261 }
262 client
263}
264
265#[cfg(test)]
266mod tests {
267 use axum::http::HeaderValue;
268
269 use super::*;
270
271 fn ip(s: &str) -> IpAddr {
272 s.parse().unwrap()
273 }
274
275 fn headers(pairs: &[(&'static str, &str)]) -> HeaderMap {
276 let mut map = HeaderMap::new();
277 for (name, value) in pairs {
278 map.append(*name, HeaderValue::from_str(value).unwrap());
279 }
280 map
281 }
282
283 fn proxies(s: &str) -> TrustedProxies {
284 s.parse().unwrap()
285 }
286
287 fn real_ip_proxies(s: &str) -> TrustedProxies {
288 proxies(s).with_header(ClientIpHeader::XRealIp)
289 }
290
291 #[test]
292 fn untrusted_peer_ignores_forwarding_headers() {
293 let h = headers(&[("x-forwarded-for", "9.9.9.9"), ("x-real-ip", "8.8.8.8")]);
294 assert_eq!(
295 client_ip(Some(ip("203.0.113.7")), &h, &TrustedProxies::default()),
296 Some(ip("203.0.113.7"))
297 );
298 assert_eq!(
299 client_ip(Some(ip("203.0.113.7")), &h, &proxies("10.0.0.0/8")),
300 Some(ip("203.0.113.7"))
301 );
302 assert_eq!(
303 client_ip(Some(ip("203.0.113.7")), &h, &real_ip_proxies("10.0.0.0/8")),
304 Some(ip("203.0.113.7"))
305 );
306 }
307
308 #[test]
309 fn trusted_peer_uses_the_last_forwarded_hop() {
310 let h = headers(&[("x-forwarded-for", "6.6.6.6, 198.51.100.4")]);
311 assert_eq!(
312 client_ip(Some(ip("10.0.0.2")), &h, &proxies("10.0.0.0/8")),
313 Some(ip("198.51.100.4"))
314 );
315 }
316
317 #[test]
318 fn trusted_hops_are_skipped_from_the_right() {
319 let h = headers(&[("x-forwarded-for", "6.6.6.6, 198.51.100.4, 10.0.0.9")]);
320 assert_eq!(
321 client_ip(Some(ip("10.0.0.2")), &h, &proxies("10.0.0.0/8")),
322 Some(ip("198.51.100.4"))
323 );
324 }
325
326 #[test]
327 fn all_hops_trusted_gives_the_leftmost() {
328 let h = headers(&[("x-forwarded-for", "10.0.0.8, 10.0.0.9")]);
329 assert_eq!(
330 client_ip(Some(ip("10.0.0.2")), &h, &proxies("10.0.0.0/8")),
331 Some(ip("10.0.0.8"))
332 );
333 }
334
335 #[test]
336 fn unparsable_hop_stops_at_the_last_trusted_hop() {
337 let h = headers(&[("x-forwarded-for", "198.51.100.4, garbage, 10.0.0.9")]);
338 assert_eq!(
339 client_ip(Some(ip("10.0.0.2")), &h, &proxies("10.0.0.0/8")),
340 Some(ip("10.0.0.9"))
341 );
342 }
343
344 #[test]
345 fn several_forwarded_for_headers_are_read_in_order() {
346 let h = headers(&[
347 ("x-forwarded-for", "6.6.6.6"),
348 ("x-forwarded-for", "198.51.100.4"),
349 ]);
350 assert_eq!(
351 client_ip(Some(ip("10.0.0.2")), &h, &proxies("10.0.0.0/8")),
352 Some(ip("198.51.100.4"))
353 );
354 }
355
356 #[test]
357 fn x_forwarded_for_mode_ignores_x_real_ip() {
358 let h = headers(&[("x-real-ip", "198.51.100.4")]);
359 assert_eq!(
360 client_ip(Some(ip("10.0.0.2")), &h, &proxies("10.0.0.0/8")),
361 Some(ip("10.0.0.2"))
362 );
363 }
364
365 #[test]
366 fn x_real_ip_mode_reads_x_real_ip() {
367 let h = headers(&[("x-real-ip", " 198.51.100.4 ")]);
368 assert_eq!(
369 client_ip(Some(ip("10.0.0.2")), &h, &real_ip_proxies("10.0.0.0/8")),
370 Some(ip("198.51.100.4"))
371 );
372 }
373
374 #[test]
375 fn x_real_ip_mode_ignores_x_forwarded_for() {
376 let h = headers(&[("x-forwarded-for", "198.51.100.4")]);
377 assert_eq!(
378 client_ip(Some(ip("10.0.0.2")), &h, &real_ip_proxies("10.0.0.0/8")),
379 Some(ip("10.0.0.2"))
380 );
381 let h = headers(&[
382 ("x-forwarded-for", "198.51.100.4"),
383 ("x-real-ip", "198.51.100.5"),
384 ]);
385 assert_eq!(
386 client_ip(Some(ip("10.0.0.2")), &h, &real_ip_proxies("10.0.0.0/8")),
387 Some(ip("198.51.100.5"))
388 );
389 }
390
391 #[test]
392 fn x_real_ip_mode_with_an_unparsable_value_gives_the_peer() {
393 let h = headers(&[("x-real-ip", "not-an-ip")]);
394 assert_eq!(
395 client_ip(Some(ip("10.0.0.2")), &h, &real_ip_proxies("10.0.0.0/8")),
396 Some(ip("10.0.0.2"))
397 );
398 }
399
400 #[test]
401 fn trusted_peer_without_headers_is_the_client() {
402 assert_eq!(
403 client_ip(
404 Some(ip("10.0.0.2")),
405 &HeaderMap::new(),
406 &proxies("10.0.0.0/8")
407 ),
408 Some(ip("10.0.0.2"))
409 );
410 }
411
412 #[test]
413 fn ipv4_mapped_peer_is_canonicalized() {
414 let h = headers(&[("x-forwarded-for", "198.51.100.4")]);
415 assert_eq!(
416 client_ip(Some(ip("::ffff:10.0.0.2")), &h, &proxies("10.0.0.2")),
417 Some(ip("198.51.100.4"))
418 );
419 assert_eq!(
420 client_ip(
421 Some(ip("::ffff:203.0.113.7")),
422 &h,
423 &TrustedProxies::default()
424 ),
425 Some(ip("203.0.113.7"))
426 );
427 }
428
429 #[test]
430 fn missing_peer_has_no_client_ip() {
431 let h = headers(&[("x-forwarded-for", "9.9.9.9"), ("x-real-ip", "8.8.8.8")]);
432 assert_eq!(client_ip(None, &h, &proxies("0.0.0.0/0")), None);
433 assert_eq!(client_ip(None, &h, &real_ip_proxies("0.0.0.0/0")), None);
434 }
435
436 #[test]
437 fn parses_addresses_and_ranges() {
438 let parsed = proxies(" 10.0.0.0/8 ,192.168.1.10, fd00::/8 ,");
439 assert!(parsed.contains(ip("10.255.0.1")));
440 assert!(parsed.contains(ip("192.168.1.10")));
441 assert!(!parsed.contains(ip("192.168.1.11")));
442 assert!(parsed.contains(ip("fd12::1")));
443 assert_eq!(parsed.header(), ClientIpHeader::XForwardedFor);
444 }
445
446 #[test]
447 fn empty_string_trusts_no_proxy() {
448 assert!(proxies("").is_empty());
449 assert!(proxies(" , ").is_empty());
450 }
451
452 #[test]
453 fn invalid_entry_is_reported() {
454 assert_eq!(
455 "10.0.0.0/8,10.0.0.0/33".parse::<TrustedProxies>(),
456 Err(InvalidTrustedProxy("10.0.0.0/33".to_string()))
457 );
458 assert_eq!(
459 "localhost".parse::<TrustedProxies>(),
460 Err(InvalidTrustedProxy("localhost".to_string()))
461 );
462 }
463
464 #[test]
465 fn client_ip_header_parses_case_insensitively() {
466 for (raw, expected) in [
467 ("x-forwarded-for", ClientIpHeader::XForwardedFor),
468 (" X-Forwarded-For ", ClientIpHeader::XForwardedFor),
469 ("x-real-ip", ClientIpHeader::XRealIp),
470 ("X-REAL-IP", ClientIpHeader::XRealIp),
471 ] {
472 assert_eq!(raw.parse::<ClientIpHeader>(), Ok(expected), "{raw:?}");
473 }
474 }
475
476 #[test]
477 fn invalid_client_ip_header_is_reported() {
478 for raw in ["", "forwarded", "x-forwarded-for,x-real-ip"] {
479 assert_eq!(
480 raw.parse::<ClientIpHeader>(),
481 Err(InvalidClientIpHeader(raw.to_string()))
482 );
483 }
484 }
485}