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.
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.
185    Unavailable,
186}
187
188/// Why a trusted proxy's forwarded address cannot be read.
189#[derive(Clone, Copy, Debug, PartialEq, Eq)]
190#[non_exhaustive]
191pub enum InvalidForwarding {
192    /// An element that is not an IP address with an optional port.
193    Malformed,
194    /// More trusted hops than the proxy reads before reaching the client.
195    TooManyHops,
196    /// More than one `X-Real-IP` field.
197    Repeated,
198}
199
200impl std::fmt::Display for InvalidForwarding {
201    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202        f.write_str(match self {
203            Self::Malformed => "malformed",
204            Self::TooManyHops => "beyond too many trusted hops",
205            Self::Repeated => "repeated",
206        })
207    }
208}
209
210impl ClientAddress {
211    /// The address of a request from `peer` through no trusted proxy: the
212    /// peer itself, or [`Resolution::Unavailable`] without one. The view a
213    /// hook's own tests hand it.
214    pub fn from_peer(peer: Option<SocketAddr>) -> Self {
215        let resolution = match peer {
216            Some(peer) => Resolution::Peer(peer.ip().to_canonical()),
217            None => Resolution::Unavailable,
218        };
219        Self::new(peer, resolution)
220    }
221
222    fn new(peer: Option<SocketAddr>, resolution: Resolution) -> Self {
223        let value = match resolution {
224            Resolution::Peer(ip) | Resolution::Forwarded(ip) => Some(header_value(ip)),
225            Resolution::Invalid(_) | Resolution::Unavailable => None,
226        };
227        Self {
228            peer,
229            resolution,
230            value,
231        }
232    }
233
234    /// The client's address, when one resolved.
235    pub fn ip(&self) -> Option<IpAddr> {
236        match self.resolution {
237            Resolution::Peer(ip) | Resolution::Forwarded(ip) => Some(ip),
238            Resolution::Invalid(_) | Resolution::Unavailable => None,
239        }
240    }
241
242    /// The connection's peer: the client, or the last proxy before this one.
243    pub fn peer(&self) -> Option<SocketAddr> {
244        self.peer
245    }
246
247    /// How the address was resolved, or why it was not.
248    pub fn resolution(&self) -> Resolution {
249        self.resolution
250    }
251
252    /// The resolved address as a header value: the client header's, and
253    /// `X-Forwarded-For`'s in the `resolved` mode.
254    pub(crate) fn header_value(&self) -> Option<&HeaderValue> {
255        self.value.as_ref()
256    }
257}
258
259/// `ip` as a header value: one allocation, for the bytes the value owns.
260fn header_value(ip: IpAddr) -> HeaderValue {
261    use std::io::Write;
262    // The longest text form, an IPv6 address ending in IPv4 notation, is 45
263    // bytes.
264    let mut buf = [0u8; 48];
265    let mut rest: &mut [u8] = &mut buf;
266    write!(rest, "{ip}").expect("an IP address fits in 48 bytes");
267    let len = 48 - rest.len();
268    HeaderValue::from_bytes(&buf[..len]).expect("an IP address is a header value")
269}
270
271/// [`ClientAddressConfig`], compiled.
272#[derive(Debug, Default)]
273pub(crate) struct Resolver {
274    trusted: Box<[IpNet]>,
275    header: ForwardingHeader,
276    required: bool,
277    forward: XForwardedFor,
278    /// Receives the resolved address.
279    client_header: Option<HeaderName>,
280    /// Receives the request's `X-Forwarded-For` as it arrived.
281    audit_header: Option<HeaderName>,
282}
283
284impl Resolver {
285    /// # Errors
286    ///
287    /// A `trusted_proxies` entry that is neither a CIDR range nor an address,
288    /// or a `forward` header name that is not a gRPC metadata key or names a
289    /// header the forwarding already writes.
290    pub(crate) fn build(config: &ClientAddressConfig) -> Result<Self, String> {
291        let trusted = config
292            .trusted_proxies
293            .iter()
294            .map(|entry| parse_trusted(entry))
295            .collect::<Result<_, _>>()?;
296        let forward = &config.forward;
297        let client_header = forward
298            .client_header
299            .as_deref()
300            .map(|name| forward_header("client_header", name, &["x-forwarded-for", "forwarded"]))
301            .transpose()?;
302        let audit_header = forward
303            .audit_header
304            .as_deref()
305            .map(|name| {
306                let mut taken = vec!["x-forwarded-for", "x-real-ip", "forwarded"];
307                taken.extend(client_header.as_ref().map(HeaderName::as_str));
308                forward_header("audit_header", name, &taken)
309            })
310            .transpose()?;
311        Ok(Self {
312            trusted,
313            header: config.header,
314            required: config.required,
315            forward: forward.x_forwarded_for,
316            client_header,
317            audit_header,
318        })
319    }
320
321    /// Whether a request whose address does not resolve is refused.
322    pub(crate) fn required(&self) -> bool {
323        self.required
324    }
325
326    /// Whether the proxy alone writes `name`: a client-address header, or a
327    /// header the forwarding policy writes. A guard may not set it.
328    pub(crate) fn reserves(&self, name: &str) -> bool {
329        owns(name)
330            || self
331                .configured_headers()
332                .any(|own| name.eq_ignore_ascii_case(own.as_str()))
333    }
334
335    /// The headers named in `forward`, the ones [`owns`] does not cover.
336    pub(crate) fn configured_headers(&self) -> impl Iterator<Item = &HeaderName> {
337        self.client_header.iter().chain(&self.audit_header)
338    }
339
340    /// Every header the forwarding policy may leave on a request: what a
341    /// transcoded call forwards as metadata in their place.
342    pub(crate) fn forwarded_headers(&self) -> impl Iterator<Item = &str> {
343        ["x-forwarded-for", "x-real-ip", "forwarded"]
344            .into_iter()
345            .chain(self.configured_headers().map(HeaderName::as_str))
346    }
347
348    fn trusts(&self, ip: IpAddr) -> bool {
349        self.trusted.iter().any(|net| net.contains(&ip))
350    }
351
352    /// The client address of a request from `peer` carrying `headers`, without
353    /// touching the request: what [`apply`](Self::apply) puts on it.
354    #[cfg(test)]
355    pub(crate) fn resolve(&self, peer: Option<SocketAddr>, headers: &HeaderMap) -> ClientAddress {
356        self.resolve_noting(peer, headers, None)
357    }
358
359    /// The client address of a request from `peer` carrying `headers` (what
360    /// [`apply`](Self::apply) puts on it), noting in `hops` the trusted
361    /// proxies between the client and this proxy, nearest first.
362    fn resolve_noting(
363        &self,
364        peer: Option<SocketAddr>,
365        headers: &HeaderMap,
366        hops: Option<&mut Vec<IpAddr>>,
367    ) -> ClientAddress {
368        let Some(peer) = peer else {
369            return ClientAddress::new(None, Resolution::Unavailable);
370        };
371        let peer_ip = peer.ip().to_canonical();
372        if !self.trusts(peer_ip) {
373            // Whatever an untrusted peer forwarded is its own assertion.
374            return ClientAddress::new(Some(peer), Resolution::Peer(peer_ip));
375        }
376        let forwarded = match self.header {
377            ForwardingHeader::XForwardedFor => {
378                self.walk(headers.get_all(X_FORWARDED_FOR).iter(), hops)
379            }
380            ForwardingHeader::XRealIp => real_ip(headers.get_all(X_REAL_IP).iter()),
381        };
382        let resolution = match forwarded {
383            Ok(Some(ip)) => Resolution::Forwarded(ip),
384            Ok(None) => Resolution::Peer(peer_ip),
385            Err(invalid) => Resolution::Invalid(invalid),
386        };
387        ClientAddress::new(Some(peer), resolution)
388    }
389
390    /// Resolve the address of `request` from `peer`, put it on the request as
391    /// a [`ClientAddress`], and rewrite its forwarding headers by the
392    /// `forward` policy, so every consumer past this point (the guards, the
393    /// fallback, the upstream) sees exactly what the upstream will:
394    ///
395    /// - the audit header, when set, gets the request's `X-Forwarded-For`
396    ///   lines as they arrived, and nothing the client sent under its name;
397    /// - `X-Forwarded-For` as the mode says (see [`XForwardedFor`]);
398    /// - `X-Real-IP` and `Forwarded` as they arrived with `preserve`, removed
399    ///   otherwise, since they would contradict it;
400    /// - the client header, last, the resolved address, so it overrides even
401    ///   a preserved `X-Real-IP`; absent when nothing resolved.
402    pub(crate) fn apply<B>(&self, request: &mut http::Request<B>, peer: Option<SocketAddr>) {
403        let mut hops = Vec::new();
404        let noting = (self.forward == XForwardedFor::Verified).then_some(&mut hops);
405        let client = self.resolve_noting(peer, request.headers(), noting);
406        let headers = request.headers_mut();
407        if let Some(audit) = &self.audit_header {
408            let arrived: Vec<HeaderValue> =
409                headers.get_all(X_FORWARDED_FOR).iter().cloned().collect();
410            headers.remove(audit);
411            for line in arrived {
412                headers.append(audit.clone(), line);
413            }
414        }
415        let xff = match self.forward {
416            XForwardedFor::Verified => verified_chain(&client, &hops),
417            XForwardedFor::Resolved => client.header_value().cloned(),
418            XForwardedFor::Append => appended_chain(headers, client.peer),
419            XForwardedFor::Preserve => None,
420            XForwardedFor::Remove => None,
421        };
422        if self.forward != XForwardedFor::Preserve {
423            headers.remove(X_FORWARDED_FOR);
424            headers.remove(X_REAL_IP);
425            headers.remove(FORWARDED);
426            if let Some(xff) = xff {
427                headers.insert(X_FORWARDED_FOR, xff);
428            }
429        }
430        if let Some(name) = &self.client_header {
431            headers.remove(name);
432            if let Some(value) = client.header_value() {
433                headers.insert(name.clone(), value.clone());
434            }
435        }
436        request.extensions_mut().insert(client);
437    }
438
439    /// The client's address in `X-Forwarded-For`, `lines` in wire order
440    /// (RFC 9110 §5.3: several field lines form one list): the first element
441    /// outside the trusted proxies from the right, or the leftmost when all
442    /// are trusted, or `None` without an element. Elements left of the client
443    /// are the client's own assertions and are never read. An element that
444    /// is not an address where the walk needs one makes the whole result
445    /// invalid: skipping it would credit a hop the chain never vouched for.
446    /// `hops`, when given, receives the trusted proxies right of the client,
447    /// nearest first.
448    fn walk<'a>(
449        &self,
450        lines: impl DoubleEndedIterator<Item = &'a HeaderValue>,
451        mut hops: Option<&mut Vec<IpAddr>>,
452    ) -> Result<Option<IpAddr>, InvalidForwarding> {
453        let mut read = 0;
454        let mut leftmost = None;
455        for line in lines.rev() {
456            let mut rest = line.as_bytes();
457            loop {
458                read += 1;
459                if read > MAX_HOPS {
460                    return Err(InvalidForwarding::TooManyHops);
461                }
462                let (element, before) = last_element(rest)?;
463                // RFC 9110 §5.6.1: a recipient ignores empty list elements.
464                if !element.is_empty() {
465                    let ip = parse_address(element)
466                        .ok_or(InvalidForwarding::Malformed)?
467                        .to_canonical();
468                    if !self.trusts(ip) {
469                        return Ok(Some(ip));
470                    }
471                    if let Some(hops) = hops.as_deref_mut() {
472                        hops.push(ip);
473                    }
474                    leftmost = Some(ip);
475                }
476                match before {
477                    Some(before) => rest = before,
478                    None => break,
479                }
480            }
481        }
482        // Every hop trusted: the leftmost is the client, not a hop.
483        if leftmost.is_some() {
484            if let Some(hops) = hops {
485                hops.pop();
486            }
487        }
488        Ok(leftmost)
489    }
490}
491
492/// The verified `X-Forwarded-For`: the resolved client, the trusted proxies
493/// `hops` (nearest first) in the order they forwarded, then this proxy's peer,
494/// each in canonical form; `None` when nothing resolved. A client that is the
495/// peer itself is listed once.
496fn verified_chain(client: &ClientAddress, hops: &[IpAddr]) -> Option<HeaderValue> {
497    use std::fmt::Write;
498    let value = client.header_value()?;
499    let Resolution::Forwarded(ip) = client.resolution else {
500        // The peer is the client: nothing else is verified.
501        return Some(value.clone());
502    };
503    let peer = client.peer?.ip().to_canonical();
504    let mut chain = ip.to_string();
505    for hop in hops.iter().rev().chain([&peer]) {
506        write!(chain, ", {hop}").expect("writing to a String cannot fail");
507    }
508    Some(HeaderValue::try_from(chain).expect("addresses and commas are a header value"))
509}
510
511/// The request's `X-Forwarded-For` as it arrived, its field lines joined in
512/// order, with `peer` appended; the lines alone without a peer, and `None`
513/// with neither.
514fn appended_chain(headers: &HeaderMap, peer: Option<SocketAddr>) -> Option<HeaderValue> {
515    use std::io::Write;
516    let mut lines = headers.get_all(X_FORWARDED_FOR).iter();
517    let Some(peer) = peer else {
518        // Nothing to append: one line stays as it is, several are joined.
519        let first = lines.next()?;
520        let mut joined = first.as_bytes().to_vec();
521        for line in lines {
522            joined.extend_from_slice(b", ");
523            joined.extend_from_slice(line.as_bytes());
524        }
525        return Some(
526            HeaderValue::from_maybe_shared(bytes::Bytes::from(joined))
527                .expect("joined header values are a header value"),
528        );
529    };
530    let mut chain = Vec::new();
531    for line in lines {
532        chain.extend_from_slice(line.as_bytes());
533        chain.extend_from_slice(b", ");
534    }
535    write!(chain, "{}", peer.ip().to_canonical()).expect("writing to a Vec cannot fail");
536    Some(
537        HeaderValue::from_maybe_shared(bytes::Bytes::from(chain))
538            .expect("header values, commas and an address are a header value"),
539    )
540}
541
542/// A `forward` header name: a text gRPC metadata key, so a transcoded call can
543/// carry it, other than the headers in `taken`. A `-bin` key would need base64
544/// values (gRPC PROTOCOL-HTTP2, "Custom-Metadata"), and an address or an
545/// address list is text.
546fn forward_header(setting: &str, name: &str, taken: &[&str]) -> Result<HeaderName, String> {
547    let header = HeaderName::from_bytes(name.as_bytes())
548        .ok()
549        .filter(|header| crate::transcode::metadata::is_grpc_key(header.as_str()))
550        .ok_or_else(|| {
551            format!("forward.{setting} {name:?} is not a gRPC metadata key (letters, digits, '_', '-' and '.')")
552        })?;
553    if header.as_str().ends_with("-bin") {
554        return Err(format!(
555            "forward.{setting} {name:?} is a binary metadata key, whose values must be base64; \
556             the address is text"
557        ));
558    }
559    if taken.contains(&header.as_str()) {
560        return Err(format!(
561            "forward.{setting} {name:?} names a header the forwarding already writes"
562        ));
563    }
564    Ok(header)
565}
566
567/// The last element of the list `bytes`, trimmed of the spaces and tabs
568/// around it (RFC 9110 §5.6.1), and what precedes its comma, if any. At most
569/// [`MAX_ELEMENT`] bytes are read.
570fn last_element(bytes: &[u8]) -> Result<(&[u8], Option<&[u8]>), InvalidForwarding> {
571    let window = bytes.len().saturating_sub(MAX_ELEMENT + 1);
572    let (element, before) = match bytes[window..].iter().rposition(|&b| b == b',') {
573        Some(comma) => (&bytes[window + comma + 1..], Some(&bytes[..window + comma])),
574        None if window == 0 => (bytes, None),
575        None => return Err(InvalidForwarding::Malformed),
576    };
577    Ok((trim_whitespace(element), before))
578}
579
580fn trim_whitespace(bytes: &[u8]) -> &[u8] {
581    let is_ows = |b: &u8| matches!(b, b' ' | b'\t');
582    let start = bytes.iter().position(|b| !is_ows(b)).unwrap_or(bytes.len());
583    let end = bytes
584        .iter()
585        .rposition(|b| !is_ows(b))
586        .map_or(start, |end| end + 1);
587    &bytes[start..end]
588}
589
590/// The client's address in `X-Real-IP`: one field with one address, written
591/// by the trusted proxy for its own peer, or `None` without the field.
592fn real_ip<'a>(
593    mut lines: impl Iterator<Item = &'a HeaderValue>,
594) -> Result<Option<IpAddr>, InvalidForwarding> {
595    let Some(line) = lines.next() else {
596        return Ok(None);
597    };
598    if lines.next().is_some() {
599        return Err(InvalidForwarding::Repeated);
600    }
601    // Bounded before trimming, so the spaces around the address count too and
602    // no value is read past `MAX_ELEMENT`, as for an `X-Forwarded-For` element.
603    let raw = line.as_bytes();
604    if raw.len() > MAX_ELEMENT {
605        return Err(InvalidForwarding::Malformed);
606    }
607    parse_address(trim_whitespace(raw))
608        .map(|ip| Some(ip.to_canonical()))
609        .ok_or(InvalidForwarding::Malformed)
610}
611
612/// An address as forwarding headers carry it: bare, IPv6 in brackets, or
613/// either with a port, which is dropped. Nothing else, so a name, `unknown`
614/// or an obfuscated identifier (RFC 7239 §6.3) never counts as an address.
615fn parse_address(bytes: &[u8]) -> Option<IpAddr> {
616    let text = std::str::from_utf8(bytes).ok()?;
617    if let Ok(ip) = text.parse::<IpAddr>() {
618        return Some(ip);
619    }
620    if let Ok(socket) = text.parse::<SocketAddr>() {
621        return Some(socket.ip());
622    }
623    let v6 = text.strip_prefix('[')?.strip_suffix(']')?;
624    v6.parse::<Ipv6Addr>().ok().map(IpAddr::V6)
625}
626
627/// A `trusted_proxies` entry: a CIDR range, or an address for one host.
628fn parse_trusted(entry: &str) -> Result<IpNet, String> {
629    let net = match entry.parse::<IpNet>() {
630        Ok(net) => net,
631        Err(_) => match entry.parse::<IpAddr>() {
632            Ok(ip) => IpNet::from(ip),
633            Err(_) => {
634                return Err(format!(
635                    "trusted_proxies entry {entry:?} is neither a CIDR range nor an IP address"
636                ))
637            }
638        },
639    };
640    Ok(canonical_net(net))
641}
642
643/// `net` in the form peers are compared in: a range inside the IPv4-mapped
644/// block `::ffff:0:0/96` (RFC 4291 §2.5.5.2) becomes the IPv4 range it maps,
645/// since peers are canonicalized to IPv4 before matching. A wider IPv6 range
646/// stays IPv6, so trusting `::/0` does not trust every IPv4 peer.
647fn canonical_net(net: IpNet) -> IpNet {
648    if let IpNet::V6(v6) = net {
649        if let (Some(v4), Some(prefix)) = (
650            v6.network().to_ipv4_mapped(),
651            v6.prefix_len().checked_sub(96),
652        ) {
653            return ipnet::Ipv4Net::new(v4, prefix)
654                .expect("a prefix of at most 128 - 96 fits IPv4")
655                .into();
656        }
657    }
658    net
659}
660
661/// Resolves the client address of every request before the service it wraps
662/// sees it: puts the [`ClientAddress`] on the request and rewrites its
663/// forwarding headers, as the proxy does before its own guards.
664///
665/// The proxy applies it itself. Use it in a router of your own that mounts
666/// proxy parts such as the [`shield`](crate::shield) middleware directly, so
667/// they key by the client behind your trusted proxies rather than by the
668/// proxies' own address. The peer comes from the `ConnectInfo<SocketAddr>` an
669/// axum server records (`into_make_service_with_connect_info`); without one
670/// the address is [`Resolution::Unavailable`]. `required` is not enforced
671/// here: read [`ClientAddress::resolution`] where it matters.
672///
673/// # Examples
674///
675/// ```
676/// use structured_proxy::client_address::ClientAddressLayer;
677/// use structured_proxy::config::ClientAddressConfig;
678///
679/// let mut trust = ClientAddressConfig::default();
680/// trust.trusted_proxies = vec!["10.0.0.0/8".into()];
681/// let app: axum::Router = axum::Router::new()
682///     .route("/", axum::routing::get(|| async { "ok" }))
683///     .layer(ClientAddressLayer::new(&trust).unwrap());
684/// # let _ = app;
685/// ```
686#[derive(Clone, Debug)]
687pub struct ClientAddressLayer {
688    resolver: Arc<Resolver>,
689}
690
691impl ClientAddressLayer {
692    /// The resolution `config` describes.
693    ///
694    /// # Errors
695    ///
696    /// A `trusted_proxies` entry that is neither a CIDR range nor an address.
697    pub fn new(config: &ClientAddressConfig) -> anyhow::Result<Self> {
698        let resolver = Resolver::build(config)
699            .map_err(|e| anyhow::anyhow!("invalid client_address config: {e}"))?;
700        Ok(Self::with(Arc::new(resolver)))
701    }
702
703    /// The layer over a resolver the proxy already compiled.
704    pub(crate) fn with(resolver: Arc<Resolver>) -> Self {
705        Self { resolver }
706    }
707}
708
709impl<S> Layer<S> for ClientAddressLayer {
710    type Service = ClientAddressService<S>;
711
712    fn layer(&self, inner: S) -> ClientAddressService<S> {
713        ClientAddressService {
714            inner,
715            resolver: self.resolver.clone(),
716        }
717    }
718}
719
720/// The service of [`ClientAddressLayer`].
721#[derive(Clone, Debug)]
722pub struct ClientAddressService<S> {
723    inner: S,
724    resolver: Arc<Resolver>,
725}
726
727impl<S, B> Service<http::Request<B>> for ClientAddressService<S>
728where
729    S: Service<http::Request<B>>,
730{
731    type Response = S::Response;
732    type Error = S::Error;
733    type Future = S::Future;
734
735    #[inline]
736    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
737        self.inner.poll_ready(cx)
738    }
739
740    fn call(&mut self, mut request: http::Request<B>) -> Self::Future {
741        let peer = request
742            .extensions()
743            .get::<ConnectInfo<SocketAddr>>()
744            .map(|ConnectInfo(peer)| *peer);
745        self.resolver.apply(&mut request, peer);
746        self.inner.call(request)
747    }
748}
749
750#[cfg(test)]
751mod tests;