use axum::{body::Body, extract::State, http::Request, middleware::Next, response::Response};
use serde::{Deserialize, Serialize};
use std::net::{IpAddr, Ipv4Addr};
#[derive(Clone, Debug, Copy)]
pub struct ClientIp(pub IpAddr);
fn ip_in_cidr(ip: &IpAddr, network: &IpAddr, prefix_len: u8) -> bool {
match (ip, network) {
(IpAddr::V4(ip), IpAddr::V4(net)) => {
if prefix_len == 0 {
return true;
}
if prefix_len > 32 {
return false;
}
let shift = 32 - prefix_len;
let mask = if shift == 32 { 0u32 } else { u32::MAX << shift };
(u32::from_be_bytes(ip.octets()) & mask) == (u32::from_be_bytes(net.octets()) & mask)
}
(IpAddr::V6(ip), IpAddr::V6(net)) => {
if prefix_len == 0 {
return true;
}
if prefix_len > 128 {
return false;
}
let shift = 128 - prefix_len;
let mask = if shift == 128 {
0u128
} else {
u128::MAX << shift
};
(u128::from_be_bytes(ip.octets()) & mask) == (u128::from_be_bytes(net.octets()) & mask)
}
_ => false,
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TrustedProxies {
exact: Vec<IpAddr>,
cidrs: Vec<(IpAddr, u8)>,
}
impl Default for TrustedProxies {
fn default() -> Self {
Self {
exact: vec![],
cidrs: vec![
("127.0.0.0".parse().unwrap(), 8), ("10.0.0.0".parse().unwrap(), 8), ("172.16.0.0".parse().unwrap(), 12), ("192.168.0.0".parse().unwrap(), 16), ("::1".parse().unwrap(), 128), ("fc00::".parse().unwrap(), 7), ],
}
}
}
impl TrustedProxies {
pub fn new(exact: Vec<IpAddr>, cidrs: Vec<(IpAddr, u8)>) -> Self {
Self { exact, cidrs }
}
pub fn is_trusted(&self, ip: &IpAddr) -> bool {
if self.exact.contains(ip) {
return true;
}
self.cidrs
.iter()
.any(|(net, prefix)| ip_in_cidr(ip, net, *prefix))
}
pub fn extract_client_ip(&self, req: &Request<Body>, conn_ip: Option<IpAddr>) -> IpAddr {
let conn_ip_value = conn_ip.unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST));
if !self.is_trusted(&conn_ip_value) {
return conn_ip_value;
}
let xff: Vec<IpAddr> = req
.headers()
.get("x-forwarded-for")
.and_then(|v| v.to_str().ok())
.map(|s| {
s.split(',')
.filter_map(|ip| ip.trim().parse().ok())
.collect()
})
.unwrap_or_default();
if xff.is_empty() {
return conn_ip_value;
}
for ip in xff.iter().rev() {
if !self.is_trusted(ip) {
return *ip;
}
}
xff.into_iter().next().unwrap_or(conn_ip_value)
}
}
use crate::utils::aliases::AEngine;
pub async fn trusted_proxies_middleware(
State(engine): State<AEngine>,
mut req: Request<Body>,
next: Next,
) -> Response {
use axum::extract::ConnectInfo;
use std::net::SocketAddr;
let conn_ip = req
.extensions()
.get::<ConnectInfo<SocketAddr>>()
.map(|ci| ci.0.ip());
let client_ip = engine.trusted_proxies.extract_client_ip(&req, conn_ip);
req.extensions_mut().insert(ClientIp(client_ip));
next.run(req).await
}