1use crate::models::*;
56use crate::parser::ComparableRegex;
57use crate::ParserError;
58use crate::ParserError::FilterError;
59use ipnet::IpNet;
60use std::net::IpAddr;
61use std::str::FromStr;
62
63#[derive(Debug, Clone, PartialEq)]
77pub enum Filter {
78 OriginAsn(u32),
79 Prefix(IpNet, PrefixMatchType),
80 PeerIp(IpAddr),
81 PeerIps(Vec<IpAddr>),
82 PeerAsn(u32),
83 Type(ElemType),
84 IpVersion(IpVersion),
85 TsStart(f64),
86 TsEnd(f64),
87 AsPath(ComparableRegex),
88 Community(ComparableRegex),
89}
90
91#[derive(Debug, Clone, PartialEq, Eq)]
92pub enum IpVersion {
93 Ipv4,
94 Ipv6,
95}
96
97#[derive(Debug, Clone, PartialEq, Eq)]
98pub enum PrefixMatchType {
99 Exact,
100 IncludeSuper,
101 IncludeSub,
102 IncludeSuperSub,
103}
104
105fn parse_time_str(time_str: &str) -> Option<chrono::NaiveDateTime> {
106 if let Ok(t) = time_str.parse::<f64>() {
107 return chrono::DateTime::from_timestamp(t as i64, 0).map(|t| t.naive_utc());
108 }
109 if let Ok(t) = chrono::DateTime::parse_from_rfc3339(time_str) {
110 return Some(t.naive_utc());
111 }
112 None
113}
114
115impl Filter {
116 pub fn new(filter_type: &str, filter_value: &str) -> Result<Filter, ParserError> {
117 match filter_type {
118 "origin_asn" => match u32::from_str(filter_value) {
119 Ok(v) => Ok(Filter::OriginAsn(v)),
120 Err(_) => Err(FilterError(format!(
121 "cannot parse origin asn from {filter_value}"
122 ))),
123 },
124 "prefix" => match IpNet::from_str(filter_value) {
125 Ok(v) => Ok(Filter::Prefix(v, PrefixMatchType::Exact)),
126 Err(_) => Err(FilterError(format!(
127 "cannot parse prefix from {filter_value}"
128 ))),
129 },
130 "prefix_super" => match IpNet::from_str(filter_value) {
131 Ok(v) => Ok(Filter::Prefix(v, PrefixMatchType::IncludeSuper)),
132 Err(_) => Err(FilterError(format!(
133 "cannot parse prefix from {filter_value}"
134 ))),
135 },
136 "prefix_sub" => match IpNet::from_str(filter_value) {
137 Ok(v) => Ok(Filter::Prefix(v, PrefixMatchType::IncludeSub)),
138 Err(_) => Err(FilterError(format!(
139 "cannot parse prefix from {filter_value}"
140 ))),
141 },
142 "prefix_super_sub" => match IpNet::from_str(filter_value) {
143 Ok(v) => Ok(Filter::Prefix(v, PrefixMatchType::IncludeSuperSub)),
144 Err(_) => Err(FilterError(format!(
145 "cannot parse prefix from {filter_value}"
146 ))),
147 },
148 "peer_ip" => match IpAddr::from_str(filter_value) {
149 Ok(v) => Ok(Filter::PeerIp(v)),
150 Err(_) => Err(FilterError(format!(
151 "cannot parse peer IP from {filter_value}"
152 ))),
153 },
154 "peer_ips" => {
155 let mut ips = vec![];
156 for ip_str in filter_value.replace(' ', "").split(',') {
157 match IpAddr::from_str(ip_str) {
158 Ok(v) => ips.push(v),
159 Err(_) => {
160 return Err(FilterError(format!("cannot parse peer IP from {ip_str}")))
161 }
162 }
163 }
164 Ok(Filter::PeerIps(ips))
165 }
166 "peer_asn" => match u32::from_str(filter_value) {
167 Ok(v) => Ok(Filter::PeerAsn(v)),
168 Err(_) => Err(FilterError(format!(
169 "cannot parse peer asn from {filter_value}"
170 ))),
171 },
172 "type" => match filter_value {
173 "w" | "withdraw" | "withdrawal" => Ok(Filter::Type(ElemType::WITHDRAW)),
174 "a" | "announce" | "announcement" => Ok(Filter::Type(ElemType::ANNOUNCE)),
175 _ => Err(FilterError(format!(
176 "cannot parse elem type from {filter_value}"
177 ))),
178 },
179 "ts_start" | "start_ts" => match parse_time_str(filter_value) {
180 Some(t) => Ok(Filter::TsStart(t.and_utc().timestamp() as f64)),
181 None => Err(FilterError(format!(
182 "cannot parse TsStart filter from {filter_value}"
183 ))),
184 },
185 "ts_end" | "end_ts" => match parse_time_str(filter_value) {
186 Some(t) => Ok(Filter::TsEnd(t.and_utc().timestamp() as f64)),
187 None => Err(FilterError(format!(
188 "cannot parse TsEnd filter from {filter_value}"
189 ))),
190 },
191 "as_path" => match ComparableRegex::new(filter_value) {
192 Ok(v) => Ok(Filter::AsPath(v)),
193 Err(_) => Err(FilterError(format!(
194 "cannot parse AS path regex from {filter_value}"
195 ))),
196 },
197 "community" => match ComparableRegex::new(filter_value) {
198 Ok(v) => Ok(Filter::Community(v)),
199 Err(_) => Err(FilterError(format!(
200 "cannot parse Community regex from {filter_value}"
201 ))),
202 },
203 "ip_version" | "ip" => match filter_value {
204 "4" | "v4" | "ipv4" => Ok(Filter::IpVersion(IpVersion::Ipv4)),
205 "6" | "v6" | "ipv6" => Ok(Filter::IpVersion(IpVersion::Ipv6)),
206 _ => Err(FilterError(format!(
207 "cannot parse IP version from {filter_value}"
208 ))),
209 },
210 _ => Err(FilterError(format!("unknown filter type: {filter_type}"))),
211 }
212 }
213}
214
215pub trait Filterable {
216 fn match_filter(&self, filter: &Filter) -> bool;
217 fn match_filters(&self, filters: &[Filter]) -> bool;
218}
219
220const fn same_family(prefix_1: &IpNet, prefix_2: &IpNet) -> bool {
221 matches!(
222 (prefix_1, prefix_2),
223 (IpNet::V4(_), IpNet::V4(_)) | (IpNet::V6(_), IpNet::V6(_))
224 )
225}
226
227fn prefix_match(match_prefix: &IpNet, input_prefix: &IpNet, t: &PrefixMatchType) -> bool {
228 let exact = input_prefix.eq(match_prefix);
229 match t {
230 PrefixMatchType::Exact => exact,
231 PrefixMatchType::IncludeSuper => {
232 if exact {
233 exact
234 } else if !same_family(match_prefix, input_prefix) {
235 false
237 } else {
238 match_prefix.addr() >= input_prefix.addr()
240 && match_prefix.broadcast() <= input_prefix.broadcast()
241 }
242 }
243 PrefixMatchType::IncludeSub => {
244 if exact {
245 exact
246 } else if !same_family(match_prefix, input_prefix) {
247 false
249 } else {
250 match_prefix.addr() <= input_prefix.addr()
252 && match_prefix.broadcast() >= input_prefix.broadcast()
253 }
254 }
255 PrefixMatchType::IncludeSuperSub => {
256 if exact {
257 exact
258 } else if !same_family(match_prefix, input_prefix) {
259 false
261 } else {
262 (match_prefix.addr() >= input_prefix.addr()
264 && match_prefix.broadcast() <= input_prefix.broadcast())
265 || (match_prefix.addr() <= input_prefix.addr()
266 && match_prefix.broadcast() >= input_prefix.broadcast())
267 }
268 }
269 }
270}
271
272impl Filterable for BgpElem {
273 fn match_filter(&self, filter: &Filter) -> bool {
274 match filter {
275 Filter::OriginAsn(v) => {
276 let asn: Asn = (*v).into();
277 if let Some(origins) = &self.origin_asns {
278 origins.contains(&asn)
279 } else {
280 false
281 }
282 }
283 Filter::Prefix(v, t) => prefix_match(v, &self.prefix.prefix, t),
284 Filter::PeerIp(v) => self.peer_ip == *v,
285 Filter::PeerIps(v) => v.contains(&self.peer_ip),
286 Filter::PeerAsn(v) => self.peer_asn.eq(v),
287 Filter::Type(v) => self.elem_type.eq(v),
288 Filter::TsStart(v) => self.timestamp >= *v,
289 Filter::TsEnd(v) => self.timestamp <= *v,
290 Filter::AsPath(v) => {
291 if let Some(path) = &self.as_path {
292 v.is_match(path.to_string().as_str())
293 } else {
294 false
295 }
296 }
297 Filter::Community(r) => {
298 if let Some(communities) = &self.communities {
299 communities.iter().any(|c| r.is_match(c.to_string()))
300 } else {
301 false
302 }
303 }
304 Filter::IpVersion(version) => match version {
305 IpVersion::Ipv4 => self.prefix.prefix.addr().is_ipv4(),
306 IpVersion::Ipv6 => self.prefix.prefix.addr().is_ipv6(),
307 },
308 }
309 }
310
311 fn match_filters(&self, filters: &[Filter]) -> bool {
312 filters.iter().all(|f| self.match_filter(f))
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319 use crate::BgpkitParser;
320 use anyhow::Result;
321 use std::str::FromStr;
322
323 #[test]
324 fn test_filters_on_mrt_file() {
325 let url = "https://spaces.bgpkit.org/parser/update-example.gz";
326 let parser = BgpkitParser::new(url).unwrap();
327 let elems = parser.into_elem_iter().collect::<Vec<BgpElem>>();
328
329 let filters = vec![Filter::PeerIp(IpAddr::from_str("185.1.8.65").unwrap())];
330 let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
331 assert_eq!(count, 3393);
332
333 let filters = vec![
334 Filter::PeerIp(IpAddr::from_str("185.1.8.65").unwrap()),
335 Filter::Prefix(
336 IpNet::from_str("190.115.192.0/22").unwrap(),
337 PrefixMatchType::Exact,
338 ),
339 ];
340 let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
341 assert_eq!(count, 5);
342
343 let filters = vec![Filter::Prefix(
344 IpNet::from_str("190.115.192.0/24").unwrap(),
345 PrefixMatchType::IncludeSuper,
346 )];
347 let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
348 assert_eq!(count, 18);
349
350 let filters = vec![Filter::Prefix(
351 IpNet::from_str("190.115.192.0/22").unwrap(),
352 PrefixMatchType::IncludeSub,
353 )];
354 let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
355 assert_eq!(count, 42);
356
357 let filters = vec![Filter::Prefix(
358 IpNet::from_str("190.115.192.0/23").unwrap(),
359 PrefixMatchType::IncludeSuperSub,
360 )];
361 let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
362 assert_eq!(count, 24);
363
364 let filters = vec![Filter::new("as_path", r" ?174 1916 52888$").unwrap()];
365 let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
366 assert_eq!(count, 12);
367
368 let filters = vec![Filter::new("community", r"60924:.*").unwrap()];
370 let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
371 assert_eq!(count, 4243);
372
373 let filters = vec![Filter::new("community", r".+:784$").unwrap()];
375 let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
376 assert_eq!(count, 107);
377
378 let filters = vec![Filter::new("community", r"\d+:\d+:\d+$").unwrap()];
380 let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
381 assert_eq!(count, 4397);
382
383 let filters = vec![
384 Filter::TsStart(1637437798_f64),
385 Filter::TsEnd(1637437798_f64),
386 ];
387 let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
388 assert_eq!(count, 13);
389
390 let filters = vec![Filter::Type(ElemType::WITHDRAW)];
391 let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
392 assert_eq!(count, 379);
393
394 let filters = vec![
395 Filter::Type(ElemType::WITHDRAW),
396 Filter::Prefix(
397 IpNet::from_str("2804:100::/32").unwrap(),
398 PrefixMatchType::Exact,
399 ),
400 ];
401 let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
402 assert_eq!(count, 1);
403
404 let filters = vec![Filter::PeerIps(vec![
415 IpAddr::from_str("185.1.8.65").unwrap(),
416 IpAddr::from_str("2001:7f8:73:0:3:fa4:0:1").unwrap(),
417 ])];
418 let count = elems.iter().filter(|e| e.match_filters(&filters)).count();
419 assert_eq!(count, 3393 + 834);
420 }
421
422 #[test]
423 fn test_filter_incorrect_filters() {
424 let incorrect_filters = [
426 Filter::new("community", r"[abc"),
427 Filter::new("as_path", r"[0-9"),
428 Filter::new("prefix_super_sub", "-192.-168.-1.1/24"),
429 ];
430 assert!(incorrect_filters
431 .iter()
432 .all(|f| matches!(f, Err(FilterError(_)))));
433 }
434
435 #[test]
436 fn test_parsing_time_str() {
437 let ts = chrono::NaiveDateTime::from_str("2021-11-20T19:49:58").unwrap();
438 assert_eq!(parse_time_str("1637437798"), Some(ts));
439 assert_eq!(parse_time_str("2021-11-20T19:49:58Z"), Some(ts));
440 assert_eq!(parse_time_str("2021-11-20T19:49:58+00:00"), Some(ts));
441
442 assert_eq!(parse_time_str("2021-11-20T19:49:58"), None);
443 assert_eq!(parse_time_str("2021-11-20T19:49:58ZDXV"), None);
444 assert_eq!(parse_time_str("2021-11-20 19:49:58"), None);
445 assert_eq!(parse_time_str("2021-11-20"), None);
446 }
447
448 #[test]
449 fn test_filter_iter() -> Result<()> {
450 let url = "https://spaces.bgpkit.org/parser/update-example.gz";
451 let parser = BgpkitParser::new(url)?
452 .add_filter("peer_ip", "185.1.8.50")?
453 .add_filter("type", "w")?;
454 let count = parser.into_elem_iter().count();
455 assert_eq!(count, 39);
456
457 let parser = BgpkitParser::new(url)?
458 .add_filter("ts_start", "1637437798")?
459 .add_filter("ts_end", "2021-11-20T19:49:58Z")?;
460 let count = parser.into_elem_iter().count();
461 assert_eq!(count, 13);
462 Ok(())
463 }
464
465 #[test]
466 fn test_filter_iter_multi_peers() {
467 let url = "https://spaces.bgpkit.org/parser/update-example.gz";
468 let parser = BgpkitParser::new(url)
469 .unwrap()
470 .add_filter("peer_ips", "185.1.8.65, 2001:7f8:73:0:3:fa4:0:1")
471 .unwrap();
472 let count = parser.into_elem_iter().count();
473 assert_eq!(count, 3393 + 834);
474 }
475
476 #[test]
477 fn test_prefix_match() {
478 let p1 = IpNet::from_str("10.1.1.0/24").unwrap();
480 let p1_exact = IpNet::from_str("10.1.1.0/24").unwrap();
481 let p1_super = IpNet::from_str("10.1.0.0/16").unwrap();
482 let p1_sub = IpNet::from_str("10.1.1.0/25").unwrap();
483
484 let p2 = IpNet::from_str("2001:0DB8:0000:000b::/64").unwrap();
485
486 assert!(prefix_match(&p1, &p1_exact, &PrefixMatchType::Exact));
488 assert!(!prefix_match(&p1, &p1_sub, &PrefixMatchType::Exact));
489 assert!(!prefix_match(&p1, &p1_super, &PrefixMatchType::Exact));
490 assert!(!prefix_match(&p1, &p2, &PrefixMatchType::Exact));
491
492 assert!(prefix_match(&p1, &p1_exact, &PrefixMatchType::IncludeSuper));
494 assert!(!prefix_match(&p1, &p1_sub, &PrefixMatchType::IncludeSuper));
495 assert!(prefix_match(&p1, &p1_super, &PrefixMatchType::IncludeSuper));
496 assert!(!prefix_match(&p1, &p2, &PrefixMatchType::IncludeSuper));
497
498 assert!(prefix_match(&p1, &p1_exact, &PrefixMatchType::IncludeSub));
500 assert!(prefix_match(&p1, &p1_sub, &PrefixMatchType::IncludeSub));
501 assert!(!prefix_match(&p1, &p1_super, &PrefixMatchType::IncludeSub));
502 assert!(!prefix_match(&p1, &p2, &PrefixMatchType::IncludeSub));
503
504 assert!(prefix_match(
506 &p1,
507 &p1_exact,
508 &PrefixMatchType::IncludeSuperSub
509 ));
510 assert!(prefix_match(
511 &p1,
512 &p1_sub,
513 &PrefixMatchType::IncludeSuperSub
514 ));
515 assert!(prefix_match(
516 &p1,
517 &p1_super,
518 &PrefixMatchType::IncludeSuperSub
519 ));
520 assert!(!prefix_match(&p1, &p2, &PrefixMatchType::IncludeSuperSub));
521 }
522
523 #[test]
524 fn test_filter_new() {
525 let filter = Filter::new("origin_asn", "12345").unwrap();
526 assert_eq!(filter, Filter::OriginAsn(12345));
527
528 let filter = Filter::new("prefix", "192.168.1.0/24").unwrap();
529 assert_eq!(
530 filter,
531 Filter::Prefix(
532 IpNet::from_str("192.168.1.0/24").unwrap(),
533 PrefixMatchType::Exact
534 )
535 );
536 let filter = Filter::new("prefix_super", "192.168.1.0/24").unwrap();
537 assert_eq!(
538 filter,
539 Filter::Prefix(
540 IpNet::from_str("192.168.1.0/24").unwrap(),
541 PrefixMatchType::IncludeSuper
542 )
543 );
544 let filter = Filter::new("prefix_sub", "192.168.1.0/24").unwrap();
545 assert_eq!(
546 filter,
547 Filter::Prefix(
548 IpNet::from_str("192.168.1.0/24").unwrap(),
549 PrefixMatchType::IncludeSub
550 )
551 );
552 let filter = Filter::new("prefix_super_sub", "192.168.1.0/24").unwrap();
553 assert_eq!(
554 filter,
555 Filter::Prefix(
556 IpNet::from_str("192.168.1.0/24").unwrap(),
557 PrefixMatchType::IncludeSuperSub
558 )
559 );
560
561 let filter = Filter::new("peer_ip", "192.168.1.1").unwrap();
562 assert_eq!(
563 filter,
564 Filter::PeerIp(IpAddr::from_str("192.168.1.1").unwrap())
565 );
566
567 let filter = Filter::new("peer_asn", "12345").unwrap();
568 assert_eq!(filter, Filter::PeerAsn(12345));
569
570 let filter = Filter::new("type", "w").unwrap();
571 assert_eq!(filter, Filter::Type(ElemType::WITHDRAW));
572
573 let filter = Filter::new("ts_start", "1637437798").unwrap();
574 assert_eq!(filter, Filter::TsStart(1637437798_f64));
575
576 let filter = Filter::new("ts_end", "1637437798").unwrap();
577 assert_eq!(filter, Filter::TsEnd(1637437798_f64));
578
579 let filter = Filter::new("as_path", r" ?174 1916 52888$").unwrap();
580 assert_eq!(
581 filter,
582 Filter::AsPath(ComparableRegex::new(r" ?174 1916 52888$").unwrap())
583 );
584
585 assert!(Filter::new("origin_asn", "not a number").is_err());
586 assert!(Filter::new("peer_asn", "not a number").is_err());
587 assert!(Filter::new("ts_start", "not a number").is_err());
588 assert!(Filter::new("ts_end", "not a number").is_err());
589 assert!(Filter::new("prefix", "not a prefix").is_err());
590 assert!(Filter::new("prefix_super", "not a prefix").is_err());
591 assert!(Filter::new("prefix_sub", "not a prefix").is_err());
592 assert!(Filter::new("peer_ip", "not a IP").is_err());
593 assert!(Filter::new("peer_ips", "not,a,IP").is_err());
594 assert!(Filter::new("type", "not a type").is_err());
595 assert!(Filter::new("as_path", "[abc").is_err());
596 assert!(Filter::new("ip_version", "5").is_err());
597 assert!(Filter::new("unknown_filter", "some_value").is_err());
598 }
599
600 #[test]
601 fn test_filterable_match_filter() {
602 let elem = BgpElem {
603 timestamp: 1637437798_f64,
604 peer_ip: IpAddr::from_str("192.168.1.1").unwrap(),
605 peer_asn: Asn::new_32bit(12345),
606 prefix: NetworkPrefix::new(IpNet::from_str("192.168.1.0/24").unwrap(), None),
607 next_hop: None,
608 as_path: Some(AsPath::from_sequence(vec![174, 1916, 52888])),
609 origin_asns: Some(vec![Asn::new_16bit(12345)]),
610 origin: None,
611 local_pref: None,
612 med: None,
613 communities: Some(vec![MetaCommunity::Large(LargeCommunity::new(
614 12345,
615 [678910, 111213],
616 ))]),
617 atomic: false,
618 aggr_asn: None,
619 aggr_ip: None,
620 only_to_customer: None,
621 unknown: None,
622 elem_type: ElemType::ANNOUNCE,
623 deprecated: None,
624 };
625
626 let mut filters = vec![];
627
628 let filter = Filter::new("origin_asn", "12345").unwrap();
629 filters.push(filter.clone());
630 assert!(elem.match_filter(&filter));
631
632 let filter = Filter::new("origin_asn", "678910").unwrap();
633 assert!(!elem.match_filter(&filter));
634
635 let filter = Filter::new("prefix", "192.168.1.0/24").unwrap();
636 filters.push(filter.clone());
637 assert!(elem.match_filter(&filter));
638
639 let filter = Filter::new("peer_ip", "192.168.1.1").unwrap();
640 filters.push(filter.clone());
641 assert!(elem.match_filter(&filter));
642
643 let filter = Filter::new("peer_asn", "12345").unwrap();
644 filters.push(filter.clone());
645 assert!(elem.match_filter(&filter));
646
647 let filter = Filter::new("type", "a").unwrap();
648 filters.push(filter.clone());
649 assert!(elem.match_filter(&filter));
650
651 let filter = Filter::new("ts_start", "1637437798").unwrap();
652 filters.push(filter.clone());
653 assert!(elem.match_filter(&filter));
654
655 let filter = Filter::new("ts_end", "1637437798").unwrap();
656 filters.push(filter.clone());
657 assert!(elem.match_filter(&filter));
658
659 let filter = Filter::new("as_path", r" ?174 1916 52888$").unwrap();
660 filters.push(filter.clone());
661 assert!(elem.match_filter(&filter));
662
663 let filter = Filter::new("ip_version", "4").unwrap();
664 filters.push(filter.clone());
665 assert!(elem.match_filter(&filter));
666
667 let filter = Filter::new("ip", "ipv6").unwrap();
668 assert!(!elem.match_filter(&filter));
669
670 let filter = Filter::new("community", r"12345:678910:111213$").unwrap();
671 filters.push(filter.clone());
672 assert!(elem.match_filter(&filter));
673
674 assert!(elem.match_filters(&filters));
675 }
676}