Trusted Proxies
Algorithm
- Read the direct connection IP (
ConnectInfo<SocketAddr>). - If it is not in the trusted list → return it as the real client IP (XFF is ignored).
- If it is trusted → parse
X-Forwarded-For, walk from right to left:- Skip entries that are trusted proxies.
- Return the first untrusted entry as the real client IP.
- If all entries are trusted → return the leftmost (the client's own claim).
The result is injected into request extensions as ClientIp(IpAddr).
Default trusted list
RFC 1918 private networks and loopback addresses:
| CIDR | Description |
|---|---|
127.0.0.0/8 | IPv4 loopback |
10.0.0.0/8 | Class A private |
172.16.0.0/12 | Class B private |
192.168.0.0/16 | Class C private |
::1/128 | IPv6 loopback |
fc00::/7 | IPv6 unique local |
Configuration via the builder
.middleware(|m| {
m.with_trusted_proxies(|t| {
// Start from the private network defaults and add a CDN IP
t.proxy("203.0.113.42")
.cidr("198.51.100.0/24")
})
})
To disable XFF processing entirely (direct server, no proxy):
.middleware(|m| {
m.with_trusted_proxies(|t| t.none())
})
Available methods
| Method | Description |
|---|---|
.private_networks() | Reset to RFC 1918 + loopback (the default) |
.proxy("1.2.3.4") | Trust an exact IP |
.cidr("10.0.0.0/8") | Trust a CIDR range |
.none() | Clear all trusted entries (XFF ignored) |
Methods are cumulative. .none() clears the list; subsequent calls add to the empty list.
Accessing the client IP in handlers
use axum::Extension;
use runique::middleware::ClientIp;
pub async fn my_handler(
Extension(client_ip): Extension<ClientIp>,
engine: Arc<RuniqueEngine>,
req: Request,
) -> Response {
let ip = client_ip.0; // IpAddr
// ...
}
Keeping the default
Do not call .with_trusted_proxies — the RFC 1918 preset applies automatically.