structured_proxy/client_address.rs
1//! The client's address: resolved once per request, before any guard, and
2//! forwarded to the upstream by an explicit policy.
3//!
4//! # Resolution
5//!
6//! The connection's peer is the client, unless it is listed in
7//! [`trusted_proxies`](ClientAddressConfig::trusted_proxies). Forwarding
8//! information is not trustworthy by itself (RFC 7239 §8.1), so only then is
9//! the header the trusted proxy reports in read:
10//!
11//! - `X-Forwarded-For` (the default): every field line, in order, as one list
12//! (RFC 9110 §5.3), walked from the right through the trusted proxies to the
13//! first address outside them. What lies left of that address is the
14//! client's own claim and is never read. When every hop is trusted, the
15//! leftmost is the client.
16//! - `X-Real-IP`, when chosen: one address, the trusted proxy's own peer.
17//!
18//! The result is a [`ClientAddress`] on the request, and every consumer reads
19//! that one value: the rate limits, the auth decider, extra routes, the
20//! fallback and an upstream in process. When nothing can be resolved (no
21//! connection information, or a trusted proxy's report that cannot be read)
22//! the [`Resolution`] says why, and no address is ever made up: a trusted
23//! proxy's own address never stands in for its client's.
24//!
25//! # What the upstream receives
26//!
27//! [`ForwardConfig`](crate::config::ForwardConfig) decides, independently of
28//! the resolution:
29//!
30//! | `x_forwarded_for` | `X-Forwarded-For` upstream | Its first element |
31//! |---|---|---|
32//! | `verified` (default) | the client, the trusted proxies after it, this proxy's peer | the client |
33//! | `resolved` | the client alone | the client |
34//! | `append` | the list as it arrived, then this proxy's peer | whatever the client wrote |
35//! | `preserve` | the list as it arrived (`X-Real-IP` and `Forwarded` too) | whatever the client wrote |
36//! | `remove` | nothing | none |
37//!
38//! Next to it, `client_header` (`X-Real-IP` by default) carries the resolved
39//! address alone, and `audit_header` (off by default) the list exactly as it
40//! arrived, for logs. These headers are the proxy's: the request cannot set
41//! them, and neither can a guard (JWT claim headers, ext_authz, the auth
42//! decider). Native gRPC, gRPC-Web and transcoded calls receive the same
43//! values, remote or in process, and so does the fallback.
44//!
45//! With `append` and `preserve` the first element of `X-Forwarded-For` is
46//! whatever the client chose to write: an upstream should then read the
47//! client header, or walk the list from the right trusting this proxy.
48//!
49//! # Examples
50//!
51//! Behind a load balancer in `10.0.0.0/8`, forwarding as nginx does with
52//! `$proxy_add_x_forwarded_for`, and keeping what arrived for the access log:
53//!
54//! ```yaml
55//! client_address:
56//! trusted_proxies: ["10.0.0.0/8"]
57//! forward:
58//! x_forwarded_for: append
59//! client_header: x-real-ip
60//! audit_header: x-original-forwarded-for
61//! ```
62//!
63//! The same in code, with the address read back in a tonic handler:
64//!
65//! ```
66//! use structured_proxy::client_address::Resolution;
67//! use structured_proxy::config::{ClientAddressConfig, XForwardedFor};
68//! use structured_proxy::{ClientAddress, ProxyServer};
69//!
70//! # fn build() -> anyhow::Result<()> {
71//! let mut client_address = ClientAddressConfig::default();
72//! client_address.trusted_proxies = vec!["10.0.0.0/8".into()];
73//! client_address.forward.x_forwarded_for = XForwardedFor::Append;
74//! client_address.forward.audit_header = Some("x-original-forwarded-for".into());
75//! let service = ProxyServer::new()
76//! .with_client_address(client_address)
77//! .service(tonic::service::Routes::default())?;
78//! # let _ = service;
79//! # Ok(())
80//! # }
81//! # build().unwrap();
82//!
83//! fn caller(request: &tonic::Request<()>) -> String {
84//! match request.extensions().get::<ClientAddress>().map(ClientAddress::resolution) {
85//! Some(Resolution::Peer(ip) | Resolution::Forwarded(ip)) => ip.to_string(),
86//! _ => "unknown".into(),
87//! }
88//! }
89//! # let _ = caller;
90//! ```
91//!
92//! Routers of your own that mount proxy parts such as the
93//! [`shield`](crate::shield) middleware get the same resolution from
94//! [`ClientAddressLayer`].
95
96use std::net::{IpAddr, Ipv6Addr, SocketAddr};
97use std::sync::Arc;
98use std::task::{Context, Poll};
99
100use axum::extract::ConnectInfo;
101use http::header::{HeaderName, FORWARDED};
102use http::{HeaderMap, HeaderValue};
103use ipnet::IpNet;
104use tower::{Layer, Service};
105
106use crate::config::{ClientAddressConfig, ForwardingHeader, XForwardedFor};
107
108/// `X-Forwarded-For`.
109pub(crate) const X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for");
110/// `X-Real-IP`.
111pub(crate) const X_REAL_IP: HeaderName = HeaderName::from_static("x-real-ip");
112
113/// Whether `name` is a header carrying a client's address: `X-Forwarded-For`,
114/// `X-Real-IP` or `Forwarded`. The forwarding policy decides what they hold
115/// upstream, so no guard (claim headers, ext_authz, the auth decider) may set
116/// them.
117pub(crate) fn owns(name: &str) -> bool {
118 name.eq_ignore_ascii_case("x-forwarded-for")
119 || name.eq_ignore_ascii_case("x-real-ip")
120 || name.eq_ignore_ascii_case("forwarded")
121}
122
123/// Most list elements of `X-Forwarded-For` read before an address outside the
124/// trusted proxies turns up. Only trusted hops and empty elements count
125/// towards it, and no real chain of proxies comes near it.
126const MAX_HOPS: usize = 32;
127
128/// Longest list element read, spaces around it included. The longest address
129/// with a port, `[ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255]:65535`, is 53
130/// bytes; anything longer is not an address. With [`MAX_HOPS`] it bounds the
131/// bytes of a header read at all, whatever its size.
132const MAX_ELEMENT: usize = 128;
133
134/// The client address the proxy resolved for a request.
135///
136/// A request extension on every request the proxy serves: an
137/// [`AuthDecider`](crate::hooks::AuthDecider) and an
138/// [`ExtraRouteHandler`](crate::hooks::ExtraRouteHandler) get it in their
139/// request view, a fallback and a tonic upstream in process read it with
140/// `request.extensions().get::<ClientAddress>()`. The connection's own record
141/// (`Request::remote_addr`, `Request::peer_certs`) stays as it is.
142///
143/// # Examples
144///
145/// ```
146/// use structured_proxy::client_address::{ClientAddress, Resolution};
147///
148/// // What a tonic handler behind the proxy does with it.
149/// fn caller(request: &tonic::Request<()>) -> Option<std::net::IpAddr> {
150/// request.extensions().get::<ClientAddress>()?.ip()
151/// }
152///
153/// let client = ClientAddress::from_peer(Some("203.0.113.7:51234".parse().unwrap()));
154/// assert_eq!(client.resolution(), Resolution::Peer("203.0.113.7".parse().unwrap()));
155/// let mut request = tonic::Request::new(());
156/// request.extensions_mut().insert(client);
157/// assert_eq!(caller(&request), Some("203.0.113.7".parse().unwrap()));
158/// ```
159#[derive(Clone, Debug, PartialEq, Eq)]
160pub struct ClientAddress {
161 peer: Option<SocketAddr>,
162 resolution: Resolution,
163 /// The resolved address as a header value, made once for every header
164 /// and metadata entry that carries it.
165 value: Option<HeaderValue>,
166}
167
168/// How the client address was resolved, or why it was not.
169#[derive(Clone, Copy, Debug, PartialEq, Eq)]
170#[non_exhaustive]
171pub enum Resolution {
172 /// The connection's peer is the client: it is not a trusted proxy, or it
173 /// is one that forwarded no address where none is required.
174 Peer(IpAddr),
175 /// A trusted proxy forwarded the address: the first one outside the
176 /// trusted proxies, read right to left, or the leftmost when every hop is
177 /// trusted.
178 Forwarded(IpAddr),
179 /// A trusted proxy forwarded something that cannot be read where the
180 /// client's address should be. No address resolves: the peer is a proxy,
181 /// not the client.
182 Invalid(InvalidForwarding),
183 /// The server recorded no connection information, so there is no peer to
184 /// start from, or a trusted proxy reported nothing where an address is
185 /// required.
186 Unavailable,
187}
188
189/// Why a trusted proxy's forwarded address cannot be read.
190#[derive(Clone, Copy, Debug, PartialEq, Eq)]
191#[non_exhaustive]
192pub enum InvalidForwarding {
193 /// An element that is not an IP address with an optional port.
194 Malformed,
195 /// More trusted hops than the proxy reads before reaching the client.
196 TooManyHops,
197 /// More than one `X-Real-IP` field.
198 Repeated,
199 /// A cleaned report whose headers do not name one and the same address.
200 Conflicting,
201}
202
203impl std::fmt::Display for InvalidForwarding {
204 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205 f.write_str(match self {
206 Self::Malformed => "malformed",
207 Self::TooManyHops => "beyond too many trusted hops",
208 Self::Repeated => "repeated",
209 Self::Conflicting => "in conflict with itself",
210 })
211 }
212}
213
214impl ClientAddress {
215 /// The address of a request from `peer` through no trusted proxy: the
216 /// peer itself, or [`Resolution::Unavailable`] without one. The view a
217 /// hook's own tests hand it.
218 pub fn from_peer(peer: Option<SocketAddr>) -> Self {
219 let resolution = match peer {
220 Some(peer) => Resolution::Peer(peer.ip().to_canonical()),
221 None => Resolution::Unavailable,
222 };
223 Self::new(peer, resolution)
224 }
225
226 fn new(peer: Option<SocketAddr>, resolution: Resolution) -> Self {
227 let value = match resolution {
228 Resolution::Peer(ip) | Resolution::Forwarded(ip) => Some(header_value(ip)),
229 Resolution::Invalid(_) | Resolution::Unavailable => None,
230 };
231 Self {
232 peer,
233 resolution,
234 value,
235 }
236 }
237
238 /// The client's address, when one resolved.
239 pub fn ip(&self) -> Option<IpAddr> {
240 match self.resolution {
241 Resolution::Peer(ip) | Resolution::Forwarded(ip) => Some(ip),
242 Resolution::Invalid(_) | Resolution::Unavailable => None,
243 }
244 }
245
246 /// The connection's peer: the client, or the last proxy before this one.
247 pub fn peer(&self) -> Option<SocketAddr> {
248 self.peer
249 }
250
251 /// How the address was resolved, or why it was not.
252 pub fn resolution(&self) -> Resolution {
253 self.resolution
254 }
255
256 /// The resolved address as a header value: the client header's, and
257 /// `X-Forwarded-For`'s in the `resolved` mode.
258 pub(crate) fn header_value(&self) -> Option<&HeaderValue> {
259 self.value.as_ref()
260 }
261}
262
263/// `ip` as a header value: one allocation, for the bytes the value owns.
264fn header_value(ip: IpAddr) -> HeaderValue {
265 use std::io::Write;
266 // The longest text form, an IPv6 address ending in IPv4 notation, is 45
267 // bytes.
268 let mut buf = [0u8; 48];
269 let mut rest: &mut [u8] = &mut buf;
270 write!(rest, "{ip}").expect("an IP address fits in 48 bytes");
271 let len = 48 - rest.len();
272 HeaderValue::from_bytes(&buf[..len]).expect("an IP address is a header value")
273}
274
275/// [`ClientAddressConfig`], compiled.
276#[derive(Debug, Default)]
277pub(crate) struct Resolver {
278 trusted: Box<[IpNet]>,
279 header: ForwardingHeader,
280 required: bool,
281 forward: XForwardedFor,
282 /// Receives the resolved address.
283 client_header: Option<HeaderName>,
284 /// Receives the request's `X-Forwarded-For` as it arrived.
285 audit_header: Option<HeaderName>,
286}
287
288impl Resolver {
289 /// # Errors
290 ///
291 /// A `trusted_proxies` entry that is neither a CIDR range nor an address,
292 /// or a `forward` header name that is not a gRPC metadata key or names a
293 /// header the forwarding already writes.
294 pub(crate) fn build(config: &ClientAddressConfig) -> Result<Self, String> {
295 let trusted = config
296 .trusted_proxies
297 .iter()
298 .map(|entry| parse_trusted(entry))
299 .collect::<Result<_, _>>()?;
300 let forward = &config.forward;
301 let client_header = forward
302 .client_header
303 .as_deref()
304 .map(|name| forward_header("client_header", name, &["x-forwarded-for", "forwarded"]))
305 .transpose()?;
306 let audit_header = forward
307 .audit_header
308 .as_deref()
309 .map(|name| {
310 let mut taken = vec!["x-forwarded-for", "x-real-ip", "forwarded"];
311 taken.extend(client_header.as_ref().map(HeaderName::as_str));
312 forward_header("audit_header", name, &taken)
313 })
314 .transpose()?;
315 Ok(Self {
316 trusted,
317 header: config.header,
318 required: config.required,
319 forward: forward.x_forwarded_for,
320 client_header,
321 audit_header,
322 })
323 }
324
325 /// Whether a request whose address does not resolve is refused.
326 pub(crate) fn required(&self) -> bool {
327 self.required
328 }
329
330 /// Whether the proxy alone writes `name`: a client-address header, or a
331 /// header the forwarding policy writes. A guard may not set it.
332 pub(crate) fn reserves(&self, name: &str) -> bool {
333 owns(name)
334 || self
335 .configured_headers()
336 .any(|own| name.eq_ignore_ascii_case(own.as_str()))
337 }
338
339 /// The headers named in `forward`, the ones [`owns`] does not cover.
340 pub(crate) fn configured_headers(&self) -> impl Iterator<Item = &HeaderName> {
341 self.client_header.iter().chain(&self.audit_header)
342 }
343
344 /// Every header the forwarding policy may leave on a request: what a
345 /// transcoded call forwards as metadata in their place.
346 pub(crate) fn forwarded_headers(&self) -> impl Iterator<Item = &str> {
347 ["x-forwarded-for", "x-real-ip", "forwarded"]
348 .into_iter()
349 .chain(self.configured_headers().map(HeaderName::as_str))
350 }
351
352 fn trusts(&self, ip: IpAddr) -> bool {
353 self.trusted.iter().any(|net| net.contains(&ip))
354 }
355
356 /// The client address of a request from `peer` carrying `headers`, without
357 /// touching the request: what [`apply`](Self::apply) puts on it.
358 #[cfg(test)]
359 pub(crate) fn resolve(&self, peer: Option<SocketAddr>, headers: &HeaderMap) -> ClientAddress {
360 self.resolve_noting(peer, headers, None)
361 }
362
363 /// The client address of a request from `peer` carrying `headers` (what
364 /// [`apply`](Self::apply) puts on it), noting in `hops` the trusted
365 /// proxies between the client and this proxy, nearest first.
366 fn resolve_noting(
367 &self,
368 peer: Option<SocketAddr>,
369 headers: &HeaderMap,
370 hops: Option<&mut Vec<IpAddr>>,
371 ) -> ClientAddress {
372 let Some(peer) = peer else {
373 return ClientAddress::new(None, Resolution::Unavailable);
374 };
375 let peer_ip = peer.ip().to_canonical();
376 if !self.trusts(peer_ip) {
377 // Whatever an untrusted peer forwarded is its own assertion.
378 return ClientAddress::new(Some(peer), Resolution::Peer(peer_ip));
379 }
380 let forwarded = match self.header {
381 ForwardingHeader::XForwardedFor => {
382 self.walk(headers.get_all(X_FORWARDED_FOR).iter(), hops)
383 }
384 ForwardingHeader::XRealIp => real_ip(headers.get_all(X_REAL_IP).iter()),
385 ForwardingHeader::CleanedReport => cleaned_report(headers),
386 };
387 let resolution = match forwarded {
388 Ok(Some(ip)) => Resolution::Forwarded(ip),
389 Ok(None) if self.required => Resolution::Unavailable,
390 Ok(None) => Resolution::Peer(peer_ip),
391 Err(invalid) => Resolution::Invalid(invalid),
392 };
393 ClientAddress::new(Some(peer), resolution)
394 }
395
396 /// Resolve the address of `request` from `peer`, put it on the request as
397 /// a [`ClientAddress`], and rewrite its forwarding headers by the
398 /// `forward` policy, so every consumer past this point (the guards, the
399 /// fallback, the upstream) sees exactly what the upstream will:
400 ///
401 /// - the audit header, when set, gets the request's `X-Forwarded-For`
402 /// lines as they arrived, and nothing the client sent under its name;
403 /// - `X-Forwarded-For` as the mode says (see [`XForwardedFor`]);
404 /// - `X-Real-IP` and `Forwarded` as they arrived with `preserve`, removed
405 /// otherwise, since they would contradict it;
406 /// - the client header, last, the resolved address, so it overrides even
407 /// a preserved `X-Real-IP`; absent when nothing resolved.
408 pub(crate) fn apply<B>(&self, request: &mut http::Request<B>, peer: Option<SocketAddr>) {
409 let mut hops = Vec::new();
410 let noting = (self.forward == XForwardedFor::Verified).then_some(&mut hops);
411 let client = self.resolve_noting(peer, request.headers(), noting);
412 let headers = request.headers_mut();
413 if let Some(audit) = &self.audit_header {
414 let arrived: Vec<HeaderValue> =
415 headers.get_all(X_FORWARDED_FOR).iter().cloned().collect();
416 headers.remove(audit);
417 for line in arrived {
418 headers.append(audit.clone(), line);
419 }
420 }
421 let xff = match self.forward {
422 XForwardedFor::Verified => verified_chain(&client, &hops),
423 XForwardedFor::Resolved => client.header_value().cloned(),
424 XForwardedFor::Append => appended_chain(headers, client.peer),
425 XForwardedFor::Preserve => None,
426 XForwardedFor::Remove => None,
427 };
428 if self.forward != XForwardedFor::Preserve {
429 headers.remove(X_FORWARDED_FOR);
430 headers.remove(X_REAL_IP);
431 headers.remove(FORWARDED);
432 if let Some(xff) = xff {
433 headers.insert(X_FORWARDED_FOR, xff);
434 }
435 }
436 if let Some(name) = &self.client_header {
437 headers.remove(name);
438 if let Some(value) = client.header_value() {
439 headers.insert(name.clone(), value.clone());
440 }
441 }
442 request.extensions_mut().insert(client);
443 }
444
445 /// The client's address in `X-Forwarded-For`, `lines` in wire order
446 /// (RFC 9110 §5.3: several field lines form one list): the first element
447 /// outside the trusted proxies from the right, or the leftmost when all
448 /// are trusted, or `None` without an element. Elements left of the client
449 /// are the client's own assertions and are never read. An element that
450 /// is not an address where the walk needs one makes the whole result
451 /// invalid: skipping it would credit a hop the chain never vouched for.
452 /// `hops`, when given, receives the trusted proxies right of the client,
453 /// nearest first.
454 fn walk<'a>(
455 &self,
456 lines: impl DoubleEndedIterator<Item = &'a HeaderValue>,
457 mut hops: Option<&mut Vec<IpAddr>>,
458 ) -> Result<Option<IpAddr>, InvalidForwarding> {
459 let mut read = 0;
460 let mut leftmost = None;
461 for line in lines.rev() {
462 let mut rest = line.as_bytes();
463 loop {
464 read += 1;
465 if read > MAX_HOPS {
466 return Err(InvalidForwarding::TooManyHops);
467 }
468 let (element, before) = last_element(rest)?;
469 // RFC 9110 §5.6.1: a recipient ignores empty list elements.
470 if !element.is_empty() {
471 let ip = parse_address(element)
472 .ok_or(InvalidForwarding::Malformed)?
473 .to_canonical();
474 if !self.trusts(ip) {
475 return Ok(Some(ip));
476 }
477 if let Some(hops) = hops.as_deref_mut() {
478 hops.push(ip);
479 }
480 leftmost = Some(ip);
481 }
482 match before {
483 Some(before) => rest = before,
484 None => break,
485 }
486 }
487 }
488 // Every hop trusted: the leftmost is the client, not a hop.
489 if leftmost.is_some() {
490 if let Some(hops) = hops {
491 hops.pop();
492 }
493 }
494 Ok(leftmost)
495 }
496}
497
498/// The verified `X-Forwarded-For`: the resolved client, the trusted proxies
499/// `hops` (nearest first) in the order they forwarded, then this proxy's peer,
500/// each in canonical form; `None` when nothing resolved. A client that is the
501/// peer itself is listed once.
502fn verified_chain(client: &ClientAddress, hops: &[IpAddr]) -> Option<HeaderValue> {
503 use std::fmt::Write;
504 let value = client.header_value()?;
505 let Resolution::Forwarded(ip) = client.resolution else {
506 // The peer is the client: nothing else is verified.
507 return Some(value.clone());
508 };
509 let peer = client.peer?.ip().to_canonical();
510 let mut chain = ip.to_string();
511 for hop in hops.iter().rev().chain([&peer]) {
512 write!(chain, ", {hop}").expect("writing to a String cannot fail");
513 }
514 Some(HeaderValue::try_from(chain).expect("addresses and commas are a header value"))
515}
516
517/// The request's `X-Forwarded-For` as it arrived, its field lines joined in
518/// order, with `peer` appended; the lines alone without a peer, and `None`
519/// with neither.
520fn appended_chain(headers: &HeaderMap, peer: Option<SocketAddr>) -> Option<HeaderValue> {
521 use std::io::Write;
522 let mut lines = headers.get_all(X_FORWARDED_FOR).iter();
523 let Some(peer) = peer else {
524 // Nothing to append: one line stays as it is, several are joined.
525 let first = lines.next()?;
526 let mut joined = first.as_bytes().to_vec();
527 for line in lines {
528 joined.extend_from_slice(b", ");
529 joined.extend_from_slice(line.as_bytes());
530 }
531 return Some(
532 HeaderValue::from_maybe_shared(bytes::Bytes::from(joined))
533 .expect("joined header values are a header value"),
534 );
535 };
536 let mut chain = Vec::new();
537 for line in lines {
538 chain.extend_from_slice(line.as_bytes());
539 chain.extend_from_slice(b", ");
540 }
541 write!(chain, "{}", peer.ip().to_canonical()).expect("writing to a Vec cannot fail");
542 Some(
543 HeaderValue::from_maybe_shared(bytes::Bytes::from(chain))
544 .expect("header values, commas and an address are a header value"),
545 )
546}
547
548/// A `forward` header name: a text gRPC metadata key, so a transcoded call can
549/// carry it, other than the headers in `taken`. A `-bin` key would need base64
550/// values (gRPC PROTOCOL-HTTP2, "Custom-Metadata"), and an address or an
551/// address list is text.
552fn forward_header(setting: &str, name: &str, taken: &[&str]) -> Result<HeaderName, String> {
553 let header = HeaderName::from_bytes(name.as_bytes())
554 .ok()
555 .filter(|header| crate::transcode::metadata::is_grpc_key(header.as_str()))
556 .ok_or_else(|| {
557 format!("forward.{setting} {name:?} is not a gRPC metadata key (letters, digits, '_', '-' and '.')")
558 })?;
559 if header.as_str().ends_with("-bin") {
560 return Err(format!(
561 "forward.{setting} {name:?} is a binary metadata key, whose values must be base64; \
562 the address is text"
563 ));
564 }
565 if taken.contains(&header.as_str()) {
566 return Err(format!(
567 "forward.{setting} {name:?} names a header the forwarding already writes"
568 ));
569 }
570 Ok(header)
571}
572
573/// The last element of the list `bytes`, trimmed of the spaces and tabs
574/// around it (RFC 9110 §5.6.1), and what precedes its comma, if any. At most
575/// [`MAX_ELEMENT`] bytes are read.
576fn last_element(bytes: &[u8]) -> Result<(&[u8], Option<&[u8]>), InvalidForwarding> {
577 let window = bytes.len().saturating_sub(MAX_ELEMENT + 1);
578 let (element, before) = match bytes[window..].iter().rposition(|&b| b == b',') {
579 Some(comma) => (&bytes[window + comma + 1..], Some(&bytes[..window + comma])),
580 None if window == 0 => (bytes, None),
581 None => return Err(InvalidForwarding::Malformed),
582 };
583 Ok((trim_whitespace(element), before))
584}
585
586fn trim_whitespace(bytes: &[u8]) -> &[u8] {
587 let is_ows = |b: &u8| matches!(b, b' ' | b'\t');
588 let start = bytes.iter().position(|b| !is_ows(b)).unwrap_or(bytes.len());
589 let end = bytes
590 .iter()
591 .rposition(|b| !is_ows(b))
592 .map_or(start, |end| end + 1);
593 &bytes[start..end]
594}
595
596/// The client's address in `X-Real-IP`: one field with one address, written
597/// by the trusted proxy for its own peer, or `None` without the field.
598fn real_ip<'a>(
599 mut lines: impl Iterator<Item = &'a HeaderValue>,
600) -> Result<Option<IpAddr>, InvalidForwarding> {
601 let Some(line) = lines.next() else {
602 return Ok(None);
603 };
604 if lines.next().is_some() {
605 return Err(InvalidForwarding::Repeated);
606 }
607 // Bounded before trimming, so the spaces around the address count too and
608 // no value is read past `MAX_ELEMENT`, as for an `X-Forwarded-For` element.
609 let raw = line.as_bytes();
610 if raw.len() > MAX_ELEMENT {
611 return Err(InvalidForwarding::Malformed);
612 }
613 parse_address(trim_whitespace(raw))
614 .map(|ip| Some(ip.to_canonical()))
615 .ok_or(InvalidForwarding::Malformed)
616}
617
618/// The client's address in a cleaned report: its `X-Real-IP`, when every other
619/// assertion of an address agrees with it. `None` when nothing is reported.
620fn cleaned_report(headers: &HeaderMap) -> Result<Option<IpAddr>, InvalidForwarding> {
621 let reported = real_ip(headers.get_all(X_REAL_IP).iter())?;
622 let mut chain = headers.get_all(X_FORWARDED_FOR).iter();
623 let (first, second) = (chain.next(), chain.next());
624 let agrees = !headers.contains_key(FORWARDED)
625 && match (reported, first, second) {
626 (_, _, Some(_)) | (None, Some(_), None) => false,
627 (_, None, None) => true,
628 (Some(ip), Some(line), None) => {
629 real_ip(std::iter::once(line)).ok().flatten() == Some(ip)
630 }
631 };
632 if !agrees {
633 return Err(InvalidForwarding::Conflicting);
634 }
635 Ok(reported)
636}
637
638/// An address as forwarding headers carry it: bare, IPv6 in brackets, or
639/// either with a port, which is dropped. Nothing else, so a name, `unknown`
640/// or an obfuscated identifier (RFC 7239 §6.3) never counts as an address.
641fn parse_address(bytes: &[u8]) -> Option<IpAddr> {
642 let text = std::str::from_utf8(bytes).ok()?;
643 if let Ok(ip) = text.parse::<IpAddr>() {
644 return Some(ip);
645 }
646 if let Ok(socket) = text.parse::<SocketAddr>() {
647 return Some(socket.ip());
648 }
649 let v6 = text.strip_prefix('[')?.strip_suffix(']')?;
650 v6.parse::<Ipv6Addr>().ok().map(IpAddr::V6)
651}
652
653/// A `trusted_proxies` entry: a CIDR range, or an address for one host.
654fn parse_trusted(entry: &str) -> Result<IpNet, String> {
655 let net = match entry.parse::<IpNet>() {
656 Ok(net) => net,
657 Err(_) => match entry.parse::<IpAddr>() {
658 Ok(ip) => IpNet::from(ip),
659 Err(_) => {
660 return Err(format!(
661 "trusted_proxies entry {entry:?} is neither a CIDR range nor an IP address"
662 ))
663 }
664 },
665 };
666 Ok(canonical_net(net))
667}
668
669/// `net` in the form peers are compared in: a range inside the IPv4-mapped
670/// block `::ffff:0:0/96` (RFC 4291 §2.5.5.2) becomes the IPv4 range it maps,
671/// since peers are canonicalized to IPv4 before matching. A wider IPv6 range
672/// stays IPv6, so trusting `::/0` does not trust every IPv4 peer.
673fn canonical_net(net: IpNet) -> IpNet {
674 if let IpNet::V6(v6) = net {
675 if let (Some(v4), Some(prefix)) = (
676 v6.network().to_ipv4_mapped(),
677 v6.prefix_len().checked_sub(96),
678 ) {
679 return ipnet::Ipv4Net::new(v4, prefix)
680 .expect("a prefix of at most 128 - 96 fits IPv4")
681 .into();
682 }
683 }
684 net
685}
686
687/// Resolves the client address of every request before the service it wraps
688/// sees it: puts the [`ClientAddress`] on the request and rewrites its
689/// forwarding headers, as the proxy does before its own guards.
690///
691/// The proxy applies it itself. Use it in a router of your own that mounts
692/// proxy parts such as the [`shield`](crate::shield) middleware directly, so
693/// they key by the client behind your trusted proxies rather than by the
694/// proxies' own address. The peer comes from the `ConnectInfo<SocketAddr>` an
695/// axum server records (`into_make_service_with_connect_info`) or the
696/// `TcpConnectInfo` a tonic server does; without one the address is
697/// [`Resolution::Unavailable`]. `required` is not enforced
698/// here: read [`ClientAddress::resolution`] where it matters.
699///
700/// # Examples
701///
702/// ```
703/// use structured_proxy::client_address::ClientAddressLayer;
704/// use structured_proxy::config::ClientAddressConfig;
705///
706/// let mut trust = ClientAddressConfig::default();
707/// trust.trusted_proxies = vec!["10.0.0.0/8".into()];
708/// let app: axum::Router = axum::Router::new()
709/// .route("/", axum::routing::get(|| async { "ok" }))
710/// .layer(ClientAddressLayer::new(&trust).unwrap());
711/// # let _ = app;
712/// ```
713#[derive(Clone, Debug)]
714pub struct ClientAddressLayer {
715 resolver: Arc<Resolver>,
716}
717
718impl ClientAddressLayer {
719 /// The resolution `config` describes.
720 ///
721 /// # Errors
722 ///
723 /// A `trusted_proxies` entry that is neither a CIDR range nor an address.
724 pub fn new(config: &ClientAddressConfig) -> anyhow::Result<Self> {
725 let resolver = Resolver::build(config)
726 .map_err(|e| anyhow::anyhow!("invalid client_address config: {e}"))?;
727 Ok(Self::with(Arc::new(resolver)))
728 }
729
730 /// The layer over a resolver the proxy already compiled.
731 pub(crate) fn with(resolver: Arc<Resolver>) -> Self {
732 Self { resolver }
733 }
734}
735
736impl<S> Layer<S> for ClientAddressLayer {
737 type Service = ClientAddressService<S>;
738
739 fn layer(&self, inner: S) -> ClientAddressService<S> {
740 ClientAddressService {
741 inner,
742 resolver: self.resolver.clone(),
743 }
744 }
745}
746
747/// The service of [`ClientAddressLayer`].
748#[derive(Clone, Debug)]
749pub struct ClientAddressService<S> {
750 inner: S,
751 resolver: Arc<Resolver>,
752}
753
754impl<S, B> Service<http::Request<B>> for ClientAddressService<S>
755where
756 S: Service<http::Request<B>>,
757{
758 type Response = S::Response;
759 type Error = S::Error;
760 type Future = S::Future;
761
762 #[inline]
763 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
764 self.inner.poll_ready(cx)
765 }
766
767 fn call(&mut self, mut request: http::Request<B>) -> Self::Future {
768 let peer = request
769 .extensions()
770 .get::<ConnectInfo<SocketAddr>>()
771 .map(|ConnectInfo(peer)| *peer)
772 .or_else(|| {
773 use crate::service::TlsConnectInfo;
774 use tonic::transport::server::TcpConnectInfo;
775 let extensions = request.extensions();
776 extensions
777 .get::<TcpConnectInfo>()
778 .or_else(|| extensions.get::<TlsConnectInfo>().map(|tls| tls.get_ref()))
779 .and_then(TcpConnectInfo::remote_addr)
780 });
781 self.resolver.apply(&mut request, peer);
782 self.inner.call(request)
783 }
784}
785
786#[cfg(test)]
787mod tests;