tako_rs_extractors/
ipaddr.rs1use std::net::IpAddr as StdIpAddr;
30use std::net::SocketAddr;
31use std::str::FromStr;
32
33use http::StatusCode;
34use http::request::Parts;
35use tako_rs_core::conn_info::ConnInfo;
36use tako_rs_core::conn_info::PeerAddr;
37use tako_rs_core::extractors::FromRequest;
38use tako_rs_core::extractors::FromRequestParts;
39use tako_rs_core::responder::Responder;
40use tako_rs_core::types::Request;
41
42#[derive(Debug, Clone, PartialEq)]
71#[doc(alias = "ip")]
72#[doc(alias = "ipaddr")]
73pub struct IpAddr(pub StdIpAddr);
74
75#[derive(Debug, Clone, Default)]
78pub struct IpAddrConfig {
79 pub trusted_proxies: Vec<StdIpAddr>,
82}
83
84impl IpAddrConfig {
85 pub fn new() -> Self {
87 Self::default()
88 }
89
90 pub fn trust(mut self, ip: StdIpAddr) -> Self {
92 self.trusted_proxies.push(ip);
93 self
94 }
95
96 pub fn with_trusted_proxies(mut self, ips: Vec<StdIpAddr>) -> Self {
98 self.trusted_proxies = ips;
99 self
100 }
101}
102
103#[derive(Debug)]
105pub enum IpAddrError {
106 NoIpFound,
108 InvalidIpFormat(String),
110 HeaderParseError,
112}
113
114impl Responder for IpAddrError {
115 fn into_response(self) -> tako_rs_core::types::Response {
117 match self {
118 IpAddrError::NoIpFound => (
119 StatusCode::BAD_REQUEST,
120 "No valid IP address found in request headers",
121 )
122 .into_response(),
123 IpAddrError::InvalidIpFormat(ip) => (
124 StatusCode::BAD_REQUEST,
125 format!("Invalid IP address format: {ip}"),
126 )
127 .into_response(),
128 IpAddrError::HeaderParseError => (
129 StatusCode::BAD_REQUEST,
130 "Failed to parse IP address from headers",
131 )
132 .into_response(),
133 }
134 }
135}
136
137impl IpAddr {
138 pub fn new(addr: StdIpAddr) -> Self {
140 Self(addr)
141 }
142
143 pub fn inner(&self) -> StdIpAddr {
145 self.0
146 }
147
148 pub fn is_ipv4(&self) -> bool {
150 self.0.is_ipv4()
151 }
152
153 pub fn is_ipv6(&self) -> bool {
155 self.0.is_ipv6()
156 }
157
158 pub fn is_loopback(&self) -> bool {
160 self.0.is_loopback()
161 }
162
163 pub fn is_private(&self) -> bool {
176 match self.0 {
177 StdIpAddr::V4(ipv4) => ipv4.is_private(),
178 StdIpAddr::V6(ipv6) => {
179 let segments = ipv6.segments();
181 (segments[0] & 0xfe00) == 0xfc00 ||
183 (segments[0] & 0xffc0) == 0xfe80 ||
185 ipv6.is_loopback()
187 }
188 }
189 }
190
191 fn extract_from(
195 extensions: &http::Extensions,
196 headers: &http::HeaderMap,
197 ) -> Result<Self, IpAddrError> {
198 let peer = peer_ip_from_extensions(extensions);
199
200 let cfg = tako_rs_core::state::get_state::<IpAddrConfig>();
201 let trust_headers = match (peer.as_ref(), cfg.as_ref()) {
202 (Some(p), Some(cfg)) => cfg.trusted_proxies.iter().any(|t| t == p),
203 _ => false,
204 };
205
206 if trust_headers
207 && let Some(cfg) = cfg.as_ref()
208 && let Some(ip) = Self::parse_forwarded_headers(headers, &cfg.trusted_proxies)
209 {
210 return Ok(Self(ip));
211 }
212
213 peer.map(Self).ok_or(IpAddrError::NoIpFound)
214 }
215
216 fn parse_forwarded_headers(
229 headers: &http::HeaderMap,
230 trusted_proxies: &[StdIpAddr],
231 ) -> Option<StdIpAddr> {
232 const MULTI_HOP: &[&str] = &["forwarded", "x-forwarded-for"];
233 const SINGLE_HOP: &[&str] = &[
234 "x-real-ip",
235 "x-client-ip",
236 "cf-connecting-ip",
237 "true-client-ip",
238 ];
239 for header_name in MULTI_HOP {
240 if let Some(v) = headers.get(*header_name)
241 && let Ok(s) = v.to_str()
242 && let Some(ip) = Self::parse_ip_right_to_left(s, trusted_proxies)
243 {
244 return Some(ip);
245 }
246 }
247 for header_name in SINGLE_HOP {
248 if let Some(v) = headers.get(*header_name)
249 && let Ok(s) = v.to_str()
250 && let Some(ip) = Self::parse_ip_from_header(s)
251 {
252 return Some(ip);
253 }
254 }
255 None
256 }
257
258 fn parse_ip_right_to_left(
262 header_value: &str,
263 trusted_proxies: &[StdIpAddr],
264 ) -> Option<StdIpAddr> {
265 let parts: Vec<&str> = header_value.split(',').collect();
266 for part in parts.iter().rev() {
267 let trimmed = part.trim();
268 if trimmed.is_empty() {
269 continue;
270 }
271 let Some(ip) = Self::parse_ip_from_part(trimmed) else {
275 continue;
276 };
277 if !trusted_proxies.contains(&ip) {
278 return Some(ip);
279 }
280 }
281 None
282 }
283
284 fn parse_ip_from_header(header_value: &str) -> Option<StdIpAddr> {
287 for part in header_value.split(',') {
288 let part = part.trim();
289 if part.is_empty() {
290 continue;
291 }
292 if let Some(ip) = Self::parse_ip_from_part(part) {
293 return Some(ip);
294 }
295 }
296 None
297 }
298
299 fn parse_ip_from_part(part: &str) -> Option<StdIpAddr> {
302 if part.is_empty() {
303 return None;
304 }
305 let ip_part = part.strip_prefix("for=").unwrap_or(part);
306 let ip_part = ip_part.trim_matches('"');
307
308 let ip_str = if ip_part.starts_with('[') {
309 if let Some(end) = ip_part.find(']') {
310 &ip_part[1..end]
311 } else {
312 ip_part
313 }
314 } else if ip_part.matches(':').count() == 1 {
315 ip_part.split(':').next().unwrap_or(ip_part)
316 } else {
317 ip_part
318 };
319
320 StdIpAddr::from_str(ip_str).ok()
321 }
322}
323
324fn peer_ip_from_extensions(ext: &http::Extensions) -> Option<StdIpAddr> {
325 if let Some(info) = ext.get::<ConnInfo>()
326 && let PeerAddr::Ip(sa) = &info.peer
327 {
328 return Some(sa.ip());
329 }
330 if let Some(sa) = ext.get::<SocketAddr>() {
331 return Some(sa.ip());
332 }
333 None
334}
335
336impl std::fmt::Display for IpAddr {
337 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
338 write!(f, "{}", self.0)
339 }
340}
341
342impl From<StdIpAddr> for IpAddr {
343 fn from(addr: StdIpAddr) -> Self {
344 Self(addr)
345 }
346}
347
348impl From<IpAddr> for StdIpAddr {
349 fn from(addr: IpAddr) -> Self {
350 addr.0
351 }
352}
353
354impl<'a> FromRequest<'a> for IpAddr {
355 type Error = IpAddrError;
356
357 fn from_request(
358 req: &'a mut Request,
359 ) -> impl core::future::Future<Output = core::result::Result<Self, Self::Error>> + Send + 'a {
360 futures_util::future::ready(Self::extract_from(req.extensions(), req.headers()))
361 }
362}
363
364impl<'a> FromRequestParts<'a> for IpAddr {
365 type Error = IpAddrError;
366
367 fn from_request_parts(
368 parts: &'a mut Parts,
369 ) -> impl core::future::Future<Output = core::result::Result<Self, Self::Error>> + Send + 'a {
370 futures_util::future::ready(Self::extract_from(&parts.extensions, &parts.headers))
371 }
372}