use crate::protocol::name::DnsName;
use crate::protocol::rdata::RData;
use std::net::{Ipv4Addr, Ipv6Addr};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PolicyAction {
NxDomain,
NoData,
Passthru,
RedirectA(Ipv4Addr),
RedirectAAAA(Ipv6Addr),
RedirectCname(DnsName),
Drop,
}
#[derive(Debug, Clone)]
pub struct RpzRule {
pub trigger: RpzTrigger,
pub action: PolicyAction,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RpzTrigger {
QName(DnsName),
QNameWildcard(DnsName),
}
impl RpzRule {
pub fn matches(&self, qname: &DnsName) -> bool {
match &self.trigger {
RpzTrigger::QName(name) => qname == name,
RpzTrigger::QNameWildcard(base) => {
let base_labels = base.labels();
let qname_labels = qname.labels();
if qname_labels.len() <= base_labels.len() {
return false;
}
let offset = qname_labels.len() - base_labels.len();
&qname_labels[offset..] == base_labels
}
}
}
}
pub fn action_from_rdata(rdata: &RData) -> PolicyAction {
match rdata {
RData::CNAME(name) => {
let dotted = name.to_dotted();
match dotted.as_str() {
"." => PolicyAction::NxDomain,
"*." => PolicyAction::NoData,
"rpz-passthru." => PolicyAction::Passthru,
"rpz-drop." => PolicyAction::Drop,
_ => PolicyAction::RedirectCname(name.clone()),
}
}
RData::A(ip) => PolicyAction::RedirectA(*ip),
RData::AAAA(ip) => PolicyAction::RedirectAAAA(*ip),
_ => PolicyAction::NxDomain, }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_qname_match() {
let rule = RpzRule {
trigger: RpzTrigger::QName(DnsName::from_str("bad.example.com").unwrap()),
action: PolicyAction::NxDomain,
};
assert!(rule.matches(&DnsName::from_str("bad.example.com").unwrap()));
assert!(!rule.matches(&DnsName::from_str("good.example.com").unwrap()));
}
#[test]
fn test_wildcard_match() {
let rule = RpzRule {
trigger: RpzTrigger::QNameWildcard(DnsName::from_str("example.com").unwrap()),
action: PolicyAction::NxDomain,
};
assert!(rule.matches(&DnsName::from_str("bad.example.com").unwrap()));
assert!(rule.matches(&DnsName::from_str("deep.sub.example.com").unwrap()));
assert!(!rule.matches(&DnsName::from_str("example.com").unwrap())); assert!(!rule.matches(&DnsName::from_str("other.com").unwrap()));
}
#[test]
fn test_action_from_rdata() {
assert_eq!(
action_from_rdata(&RData::CNAME(DnsName::from_str(".").unwrap())),
PolicyAction::NxDomain
);
assert_eq!(
action_from_rdata(&RData::A(Ipv4Addr::new(127, 0, 0, 1))),
PolicyAction::RedirectA(Ipv4Addr::new(127, 0, 0, 1))
);
}
}