bgpkit_parser/parser/
filter.rs

1/*!
2## Message Filters
3
4The filter module defines a number of available filters that users can use, and implements
5the filtering mechanism for [BgpElem].
6
7The available filters are:
8- `origin_asn` -- origin AS number
9- `prefix` -- network prefix and match type
10- `peer_ip` -- peer's IP address
11- `peer_ips` -- peers' IP addresses
12- `peer_asn` -- peer's IP address
13- `type` -- message type (`withdraw` or `announce`)
14- `ts_start` -- start and end unix timestamp
15- `as_path` -- regular expression for AS path string
16- `ip_version` -- IP version (`ipv4` or `ipv6`)
17
18[Filter::new] function takes a `str` as the filter type and `str` as the filter value and returns a
19Result of a [Filter] or a parsing error.
20
21[BgpkitParser](crate::BgpkitParser) implements the function `add_filter("filter_type", "filter_value")` that takes the parser's ownership itself
22and returns a new parser with specified filter added. See the example below.
23
24### Example
25
26```no_run
27use bgpkit_parser::BgpkitParser;
28
29/// This example shows how to parse a MRT file and filter by prefix.
30env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
31
32log::info!("downloading updates file");
33let parser = BgpkitParser::new("http://archive.routeviews.org/bgpdata/2021.10/UPDATES/updates.20211001.0000.bz2").unwrap()
34    .add_filter("prefix", "211.98.251.0/24").unwrap()
35    .add_filter("type", "a").unwrap();
36
37// iterating through the parser. the iterator returns `BgpElem` one at a time.
38log::info!("parsing updates file");
39for elem in parser {
40    log::info!("{}", &elem);
41}
42log::info!("done");
43```
44
45Note, by default, the prefix filtering is for the exact prefix. You can include super-prefixes or
46sub-prefixes when fitlering by using `"prefix_super"`, `"prefix_sub"`, or  `"prefix_super_sub"` as
47the filter type string.
48
49### Note
50
51Currently, only [BgpElem] implements the filtering capability. Support for [MrtRecord] will come in
52later releases.
53
54*/
55use 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/// Filter enum: definition o types of filters
64///
65/// The available filters are (`filter_type` (`FilterType`) -- definition):
66/// - `origin_asn` (`OriginAsn(u32)`) -- origin AS number
67/// - `prefix(_super, _sub, _super_sub)` (`Prefix(IpNet, PrefixMatchType)`) -- network prefix and match type
68/// - `peer_ip` (`PeerIp(IpAddr)`) -- peer's IP address
69/// - `peer_ips` (`Vec<PeerIp(IpAddr)>`) -- peers' IP addresses
70/// - `peer_asn` (`PeerAsn(u32)`) -- peer's IP address
71/// - `type` (`Type(ElemType)`) -- message type (`withdraw` or `announce`)
72/// - `ts_start` (`TsStart(f64)`) and `ts_end` (`TsEnd(f64)`) -- start and end unix timestamp
73/// - `as_path` (`ComparableRegex`) -- regular expression for AS path string
74/// - `community` (`ComparableRegex`) -- regular expression for community string
75/// - `ip_version` (`IpVersion`) -- IP version (`ipv4` or `ipv6`)
76#[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                // version not match
236                false
237            } else {
238                // input_prefix is super prefix of match_prefix
239                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                // version not match
248                false
249            } else {
250                // input_prefix is sub prefix of match_prefix
251                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                // version not match
260                false
261            } else {
262                // input_prefix is super prefix of match_prefix
263                (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        // filter by community starting with some value
369        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        // filter by community ending with some value
374        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        // filter by community with large community (i.e. with 3 values, separated by ':')
379        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        // test filtering by multiple peers
405        /*
406        1167 185.1.8.3
407        1563 185.1.8.50
408        3393 185.1.8.65
409          51 185.1.8.89
410         834 2001:7f8:73:0:3:fa4:0:1
411          94 2001:7f8:73::c2a8:0:1
412        1058 2001:7f8:73::edfc:0:2
413         */
414        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        // filter by community with large community (i.e. with 3 values, separated by ':')
425        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        // network
479        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        // exact
487        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        // include super
493        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        // include sub
499        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        // include both
505        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}