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