1use std::net::IpAddr;
4
5use crate::addr::{AddrError, Cidr, normalize};
6
7#[derive(Clone, Debug, PartialEq, Eq)]
26pub struct TrustedProxies {
27 blocks: Vec<Cidr>,
28 v4: Vec<(u32, u32)>,
29 v6: Vec<(u128, u128)>,
30}
31
32impl TrustedProxies {
33 pub const MAX: usize = 1024;
35
36 pub fn new(blocks: &[Cidr]) -> Result<TrustedProxies, AddrError> {
49 if blocks.len() > Self::MAX {
50 return Err(AddrError::TooMany { max: Self::MAX });
51 }
52 let mut sorted = blocks.to_vec();
53 sorted.sort_by_key(|c| sort_key(*c));
54 sorted.dedup();
55 let mut v4 = Vec::new();
56 let mut v6 = Vec::new();
57 for c in &sorted {
58 match c.network() {
59 IpAddr::V4(a) => {
60 let start = u32::from(a);
61 let span = if c.prefix_len() == 0 {
62 u32::MAX
63 } else {
64 (1u32 << (32 - u32::from(c.prefix_len()))) - 1
65 };
66 v4.push((start, start | span));
67 }
68 IpAddr::V6(a) => {
69 let start = u128::from(a);
70 let span = if c.prefix_len() == 0 {
71 u128::MAX
72 } else {
73 (1u128 << (128 - u32::from(c.prefix_len()))) - 1
74 };
75 v6.push((start, start | span));
76 }
77 }
78 }
79 Ok(TrustedProxies {
80 blocks: sorted,
81 v4: merge(v4),
82 v6: merge(v6),
83 })
84 }
85
86 pub fn parse(text: &str) -> Result<TrustedProxies, AddrError> {
101 let mut blocks = Vec::new();
102 let mut start = None;
103 for (i, ch) in text
104 .char_indices()
105 .chain(std::iter::once((text.len(), ' ')))
106 {
107 let separator = ch == ',' || ch.is_whitespace();
108 match (start, separator) {
109 (None, false) => start = Some(i),
110 (Some(s), true) => {
111 if blocks.len() == Self::MAX {
112 return Err(AddrError::TooMany { max: Self::MAX });
113 }
114 let block = Cidr::parse(&text[s..i]).map_err(|e| match e {
115 AddrError::Syntax { at } => AddrError::Syntax { at: s + at },
116 other => other,
117 })?;
118 blocks.push(block);
119 start = None;
120 }
121 _ => {}
122 }
123 }
124 if blocks.is_empty() {
125 return Err(AddrError::Empty);
126 }
127 TrustedProxies::new(&blocks)
128 }
129
130 #[must_use]
136 pub fn none() -> TrustedProxies {
137 TrustedProxies {
138 blocks: Vec::new(),
139 v4: Vec::new(),
140 v6: Vec::new(),
141 }
142 }
143
144 #[must_use]
150 pub fn loopback() -> TrustedProxies {
151 let blocks = [
152 Cidr::new(IpAddr::from([127, 0, 0, 0]), 8),
153 Cidr::new(IpAddr::from([0u16, 0, 0, 0, 0, 0, 0, 1]), 128),
154 ];
155 let blocks: Vec<Cidr> = blocks.into_iter().flatten().collect();
156 TrustedProxies::new(&blocks).unwrap_or_else(|_| TrustedProxies::none())
157 }
158
159 #[inline]
168 #[must_use]
169 pub fn contains(&self, ip: IpAddr) -> bool {
170 match normalize(ip) {
171 IpAddr::V4(a) => in_ranges(&self.v4, u32::from(a)),
172 IpAddr::V6(a) => in_ranges(&self.v6, u128::from(a)),
173 }
174 }
175
176 #[must_use]
178 pub fn len(&self) -> usize {
179 self.blocks.len()
180 }
181
182 #[must_use]
184 pub fn is_empty(&self) -> bool {
185 self.blocks.is_empty()
186 }
187
188 pub fn iter(&self) -> impl Iterator<Item = Cidr> + '_ {
190 self.blocks.iter().copied()
191 }
192}
193
194fn sort_key(c: Cidr) -> (u8, u128, u8) {
195 match c.network() {
196 IpAddr::V4(a) => (4, u128::from(u32::from(a)), c.prefix_len()),
197 IpAddr::V6(a) => (6, u128::from(a), c.prefix_len()),
198 }
199}
200
201fn merge<T: Copy + Ord + Step>(mut ranges: Vec<(T, T)>) -> Vec<(T, T)> {
203 ranges.sort_unstable();
204 let mut out: Vec<(T, T)> = Vec::with_capacity(ranges.len());
205 for (start, end) in ranges {
206 match out.last_mut() {
207 Some(last) if start <= last.1.next_or_max() => last.1 = last.1.max(end),
208 _ => out.push((start, end)),
209 }
210 }
211 out.shrink_to_fit();
212 out
213}
214
215fn in_ranges<T: Copy + Ord>(ranges: &[(T, T)], x: T) -> bool {
216 let i = ranges.partition_point(|r| r.0 <= x);
218 i > 0 && x <= ranges[i - 1].1
219}
220
221trait Step {
223 fn next_or_max(self) -> Self;
224}
225
226impl Step for u32 {
227 fn next_or_max(self) -> Self {
228 self.saturating_add(1)
229 }
230}
231
232impl Step for u128 {
233 fn next_or_max(self) -> Self {
234 self.saturating_add(1)
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241
242 fn ip(s: &str) -> IpAddr {
243 s.parse().unwrap()
244 }
245
246 #[test]
247 fn test_trusted_parse_mixed_separators_and_bare_addresses() {
248 let p = TrustedProxies::parse(" 10.0.0.0/8,192.168.1.10\t::1 ,, 2001:db8::/32 ").unwrap();
249 assert_eq!(p.len(), 4);
250 assert!(p.contains(ip("10.255.255.255")));
251 assert!(p.contains(ip("192.168.1.10")));
252 assert!(!p.contains(ip("192.168.1.11")));
253 assert!(p.contains(ip("::1")));
254 assert!(p.contains(ip("2001:db8:ffff::1")));
255 assert!(!p.contains(ip("2001:db9::1")));
256 assert!(!p.contains(ip("11.0.0.0")));
257 }
258
259 #[test]
260 fn test_trusted_parse_errors() {
261 assert_eq!(TrustedProxies::parse(" , "), Err(AddrError::Empty));
262 assert_eq!(
263 TrustedProxies::parse("10.0.0.0/8, nope"),
264 Err(AddrError::Syntax { at: 12 })
265 );
266 assert_eq!(
267 TrustedProxies::parse("10.0.0.1/8"),
268 Err(AddrError::HostBitsSet)
269 );
270 let mut many = String::new();
271 for i in 0..1025u32 {
272 use std::fmt::Write;
273 let _ = write!(many, "10.{}.{}.0/24 ", i / 256, i % 256);
274 }
275 assert_eq!(
276 TrustedProxies::parse(&many),
277 Err(AddrError::TooMany { max: 1024 })
278 );
279 }
280
281 #[test]
282 fn test_trusted_nested_and_adjacent_blocks_merge() {
283 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();
284 assert_eq!(p.len(), 4, "iter keeps every distinct block");
285 assert_eq!(
286 p.v4,
287 vec![(0, u32::from(std::net::Ipv4Addr::new(127, 255, 255, 255)))]
288 );
289 assert!(p.contains(ip("11.200.0.1")));
290 assert!(!p.contains(ip("128.0.0.0")));
291 }
292
293 #[test]
294 fn test_trusted_whole_space_and_none() {
295 let all = TrustedProxies::parse("0.0.0.0/0 ::/0").unwrap();
296 assert!(all.contains(ip("255.255.255.255")));
297 assert!(all.contains(ip("ffff::1")));
298 let none = TrustedProxies::none();
299 assert!(!none.contains(ip("127.0.0.1")));
300 let lo = TrustedProxies::loopback();
301 assert_eq!(lo.len(), 2);
302 assert!(lo.contains(ip("127.9.9.9")) && lo.contains(ip("::1")) && !lo.contains(ip("::2")));
303 }
304
305 #[test]
307 fn prop_trusted_contains_matches_a_linear_scan() {
308 use crate::testing::Gen;
309 let mut g = Gen::new(0x7275_5354);
310 for _ in 0..if cfg!(miri) { 20 } else { 2000 } {
311 let n = 1 + g.below(12) as usize;
312 let blocks: Vec<Cidr> = (0..n)
313 .map(|_| {
314 let a = std::net::Ipv4Addr::from(g.next_u64() as u32 & 0x0F0F_FFFF);
315 Cidr::new_masked(IpAddr::V4(a), 8 + g.below(25) as u8).unwrap()
316 })
317 .collect();
318 let p = TrustedProxies::new(&blocks).unwrap();
319 for _ in 0..50 {
320 let probe = IpAddr::V4(std::net::Ipv4Addr::from(g.next_u64() as u32 & 0x0F0F_FFFF));
321 assert_eq!(
322 p.contains(probe),
323 blocks.iter().any(|b| b.contains(probe)),
324 "{blocks:?} {probe}"
325 );
326 }
327 }
328 }
329}