use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use std::time::Duration;
use crate::addr::{Cidr, denied};
#[derive(Clone, Debug)]
pub struct Policy {
pub(crate) deny: Vec<Cidr>,
pub(crate) allow: Vec<Cidr>,
pub(crate) sources: Sources,
pub(crate) max_redirects: usize,
pub(crate) timeout: Duration,
pub(crate) max_body: usize,
}
impl Default for Policy {
fn default() -> Policy {
Policy {
deny: Vec::new(),
allow: Vec::new(),
sources: Sources::default(),
max_redirects: 3,
timeout: Duration::from_secs(10),
max_body: 10 * 1024 * 1024,
}
}
}
impl Policy {
pub fn builder() -> PolicyBuilder {
PolicyBuilder::default()
}
pub fn allows(&self, ip: IpAddr) -> bool {
if self.deny.iter().any(|net| net.contains(ip)) {
return false;
}
!denied(ip) || self.allow.iter().any(|net| net.contains(ip))
}
pub fn deny(&self) -> &[Cidr] {
&self.deny
}
pub fn allow(&self) -> &[Cidr] {
&self.allow
}
pub fn source_v4(&self) -> Option<Ipv4Addr> {
match self.sources.v4 {
Some(IpAddr::V4(ip)) => Some(ip),
_ => None,
}
}
pub fn source_v6(&self) -> Option<Ipv6Addr> {
match self.sources.v6 {
Some(IpAddr::V6(ip)) => Some(ip),
_ => None,
}
}
pub fn max_redirects(&self) -> usize {
self.max_redirects
}
pub fn timeout(&self) -> Duration {
self.timeout
}
pub fn max_body(&self) -> usize {
self.max_body
}
}
#[derive(Clone, Debug, Default)]
#[must_use]
pub struct PolicyBuilder {
policy: Policy,
}
impl PolicyBuilder {
pub fn deny(mut self, net: Cidr) -> PolicyBuilder {
self.policy.deny.push(net);
self
}
pub fn allow(mut self, net: Cidr) -> PolicyBuilder {
self.policy.allow.push(net);
self
}
pub fn source(mut self, ip: IpAddr) -> PolicyBuilder {
match ip {
IpAddr::V4(_) => self.policy.sources.v4 = Some(ip),
IpAddr::V6(_) => self.policy.sources.v6 = Some(ip),
}
self
}
pub fn max_redirects(mut self, n: usize) -> PolicyBuilder {
self.policy.max_redirects = n;
self
}
pub fn timeout(mut self, timeout: Duration) -> PolicyBuilder {
self.policy.timeout = timeout;
self
}
pub fn max_body(mut self, bytes: usize) -> PolicyBuilder {
self.policy.max_body = bytes;
self
}
pub fn build(self) -> Policy {
self.policy
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) struct Sources {
pub(crate) v4: Option<IpAddr>,
pub(crate) v6: Option<IpAddr>,
}
impl Sources {
pub(crate) fn for_peer(&self, to: IpAddr) -> Result<Option<IpAddr>, ()> {
if self.v4.is_none() && self.v6.is_none() {
return Ok(None);
}
match to {
IpAddr::V4(_) => self.v4.map(Some).ok_or(()),
IpAddr::V6(_) => self.v6.map(Some).ok_or(()),
}
}
pub(crate) fn reachable(&self, addrs: Vec<SocketAddr>) -> Vec<SocketAddr> {
addrs
.into_iter()
.filter(|a| self.for_peer(a.ip()).is_ok())
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn ip(s: &str) -> IpAddr {
s.parse().unwrap()
}
#[test]
fn deny_beats_allow_beats_builtin() {
let p = Policy::default();
assert!(!p.allows(ip("127.0.0.1")));
assert!(p.allows(ip("8.8.8.8")));
let p = Policy::builder()
.allow("127.0.0.0/8".parse().unwrap())
.build();
assert!(p.allows(ip("127.0.0.1")));
assert!(p.allows(ip("::ffff:127.0.0.1")));
assert!(
!p.allows(ip("::1")),
"allowing 127/8 says nothing about ::1"
);
assert!(!p.allows(ip("10.1.2.3")));
let p = Policy::builder()
.allow("127.0.0.0/8".parse().unwrap())
.deny("127.0.0.2".parse().unwrap())
.deny("8.8.8.0/24".parse().unwrap())
.build();
assert!(p.allows(ip("127.0.0.1")));
assert!(!p.allows(ip("127.0.0.2")));
assert!(!p.allows(ip("8.8.8.8")));
assert!(
!p.allows(ip("64:ff9b::808:808")),
"NAT64 spelling of a denied address"
);
assert!(p.allows(ip("8.8.4.4")));
let p = Policy::builder().deny("::/0".parse().unwrap()).build();
assert!(!p.allows(ip("::ffff:8.8.8.8")));
assert!(!p.allows(ip("::8.8.8.8")));
assert!(!p.allows(ip("2606:4700::1111")));
assert!(p.allows(ip("8.8.8.8")));
let p = Policy::builder()
.deny("64:ff9b::/96".parse().unwrap())
.build();
assert!(!p.allows(ip("64:ff9b::808:808")));
}
#[test]
fn no_sources_means_the_kernel_picks() {
let none = Sources::default();
assert_eq!(none.for_peer(ip("192.0.2.1")), Ok(None));
let addrs = vec!["[2001:db8::1]:443".parse().unwrap()];
assert_eq!(none.reachable(addrs.clone()), addrs);
}
#[test]
fn a_family_without_a_source_is_unreachable() {
let v4_only = Policy::builder().source(ip("198.51.100.7")).build().sources;
assert_eq!(
v4_only.for_peer(ip("192.0.2.1")),
Ok(Some(ip("198.51.100.7")))
);
assert!(v4_only.for_peer(ip("2001:db8::1")).is_err());
let both: Vec<SocketAddr> = vec![
"[2001:db8::1]:443".parse().unwrap(),
"192.0.2.1:443".parse().unwrap(),
];
assert_eq!(
v4_only.reachable(both),
vec!["192.0.2.1:443".parse().unwrap()]
);
assert!(
v4_only
.reachable(vec!["[2001:db8::1]:443".parse().unwrap()])
.is_empty()
);
}
#[test]
fn getters_read_back_what_the_builder_set() {
let d = Policy::default();
assert_eq!(
(d.max_redirects(), d.timeout(), d.max_body()),
(3, Duration::from_secs(10), 10 * 1024 * 1024)
);
assert!(d.deny().is_empty() && d.allow().is_empty());
assert_eq!((d.source_v4(), d.source_v6()), (None, None));
let p = Policy::builder()
.deny("8.8.8.0/24".parse().unwrap())
.deny("1.1.1.1".parse().unwrap())
.allow("127.0.0.0/8".parse().unwrap())
.source(ip("198.51.100.7"))
.source(ip("2001:db8::7"))
.max_redirects(0)
.timeout(Duration::from_millis(1500))
.max_body(99)
.build();
let shown: Vec<String> = p.deny().iter().map(ToString::to_string).collect();
assert_eq!(shown, ["8.8.8.0/24", "1.1.1.1/32"]);
assert_eq!(p.allow()[0].to_string(), "127.0.0.0/8");
assert_eq!(p.source_v4(), Some("198.51.100.7".parse().unwrap()));
assert_eq!(p.source_v6(), Some("2001:db8::7".parse().unwrap()));
assert_eq!(
(p.max_redirects(), p.timeout(), p.max_body()),
(0, Duration::from_millis(1500), 99)
);
}
}