Decide which address a request actually came from.
This is the piece an allowlist cannot afford to get wrong: X-Forwarded-For
is client-controlled, so reading it for a peer that is not itself a trusted
proxy is the same as letting the caller choose its own IP and walk straight
through the route guard.
The rule, in full:
- The peer is
conn.remote_ip, normalized. - No trusted proxies configured, or the peer matches none of them → the peer is the client. The header is not read at all.
- Otherwise the peer is a proxy, so the chain is trustworthy as far as this
node can tell: every
x-forwarded-forvalue is joined in the order the request carried them, split on commas and trimmed, and empty entries are dropped. Nothing left → the peer. - The chain is walked right to left, one entry at a time and only as far as
needed. Proxies append, so the rightmost entries are the ones this node's
own proxies wrote, and the rightmost untrusted entry is the closest
client we can vouch for: that entry is the client, and the entries to its
left — the prefix a client can forge — are never even parsed. An entry
that cannot be read is
:error: a chain we cannot fully account for is not one we believe, and the request is denied rather than falling back to the proxy's own address, which is exactly what a forged entry would be trying to achieve. If every entry is trusted, the leftmost one is the client. - An entry is a bare IPv4 or IPv6 address, or one of the
host:portforms1.2.3.4:5678and[2001:db8::1]:443(the brackets are how an IPv6 address, which owns colons itself, carries a port). Anything else —garbage,1.2.3.4:abc,1.2.3.4:99999,[::1, a non-UTF-8 byte — is an entry that cannot be read.
The result is normalized, so a peer or an entry written as the IPv4-mapped
::ffff:1.2.3.4 resolves to an IPv4 address and matches IPv4 rules.
Summary
Functions
Resolve the client address for conn, given the CIDRs whose peers may set
X-Forwarded-For.
Functions
@spec resolve(Plug.Conn.t(), [Ankusa.Net.cidr()]) :: {:ok, :inet.ip_address()} | :error
Resolve the client address for conn, given the CIDRs whose peers may set
X-Forwarded-For.
:error when conn.remote_ip is nil — a conn with no peer at all (a test
conn with the field cleared) — or when an entry the chain depends on cannot be
read. Callers treat both as an IP rejection: there is no address to match
against rules, and neither case may quietly become the proxy's own address.