use std::net::IpAddr;
use rocket::http::HeaderMap;
use crate::{
ChainHeader, ClientIp, ClientIpConfig, ClientIpSource, TrustAllChainIpSelection,
TrustAllProxyMode,
canonical::canonical_ip,
config::{ChainHeaderKind, TrustModel},
headers::{
ChainEntry, configured_client_ip_header, forwarded_entries, header_lines,
list_header_entries,
},
};
impl ClientIpConfig {
pub fn resolve_client_ip(&self, headers: &HeaderMap<'_>, peer_ip: IpAddr) -> ClientIp {
let peer_ip = canonical_ip(peer_ip);
match &self.trust {
TrustModel::NoProxy => ClientIp::new(peer_ip, ClientIpSource::Socket),
TrustModel::TrustedProxies {
chain_header_order, ..
} => resolve_client_ip_from_trusted_proxies(headers, peer_ip, self, chain_header_order),
TrustModel::TrustAllProxies(mode) => {
resolve_client_ip_trusting_all_proxies(headers, peer_ip, mode)
},
}
}
}
fn resolve_client_ip_from_trusted_proxies(
headers: &HeaderMap<'_>,
socket_ip: IpAddr,
config: &ClientIpConfig,
chain_header_order: &[ChainHeader],
) -> ClientIp {
let Some(socket_proxy_rule) = config.rule_for(socket_ip) else {
return ClientIp::new(socket_ip, ClientIpSource::Socket);
};
if let Some(header) = socket_proxy_rule.client_ip_header()
&& let Some(ip) = configured_client_ip_header(headers, header)
{
return ClientIp::new(ip, ClientIpSource::ConfiguredHeader(header.clone()));
}
if let Some((ip, source)) =
client_ip_from_chain_headers(headers, chain_header_order, |entries| {
first_non_trusted_from_right(entries, config)
})
{
return ClientIp::new(ip, source);
}
ClientIp::new(socket_ip, ClientIpSource::Socket)
}
fn resolve_client_ip_trusting_all_proxies(
headers: &HeaderMap<'_>,
socket_ip: IpAddr,
mode: &TrustAllProxyMode,
) -> ClientIp {
if let Some(header) = mode.client_ip_header()
&& let Some(ip) = configured_client_ip_header(headers, header)
{
return ClientIp::new(ip, ClientIpSource::ConfiguredHeader(header.clone()));
}
if let Some((ip, source)) =
client_ip_from_chain_headers(headers, mode.chain_header_order(), |entries| {
select_trust_all_chain_ip(entries, mode.chain_ip_selection())
})
{
return ClientIp::new(ip, source);
}
ClientIp::new(socket_ip, ClientIpSource::Socket)
}
fn client_ip_from_chain_headers(
headers: &HeaderMap<'_>,
chain_header_order: &[ChainHeader],
mut pick: impl FnMut(&mut dyn DoubleEndedIterator<Item = ChainEntry>) -> Option<IpAddr>,
) -> Option<(IpAddr, ClientIpSource)> {
for chain_header in chain_header_order {
let header = chain_header.as_header_name();
let lines = header_lines(headers, header);
if lines.is_empty() {
continue;
}
let ip = match chain_header.kind() {
ChainHeaderKind::Forwarded => {
let entries = forwarded_entries(lines)?;
pick(&mut entries.into_iter())
},
ChainHeaderKind::XForwardedFor => {
let mut entries = list_header_entries(lines)?;
pick(&mut entries)
},
};
return Some((ip?, ClientIpSource::ChainHeader(header.clone())));
}
None
}
fn select_trust_all_chain_ip(
entries: &mut dyn DoubleEndedIterator<Item = ChainEntry>,
selection: TrustAllChainIpSelection,
) -> Option<IpAddr> {
let entry = match selection {
TrustAllChainIpSelection::Leftmost => entries.next(),
TrustAllChainIpSelection::Rightmost => entries.next_back(),
TrustAllChainIpSelection::SkipRightmostHops(hops) => entries.nth_back(hops),
}?;
match entry {
ChainEntry::Ip(ip) => Some(ip),
ChainEntry::Opaque => None,
}
}
fn first_non_trusted_from_right(
entries: &mut dyn DoubleEndedIterator<Item = ChainEntry>,
config: &ClientIpConfig,
) -> Option<IpAddr> {
while let Some(entry) = entries.next_back() {
match entry {
ChainEntry::Ip(ip) if config.is_trusted_proxy(ip) => continue,
ChainEntry::Ip(ip) => return Some(ip),
ChainEntry::Opaque => return None,
}
}
None
}