use axum::extract::ConnectInfo;
use ipnet::IpNet;
use std::net::{IpAddr, SocketAddr};
#[must_use]
pub fn resolve<B>(req: &http::Request<B>, trusted_cidrs: &[IpNet]) -> Option<IpAddr> {
let peer_addr = req
.extensions()
.get::<ConnectInfo<SocketAddr>>()
.map(|ci| ci.0)?;
let peer_ip = peer_addr.ip();
if !is_trusted(peer_ip, trusted_cidrs) {
return Some(peer_ip);
}
let xff_value = req.headers().get("x-forwarded-for")?;
let xff_str = std::str::from_utf8(xff_value.as_bytes()).ok()?;
for part in xff_str.rsplit(',') {
let trimmed = part.trim();
if let Ok(ip) = trimmed.parse::<IpAddr>() {
if !is_trusted(ip, trusted_cidrs) {
return Some(ip);
}
}
}
Some(peer_ip)
}
fn is_trusted(ip: IpAddr, cidrs: &[IpNet]) -> bool {
cidrs.iter().any(|net| net.contains(&ip))
}
#[cfg(test)]
mod tests {
use super::*;
use std::net::Ipv4Addr;
fn make_req(peer: &str, xff: Option<&str>) -> http::Request<()> {
let peer_addr: SocketAddr = peer.parse().unwrap();
let mut req = http::Request::new(());
req.extensions_mut()
.insert(ConnectInfo::<SocketAddr>(peer_addr));
if let Some(xff_val) = xff {
req.headers_mut()
.insert("x-forwarded-for", xff_val.parse().unwrap());
}
req
}
fn trusted() -> Vec<IpNet> {
vec!["10.0.0.0/8".parse().unwrap(), "192.168.0.0/16".parse().unwrap()]
}
#[test]
fn no_connect_info_returns_none() {
let req = http::Request::new(());
assert_eq!(resolve(&req, &trusted()), None);
}
#[test]
fn peer_not_trusted_returns_peer() {
let req = make_req("203.0.113.5:1234", Some("1.2.3.4"));
let ip = resolve(&req, &trusted()).unwrap();
assert_eq!(ip, IpAddr::V4(Ipv4Addr::new(203, 0, 113, 5)));
}
#[test]
fn peer_trusted_xff_rightmost_untrusted() {
let req = make_req("192.168.1.1:4000", Some("203.0.113.1, 10.0.0.2"));
let ip = resolve(&req, &trusted()).unwrap();
assert_eq!(ip, IpAddr::V4(Ipv4Addr::new(203, 0, 113, 1)));
}
#[test]
fn peer_trusted_xff_all_trusted_returns_peer() {
let req = make_req("10.0.0.1:80", Some("10.0.0.2, 10.0.0.3"));
let ip = resolve(&req, &trusted()).unwrap();
assert_eq!(ip, IpAddr::V4(Ipv4Addr::from([10, 0, 0, 1])));
}
#[test]
fn malformed_xff_returns_peer() {
let req = make_req("192.168.1.5:9000", Some("not-an-ip, also-bad"));
let ip = resolve(&req, &trusted()).unwrap();
assert_eq!(ip, IpAddr::V4(Ipv4Addr::new(192, 168, 1, 5)));
}
}