use std::net::IpAddr;
use crate::addr::{AddrError, Cidr, normalize};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TrustedProxies {
blocks: Vec<Cidr>,
v4: Vec<(u32, u32)>,
v6: Vec<(u128, u128)>,
}
impl TrustedProxies {
pub const MAX: usize = 1024;
pub fn new(blocks: &[Cidr]) -> Result<TrustedProxies, AddrError> {
if blocks.len() > Self::MAX {
return Err(AddrError::TooMany { max: Self::MAX });
}
let mut sorted = blocks.to_vec();
sorted.sort_by_key(|c| sort_key(*c));
sorted.dedup();
let mut v4 = Vec::new();
let mut v6 = Vec::new();
for c in &sorted {
match c.network() {
IpAddr::V4(a) => {
let start = u32::from(a);
let span = if c.prefix_len() == 0 {
u32::MAX
} else {
(1u32 << (32 - u32::from(c.prefix_len()))) - 1
};
v4.push((start, start | span));
}
IpAddr::V6(a) => {
let start = u128::from(a);
let span = if c.prefix_len() == 0 {
u128::MAX
} else {
(1u128 << (128 - u32::from(c.prefix_len()))) - 1
};
v6.push((start, start | span));
}
}
}
Ok(TrustedProxies {
blocks: sorted,
v4: merge(v4),
v6: merge(v6),
})
}
pub fn parse(text: &str) -> Result<TrustedProxies, AddrError> {
let mut blocks = Vec::new();
let mut start = None;
for (i, ch) in text
.char_indices()
.chain(std::iter::once((text.len(), ' ')))
{
let separator = ch == ',' || ch.is_whitespace();
match (start, separator) {
(None, false) => start = Some(i),
(Some(s), true) => {
if blocks.len() == Self::MAX {
return Err(AddrError::TooMany { max: Self::MAX });
}
let block = Cidr::parse(&text[s..i]).map_err(|e| match e {
AddrError::Syntax { at } => AddrError::Syntax { at: s + at },
other => other,
})?;
blocks.push(block);
start = None;
}
_ => {}
}
}
if blocks.is_empty() {
return Err(AddrError::Empty);
}
TrustedProxies::new(&blocks)
}
#[must_use]
pub fn none() -> TrustedProxies {
TrustedProxies {
blocks: Vec::new(),
v4: Vec::new(),
v6: Vec::new(),
}
}
#[must_use]
pub fn loopback() -> TrustedProxies {
let blocks = [
Cidr::new(IpAddr::from([127, 0, 0, 0]), 8),
Cidr::new(IpAddr::from([0u16, 0, 0, 0, 0, 0, 0, 1]), 128),
];
let blocks: Vec<Cidr> = blocks.into_iter().flatten().collect();
TrustedProxies::new(&blocks).unwrap_or_else(|_| TrustedProxies::none())
}
#[inline]
#[must_use]
pub fn contains(&self, ip: IpAddr) -> bool {
match normalize(ip) {
IpAddr::V4(a) => in_ranges(&self.v4, u32::from(a)),
IpAddr::V6(a) => in_ranges(&self.v6, u128::from(a)),
}
}
#[must_use]
pub fn len(&self) -> usize {
self.blocks.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.blocks.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = Cidr> + '_ {
self.blocks.iter().copied()
}
}
fn sort_key(c: Cidr) -> (u8, u128, u8) {
match c.network() {
IpAddr::V4(a) => (4, u128::from(u32::from(a)), c.prefix_len()),
IpAddr::V6(a) => (6, u128::from(a), c.prefix_len()),
}
}
fn merge<T: Copy + Ord + Step>(mut ranges: Vec<(T, T)>) -> Vec<(T, T)> {
ranges.sort_unstable();
let mut out: Vec<(T, T)> = Vec::with_capacity(ranges.len());
for (start, end) in ranges {
match out.last_mut() {
Some(last) if start <= last.1.next_or_max() => last.1 = last.1.max(end),
_ => out.push((start, end)),
}
}
out.shrink_to_fit();
out
}
fn in_ranges<T: Copy + Ord>(ranges: &[(T, T)], x: T) -> bool {
let i = ranges.partition_point(|r| r.0 <= x);
i > 0 && x <= ranges[i - 1].1
}
trait Step {
fn next_or_max(self) -> Self;
}
impl Step for u32 {
fn next_or_max(self) -> Self {
self.saturating_add(1)
}
}
impl Step for u128 {
fn next_or_max(self) -> Self {
self.saturating_add(1)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn ip(s: &str) -> IpAddr {
s.parse().unwrap()
}
#[test]
fn test_trusted_parse_mixed_separators_and_bare_addresses() {
let p = TrustedProxies::parse(" 10.0.0.0/8,192.168.1.10\t::1 ,, 2001:db8::/32 ").unwrap();
assert_eq!(p.len(), 4);
assert!(p.contains(ip("10.255.255.255")));
assert!(p.contains(ip("192.168.1.10")));
assert!(!p.contains(ip("192.168.1.11")));
assert!(p.contains(ip("::1")));
assert!(p.contains(ip("2001:db8:ffff::1")));
assert!(!p.contains(ip("2001:db9::1")));
assert!(!p.contains(ip("11.0.0.0")));
}
#[test]
fn test_trusted_parse_errors() {
assert_eq!(TrustedProxies::parse(" , "), Err(AddrError::Empty));
assert_eq!(
TrustedProxies::parse("10.0.0.0/8, nope"),
Err(AddrError::Syntax { at: 12 })
);
assert_eq!(
TrustedProxies::parse("10.0.0.1/8"),
Err(AddrError::HostBitsSet)
);
let mut many = String::new();
for i in 0..1025u32 {
use std::fmt::Write;
let _ = write!(many, "10.{}.{}.0/24 ", i / 256, i % 256);
}
assert_eq!(
TrustedProxies::parse(&many),
Err(AddrError::TooMany { max: 1024 })
);
}
#[test]
fn test_trusted_nested_and_adjacent_blocks_merge() {
let p = TrustedProxies::parse("10.0.0.0/8 10.1.0.0/16 11.0.0.0/8 0.0.0.0/1").unwrap();
assert_eq!(p.len(), 4, "iter keeps every distinct block");
assert_eq!(
p.v4,
vec![(0, u32::from(std::net::Ipv4Addr::new(127, 255, 255, 255)))]
);
assert!(p.contains(ip("11.200.0.1")));
assert!(!p.contains(ip("128.0.0.0")));
}
#[test]
fn test_trusted_whole_space_and_none() {
let all = TrustedProxies::parse("0.0.0.0/0 ::/0").unwrap();
assert!(all.contains(ip("255.255.255.255")));
assert!(all.contains(ip("ffff::1")));
let none = TrustedProxies::none();
assert!(!none.contains(ip("127.0.0.1")));
let lo = TrustedProxies::loopback();
assert_eq!(lo.len(), 2);
assert!(lo.contains(ip("127.9.9.9")) && lo.contains(ip("::1")) && !lo.contains(ip("::2")));
}
#[test]
fn prop_trusted_contains_matches_a_linear_scan() {
use crate::testing::Gen;
let mut g = Gen::new(0x7275_5354);
for _ in 0..if cfg!(miri) { 20 } else { 2000 } {
let n = 1 + g.below(12) as usize;
let blocks: Vec<Cidr> = (0..n)
.map(|_| {
let a = std::net::Ipv4Addr::from(g.next_u64() as u32 & 0x0F0F_FFFF);
Cidr::new_masked(IpAddr::V4(a), 8 + g.below(25) as u8).unwrap()
})
.collect();
let p = TrustedProxies::new(&blocks).unwrap();
for _ in 0..50 {
let probe = IpAddr::V4(std::net::Ipv4Addr::from(g.next_u64() as u32 & 0x0F0F_FFFF));
assert_eq!(
p.contains(probe),
blocks.iter().any(|b| b.contains(probe)),
"{blocks:?} {probe}"
);
}
}
}
}