systemprompt_api/services/middleware/
client_addr.rs1use std::net::{IpAddr, SocketAddr};
25
26use axum::extract::ConnectInfo;
27use axum::http::HeaderMap;
28use ipnet::IpNet;
29
30#[must_use]
31pub fn parse_trusted_proxies(raw: &[String]) -> Vec<IpNet> {
32 raw.iter()
33 .filter_map(|s| {
34 let trimmed = s.trim();
35 if trimmed.is_empty() {
36 return None;
37 }
38 if let Ok(net) = trimmed.parse::<IpNet>() {
39 return Some(net);
40 }
41 if let Ok(addr) = trimmed.parse::<IpAddr>() {
42 let prefix = match addr {
43 IpAddr::V4(_) => 32,
44 IpAddr::V6(_) => 128,
45 };
46 if let Ok(net) = IpNet::new(addr, prefix) {
47 return Some(net);
48 }
49 }
50 tracing::warn!(entry = %trimmed, "ignoring invalid trusted_proxies entry");
51 None
52 })
53 .collect()
54}
55
56fn is_trusted(addr: IpAddr, trusted: &[IpNet]) -> bool {
57 trusted.iter().any(|net| net.contains(&addr))
58}
59
60#[must_use]
61pub fn resolve_client_ip(
62 headers: &HeaderMap,
63 connect_info: Option<&ConnectInfo<SocketAddr>>,
64 trusted: &[IpNet],
65) -> Option<IpAddr> {
66 let peer_ip = connect_info.map(|c| c.0.ip())?;
67
68 if !is_trusted(peer_ip, trusted) {
69 return Some(peer_ip);
70 }
71
72 if let Some(xff) = headers.get("x-forwarded-for").and_then(|v| v.to_str().ok()) {
73 let hops: Vec<&str> = xff
74 .split(',')
75 .map(str::trim)
76 .filter(|s| !s.is_empty())
77 .collect();
78 for hop in hops.iter().rev() {
79 if let Ok(addr) = hop.parse::<IpAddr>()
80 && !is_trusted(addr, trusted)
81 {
82 return Some(addr);
83 }
84 }
85 }
86
87 for header in ["x-real-ip", "cf-connecting-ip"] {
88 if let Some(raw) = headers.get(header).and_then(|v| v.to_str().ok())
89 && let Ok(addr) = raw.trim().parse::<IpAddr>()
90 && !is_trusted(addr, trusted)
91 {
92 return Some(addr);
93 }
94 }
95
96 Some(peer_ip)
97}