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;