Skip to content

Commit 837dd15

Browse files
committed
Use canonical addresses for the IP filter
1 parent 49df4b5 commit 837dd15

2 files changed

Lines changed: 53 additions & 19 deletions

File tree

ntp-proto/src/ipfilter.rs

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -200,19 +200,20 @@ impl IpFilter {
200200

201201
/// Check whether a given ip address is contained in the filter.
202202
/// Complexity: O(1)
203-
pub fn is_in(&self, addr: &IpAddr) -> bool {
203+
pub fn is_in(&self, addr: IpAddr) -> bool {
204+
let addr = addr.to_canonical();
204205
match addr {
205206
IpAddr::V4(addr) => self.is_in4(addr),
206207
IpAddr::V6(addr) => self.is_in6(addr),
207208
}
208209
}
209210

210-
fn is_in4(&self, addr: &Ipv4Addr) -> bool {
211+
fn is_in4(&self, addr: Ipv4Addr) -> bool {
211212
self.ipv4_filter
212213
.lookup((u32::from_be_bytes(addr.octets()) as u128) << 96)
213214
}
214215

215-
fn is_in6(&self, addr: &Ipv6Addr) -> bool {
216+
fn is_in6(&self, addr: Ipv6Addr) -> bool {
216217
self.ipv6_filter.lookup(u128::from_be_bytes(addr.octets()))
217218
}
218219
}
@@ -263,13 +264,15 @@ pub mod fuzz {
263264
let filter = IpFilter::new(nets);
264265

265266
for addr in addr {
266-
assert_eq!(filter.is_in(addr), any_contains(nets, addr));
267+
assert_eq!(filter.is_in(*addr), any_contains(nets, addr));
267268
}
268269
}
269270
}
270271

271272
#[cfg(test)]
272273
mod tests {
274+
use crate::SubnetParseError;
275+
273276
use super::*;
274277

275278
#[test]
@@ -295,31 +298,45 @@ mod tests {
295298
fn test_filter() {
296299
let filter = IpFilter::new(&[
297300
"127.0.0.0/24".parse().unwrap(),
298-
"::FFFF:0000:0000/96".parse().unwrap(),
301+
"::FFFF:192.168.0.0/104".parse().unwrap(),
299302
]);
300-
assert!(filter.is_in(&"127.0.0.1".parse().unwrap()));
301-
assert!(!filter.is_in(&"192.168.1.1".parse().unwrap()));
302-
assert!(filter.is_in(&"::FFFF:ABCD:0123".parse().unwrap()));
303-
assert!(!filter.is_in(&"::FEEF:ABCD:0123".parse().unwrap()));
303+
assert!(filter.is_in("127.0.0.1".parse().unwrap()));
304+
assert!(!filter.is_in("10.0.1.1".parse().unwrap()));
305+
assert!(filter.is_in("::FFFF:192.168.1.1".parse().unwrap()));
306+
assert!(!filter.is_in("::FFFF:10.0.0.5".parse().unwrap()));
307+
assert!(!filter.is_in("::FEEF:ABCD:1234".parse().unwrap()));
308+
}
309+
310+
#[test]
311+
fn test_subnet_mapped_ipv4_overlap() {
312+
let subnet_err = "::FFFF:192.168.0.0/95".parse::<IpSubnet>().unwrap_err();
313+
assert_eq!(subnet_err, SubnetParseError::MaskV4Range);
314+
}
315+
316+
#[test]
317+
fn test_subnet_mapped_ipv4() {
318+
let subnet = "::FFFF:192.168.0.0/120".parse::<IpSubnet>().unwrap();
319+
assert_eq!(subnet.addr, "192.168.0.0".parse::<IpAddr>().unwrap());
320+
assert_eq!(subnet.mask, 24);
304321
}
305322

306323
#[test]
307324
fn test_subnet_edgecases() {
308325
let filter = IpFilter::new(&["0.0.0.0/0".parse().unwrap(), "::/0".parse().unwrap()]);
309326

310-
assert!(filter.is_in(&"0.0.0.0".parse().unwrap()));
311-
assert!(filter.is_in(&"255.255.255.255".parse().unwrap()));
312-
assert!(filter.is_in(&"::".parse().unwrap()));
313-
assert!(filter.is_in(&"FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF".parse().unwrap()));
327+
assert!(filter.is_in("0.0.0.0".parse().unwrap()));
328+
assert!(filter.is_in("255.255.255.255".parse().unwrap()));
329+
assert!(filter.is_in("::".parse().unwrap()));
330+
assert!(filter.is_in("FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF".parse().unwrap()));
314331

315332
let filter = IpFilter::new(&[
316333
"1.2.3.4/32".parse().unwrap(),
317334
"10:32:54:76:98:BA:DC:FE/128".parse().unwrap(),
318335
]);
319336

320-
assert!(filter.is_in(&"1.2.3.4".parse().unwrap()));
321-
assert!(!filter.is_in(&"1.2.3.5".parse().unwrap()));
322-
assert!(filter.is_in(&"10:32:54:76:98:BA:DC:FE".parse().unwrap()));
323-
assert!(!filter.is_in(&"10:32:54:76:98:BA:DC:FF".parse().unwrap()));
337+
assert!(filter.is_in("1.2.3.4".parse().unwrap()));
338+
assert!(!filter.is_in("1.2.3.5".parse().unwrap()));
339+
assert!(filter.is_in("10:32:54:76:98:BA:DC:FE".parse().unwrap()));
340+
assert!(!filter.is_in("10:32:54:76:98:BA:DC:FF".parse().unwrap()));
324341
}
325342
}

ntp-proto/src/server.rs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,10 @@ impl<C> Server<C> {
125125
}
126126

127127
fn intended_action(&mut self, client_ip: IpAddr) -> (ServerResponse, ServerReason) {
128-
if self.denyfilter.is_in(&client_ip) {
128+
if self.denyfilter.is_in(client_ip) {
129129
// First apply denylist
130130
(self.config.denylist.action.into(), ServerReason::Policy)
131-
} else if !self.allowfilter.is_in(&client_ip) {
131+
} else if !self.allowfilter.is_in(client_ip) {
132132
// Then allowlist
133133
(self.config.allowlist.action.into(), ServerReason::Policy)
134134
} else if !self.client_cache.is_allowed(
@@ -422,6 +422,7 @@ pub enum SubnetParseError {
422422
Subnet,
423423
Ip(AddrParseError),
424424
Mask,
425+
MaskV4Range,
425426
}
426427

427428
impl std::error::Error for SubnetParseError {}
@@ -432,6 +433,10 @@ impl Display for SubnetParseError {
432433
Self::Subnet => write!(f, "Invalid subnet syntax"),
433434
Self::Ip(e) => write!(f, "{e} in subnet"),
434435
Self::Mask => write!(f, "Invalid subnet mask"),
436+
Self::MaskV4Range => write!(
437+
f,
438+
"Subnet mask overflows the IPv4 range of an IPv4-mapped IPv6 address"
439+
),
435440
}
436441
}
437442
}
@@ -449,13 +454,25 @@ impl std::str::FromStr for IpSubnet {
449454
let (addr, mask) = s.split_once('/').ok_or(SubnetParseError::Subnet)?;
450455
let addr: IpAddr = addr.parse()?;
451456
let mask: u8 = mask.parse().map_err(|_| SubnetParseError::Mask)?;
457+
458+
// Canonicalize IPv4-mapped IPv6 addresses (e.g. `::ffff:192.168.0.0`)
459+
// to their IPv4 form so they match against canonicalized filtered IPs.
460+
let (addr, mask) = match (addr, addr.to_canonical()) {
461+
(IpAddr::V6(_), canonical @ IpAddr::V4(_)) => {
462+
let mask = mask.checked_sub(96).ok_or(SubnetParseError::MaskV4Range)?;
463+
(canonical, mask)
464+
}
465+
_ => (addr, mask),
466+
};
467+
452468
let max_mask = match addr {
453469
IpAddr::V4(_) => 32,
454470
IpAddr::V6(_) => 128,
455471
};
456472
if mask > max_mask {
457473
return Err(SubnetParseError::Mask);
458474
}
475+
459476
Ok(IpSubnet { addr, mask })
460477
}
461478
}

0 commit comments

Comments
 (0)