pub fn parse_client_ip(
headers: &HeaderMap,
max_hops: usize,
header: ForwardedHeader,
) -> Option<String>Expand description
Extract the client IP max_hops entries in from the right end of
whichever single header header selects — never both. Falls back to
None if that header is absent, empty, or the walk runs off the end of
the chain.
Only one header is ever consulted (#415 remediation). RFC 7239
Forwarded and the legacy X-Forwarded-For are alternatives, not
complements — a real proxy emits one or the other, never both
meaningfully. Consulting Forwarded whenever it happens to be present,
ahead of X-Forwarded-For, let an attacker who knows a deployment
trusts X-Forwarded-For bypass every hop-count/allowlist check just by
sending an entirely unvalidated Forwarded header instead. header
(from crate::trusted_proxy::TrustedProxyConfig::forwarded_header)
names the one header this deployment’s proxy actually writes; the other
is never even inspected.
Callers must only invoke this with a max_hops/header they have
independently established is trustworthy (i.e. after confirming the
request’s socket peer is a configured trusted proxy) — this function
has no notion of trust itself, it only walks the chain. See
crate::trusted_proxy::TrustedProxyConfig and
crate::headers::enrich_context_from_headers for the trust check,
and crate::headers::enrich_context_from_headers for the IP-shape
validation applied to whatever this function selects.
Right-to-left, not left-to-right. The left end of the chain is
exactly the part an untrusted client controls (it can prepend arbitrary
entries), so walking in from the left re-opens the identical spoofing
gap for any chain longer than one hop. max_hops counts inward from
the right: max_hops = 1 takes the rightmost entry (the immediate
trusted proxy’s own contribution); max_hops = 2 takes the
second-from-right entry (what the next hop in reported seeing),
and so on. max_hops = 0 trusts nothing and always returns None.
See decision 5 in docs/design/trusted-proxy-client-ip.md.
Duplicate header occurrences are merged, not dropped (#415
remediation). RFC 7230 §3.2.2: repeated list-type header fields are
semantically equivalent to a single comma-joined value. A proxy that
appends its hop as a second X-Forwarded-For line (rather than
extending the first) must not have that value silently lost to
whichever line an attacker sent first — every occurrence is
concatenated, in wire order, before the chain is walked.