use std::net::IpAddr;
use std::str::FromStr;
use ipnet::IpNet;
use bgp_models::prelude::*;
use regex::Regex;
use crate::ParserError;
use crate::ParserError::FilterError;
pub enum Filter {
OriginAsn(u32),
Prefix(IpNet, PrefixMatchType),
PeerIp(IpAddr),
PeerIps(Vec<IpAddr>),
PeerAsn(u32),
Type(ElemType),
TsStart(f64),
TsEnd(f64),
AsPath(Regex),
}
pub enum PrefixMatchType {
Exact,
IncludeSuper,
IncludeSub,
IncludeSuperSub,
}
impl Filter {
pub fn new(filter_type: &str, filter_value: &str) -> Result<Filter, ParserError> {
match filter_type {
"origin_asn" => {
match u32::from_str(filter_value) {
Ok(v) => {Ok(Filter::OriginAsn(v))},
Err(_) => {
Err(FilterError(format!("cannot parse origin asn from {}", filter_value)))
}
}
}
"prefix" => {
match IpNet::from_str(filter_value) {
Ok(v) => {Ok(Filter::Prefix(v,PrefixMatchType::Exact))}
Err(_) => {
Err(FilterError(format!("cannot parse prefix from {}", filter_value)))
}
}
}
"prefix_super" => {
match IpNet::from_str(filter_value) {
Ok(v) => {Ok(Filter::Prefix(v,PrefixMatchType::IncludeSuper))}
Err(_) => {
Err(FilterError(format!("cannot parse prefix from {}", filter_value)))
}
}
}
"prefix_sub" => {
match IpNet::from_str(filter_value) {
Ok(v) => {Ok(Filter::Prefix(v,PrefixMatchType::IncludeSub))}
Err(_) => {
Err(FilterError(format!("cannot parse prefix from {}", filter_value)))
}
}
}
"prefix_super_sub" => {
match IpNet::from_str(filter_value) {
Ok(v) => {Ok(Filter::Prefix(v,PrefixMatchType::IncludeSuperSub))}
Err(_) => {
Err(FilterError(format!("cannot parse prefix from {}", filter_value)))
}
}
}
"peer_ip" => {
match IpAddr::from_str(filter_value) {
Ok(v) => {Ok(Filter::PeerIp(v))}
Err(_) => {
Err(FilterError(format!("cannot parse peer IP from {}", filter_value)))
}
}
}
"peer_ips" => {
let mut ips = vec![];
for ip_str in filter_value.replace(' ',"").split(',') {
match IpAddr::from_str(ip_str) {
Ok(v) => {ips.push(v)}
Err(_) => {
return Err(FilterError(format!("cannot parse peer IP from {}", ip_str)))
}
}
}
Ok(Filter::PeerIps(ips))
}
"peer_asn" => {
match u32::from_str(filter_value) {
Ok(v) => {Ok(Filter::PeerAsn(v))},
Err(_) => {
Err(FilterError(format!("cannot parse peer asn from {}", filter_value)))
}
}
}
"type" => {
match filter_value {
"w"|"withdraw"|"withdrawal" => {
Ok(Filter::Type(ElemType::WITHDRAW))
}
"a"|"announce"|"announcement" => {
Ok(Filter::Type(ElemType::ANNOUNCE))
}
_ => {
Err(FilterError(format!("cannot parse elem type from {}", filter_value)))
}
}
}
"start_ts" => {
match f64::from_str(filter_value) {
Ok(v) => {Ok(Filter::TsStart(v))},
Err(_) => {
Err(FilterError(format!("cannot parse f64 value from {}", filter_value)))
}
}
}
"end_ts" => {
match f64::from_str(filter_value) {
Ok(v) => {Ok(Filter::TsEnd(v))},
Err(_) => {
Err(FilterError(format!("cannot parse f64 value from {}", filter_value)))
}
}
}
"as_path" => {
match Regex::from_str(filter_value) {
Ok(v) => {
Ok(Filter::AsPath(v))
}
Err(_) => {
Err(FilterError(format!("cannot parse AS path regex from {}", filter_value)))
}
}
}
_ => {
Err(FilterError(format!("unknown filter type: {}", filter_type)))
}
}
}
}
pub trait Filterable {
fn match_filter(&self, filter: &Filter) -> bool;
fn match_filters(&self, filters: &[Filter]) -> bool;
}
const fn same_family(prefix_1: &IpNet, prefix_2: &IpNet) -> bool {
matches!((prefix_1, prefix_2), (IpNet::V4(_), IpNet::V4(_)) | (IpNet::V6(_), IpNet::V6(_)))
}
fn prefix_match(match_prefix: &IpNet, input_prefix: &IpNet, t: &PrefixMatchType) -> bool {
let exact = input_prefix.eq(match_prefix);
match t {
PrefixMatchType::Exact => {
exact
}
PrefixMatchType::IncludeSuper => {
if exact {
exact
} else if !same_family(match_prefix, input_prefix) {
false
} else {
match_prefix.addr() >= input_prefix.addr() && match_prefix.broadcast() <= input_prefix.broadcast()
}
}
PrefixMatchType::IncludeSub => {
if exact {
exact
} else if !same_family(match_prefix, input_prefix) {
false
} else {
match_prefix.addr() <= input_prefix.addr() && match_prefix.broadcast() >= input_prefix.broadcast()
}
}
PrefixMatchType::IncludeSuperSub => {
if exact {
exact
} else if !same_family(match_prefix, input_prefix) {
false
} else {
(match_prefix.addr() >= input_prefix.addr() && match_prefix.broadcast() <= input_prefix.broadcast()) ||
(match_prefix.addr() <= input_prefix.addr() && match_prefix.broadcast() >= input_prefix.broadcast())
}
}
}
}
impl Filterable for BgpElem {
fn match_filter(&self, filter: &Filter) -> bool {
match filter {
Filter::OriginAsn(v) => {
let asn: Asn = (*v).into();
if let Some(origins) = &self.origin_asns {
origins.contains(&asn)
} else {
false
}
}
Filter::Prefix(v, t) => {
prefix_match(v, &self.prefix.prefix, t)
}
Filter::PeerIp(v) => {
self.peer_ip == *v
}
Filter::PeerIps(v) => {
v.contains(&self.peer_ip)
}
Filter::PeerAsn(v) => {
self.peer_asn.eq(v)
}
Filter::Type(v) => {
self.elem_type.eq(v)
}
Filter::TsStart(v) => {
self.timestamp >= *v
}
Filter::TsEnd(v) => {
self.timestamp <= *v
}
Filter::AsPath(v) => {
if let Some(path) = &self.as_path {
v.is_match(path.to_string().as_str())
} else {
false
}
}
}
}
fn match_filters(&self, filters: &[Filter]) -> bool {
filters.iter().all(|f| {
self.match_filter(f)
})
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use crate::BgpkitParser;
use super::*;
#[test]
fn test_filter() {
let url = "https://spaces.bgpkit.org/parser/update-example.gz";
let parser = BgpkitParser::new(url).unwrap();
let elems = parser.into_elem_iter().collect::<Vec<BgpElem>>();
let mut filters = vec![];
filters.push(Filter::PeerIp(IpAddr::from_str("185.1.8.65").unwrap()));
let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
assert_eq!(count, 3393);
let mut filters = vec![];
filters.push(Filter::PeerIp(IpAddr::from_str("185.1.8.65").unwrap()));
filters.push(Filter::Prefix(IpNet::from_str("190.115.192.0/22").unwrap(), PrefixMatchType::Exact));
let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
assert_eq!(count, 5);
let mut filters = vec![];
filters.push(Filter::Prefix(IpNet::from_str("190.115.192.0/24").unwrap(), PrefixMatchType::IncludeSuper));
let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
assert_eq!(count, 18);
let mut filters = vec![];
filters.push(Filter::Prefix(IpNet::from_str("190.115.192.0/22").unwrap(), PrefixMatchType::IncludeSub));
let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
assert_eq!(count, 42);
let mut filters = vec![];
filters.push(Filter::Prefix(IpNet::from_str("190.115.192.0/23").unwrap(), PrefixMatchType::IncludeSuperSub));
let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
assert_eq!(count, 24);
let mut filters = vec![];
let regex = Regex::new(r" ?174 1916 52888$").unwrap();
filters.push(Filter::AsPath(regex));
let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
assert_eq!(count, 12);
let mut filters = vec![];
filters.push(Filter::TsStart(1637437798 as f64));
filters.push(Filter::TsEnd(1637437798 as f64));
let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
assert_eq!(count, 13);
let mut filters = vec![];
filters.push(Filter::Type(ElemType::WITHDRAW));
let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
assert_eq!(count, 379);
let mut filters = vec![];
filters.push(Filter::Type(ElemType::WITHDRAW));
filters.push(Filter::Prefix(IpNet::from_str("2804:100::/32").unwrap(), PrefixMatchType::Exact));
let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
assert_eq!(count, 1);
let mut filters = vec![];
let peers = vec![
IpAddr::from_str("185.1.8.65").unwrap(),
IpAddr::from_str("2001:7f8:73:0:3:fa4:0:1").unwrap()
];
filters.push(Filter::PeerIps(peers));
let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
assert_eq!(count, 3393+834);
}
#[test]
fn test_filter_iter() {
let url = "https://spaces.bgpkit.org/parser/update-example.gz";
let parser = BgpkitParser::new(url).unwrap()
.add_filter("peer_ip", "185.1.8.50").unwrap()
.add_filter("type", "w").unwrap();
let count = parser.into_elem_iter().count();
assert_eq!(count, 39);
}
#[test]
fn test_filter_iter_multi_peers() {
let url = "https://spaces.bgpkit.org/parser/update-example.gz";
let parser = BgpkitParser::new(url).unwrap()
.add_filter("peer_ips", "185.1.8.65, 2001:7f8:73:0:3:fa4:0:1").unwrap();
let count = parser.into_elem_iter().count();
assert_eq!(count, 3393+834);
}
#[test]
fn test_prefix_match() {
let p1 = IpNet::from_str("10.1.1.0/24").unwrap();
let p1_exact = IpNet::from_str("10.1.1.0/24").unwrap();
let p1_super = IpNet::from_str("10.1.0.0/16").unwrap();
let p1_sub = IpNet::from_str("10.1.1.0/25").unwrap();
let p2 = IpNet::from_str("2001:0DB8:0000:000b::/64").unwrap();
assert_eq!(prefix_match(&p1, &p1_exact, &PrefixMatchType::Exact), true);
assert_eq!(prefix_match(&p1, &p1_sub, &PrefixMatchType::Exact), false);
assert_eq!(prefix_match(&p1, &p1_super, &PrefixMatchType::Exact), false);
assert_eq!(prefix_match(&p1, &p2, &PrefixMatchType::Exact), false);
assert_eq!(prefix_match(&p1, &p1_exact, &PrefixMatchType::IncludeSuper), true);
assert_eq!(prefix_match(&p1, &p1_sub, &PrefixMatchType::IncludeSuper), false);
assert_eq!(prefix_match(&p1, &p1_super, &PrefixMatchType::IncludeSuper), true);
assert_eq!(prefix_match(&p1, &p2, &PrefixMatchType::IncludeSuper), false);
assert_eq!(prefix_match(&p1, &p1_exact, &PrefixMatchType::IncludeSub), true);
assert_eq!(prefix_match(&p1, &p1_sub, &PrefixMatchType::IncludeSub), true);
assert_eq!(prefix_match(&p1, &p1_super, &PrefixMatchType::IncludeSub), false);
assert_eq!(prefix_match(&p1, &p2, &PrefixMatchType::IncludeSub), false);
assert_eq!(prefix_match(&p1, &p1_exact, &PrefixMatchType::IncludeSuperSub), true);
assert_eq!(prefix_match(&p1, &p1_sub, &PrefixMatchType::IncludeSuperSub), true);
assert_eq!(prefix_match(&p1, &p1_super, &PrefixMatchType::IncludeSuperSub), true);
assert_eq!(prefix_match(&p1, &p2, &PrefixMatchType::IncludeSuperSub), false);
}
}