Skip to main content

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;