pacenotes_server/reports/
client_address.rs1pub fn client_ip(
8 peer: std::net::IpAddr,
9 headers: &axum::http::HeaderMap,
10 trusted: &[super::Cidr],
11) -> std::net::IpAddr {
12 let peer = peer.to_canonical();
13 let is_trusted = |ip: std::net::IpAddr| trusted.iter().any(|network| network.contains(ip));
14 if !is_trusted(peer) {
15 return peer;
16 }
17 let hops: Vec<&str> = headers
18 .get_all("x-forwarded-for")
19 .iter()
20 .filter_map(|value| value.to_str().ok())
21 .flat_map(|value| value.split(','))
22 .map(str::trim)
23 .collect();
24 for hop in hops.into_iter().rev() {
25 match hop.parse::<std::net::IpAddr>() {
26 Ok(ip) if is_trusted(ip.to_canonical()) => continue,
27 Ok(ip) => return ip.to_canonical(),
28 Err(_) => return peer,
29 }
30 }
31 peer
32}
33
34#[cfg(test)]
35mod tests {
36 fn forwarded_for(value: &str) -> axum::http::HeaderMap {
37 let mut headers = axum::http::HeaderMap::new();
38 headers.insert(
39 "x-forwarded-for",
40 axum::http::HeaderValue::from_str(value).unwrap(),
41 );
42 headers
43 }
44
45 fn ip(text: &str) -> std::net::IpAddr {
46 text.parse().unwrap()
47 }
48
49 fn trusted() -> Vec<super::super::Cidr> {
50 vec![super::super::Cidr::parse("10.0.0.0/24").unwrap()]
51 }
52
53 const CADDY: &str = "10.0.0.90";
54
55 #[test]
56 fn through_a_trusted_proxy_the_client_is_the_entry_it_appended() {
57 let client = super::client_ip(ip(CADDY), &forwarded_for("198.51.100.1"), &trusted());
58 assert_eq!(client, ip("198.51.100.1"));
59 }
60
61 #[test]
62 fn a_forged_entry_left_of_the_proxys_is_ignored() {
63 let headers = forwarded_for("1.1.1.1, 198.51.100.1");
64 assert_eq!(
65 super::client_ip(ip(CADDY), &headers, &trusted()),
66 ip("198.51.100.1")
67 );
68 }
69
70 #[test]
71 fn the_header_from_anyone_else_is_ignored() {
72 let stranger = ip("203.0.113.7");
73 let headers = forwarded_for("1.1.1.1");
74 assert_eq!(super::client_ip(stranger, &headers, &trusted()), stranger);
75 assert_eq!(
76 super::client_ip(ip(CADDY), &forwarded_for("198.51.100.1"), &[]),
77 ip(CADDY)
78 );
79 }
80
81 #[test]
82 fn garbage_where_the_client_belongs_falls_back_to_the_peer() {
83 let headers = forwarded_for("1.1.1.1, unknown");
84 assert_eq!(super::client_ip(ip(CADDY), &headers, &trusted()), ip(CADDY));
85 }
86}