bgpfu/
query.rs

1use std::fmt::Display;
2
3use ip::{Any, Prefix, PrefixSet};
4
5use irrc::{Connection, IrrClient, Query, ResponseItem};
6
7use rpsl::{
8    attr::{AttributeType, RpslAttribute},
9    expr::{
10        eval::{Evaluate, Evaluator, Resolver},
11        MpFilterExpr,
12    },
13    names::{AsSet, AutNum, FilterSet, RouteSet},
14    obj::{RpslObject, RpslObjectClass},
15    primitive::PeerAs,
16};
17
18use crate::error::Error;
19
20/// An implementation of [`rpsl::expr::eval::Evaluator`] that resolves RPSL names using the IRRd
21/// query protocol.
22///
23/// # Examples
24///
25/// ``` no_run
26/// use bgpfu::RpslEvaluator;
27/// use ip::traits::PrefixSet;
28/// use rpsl::expr::MpFilterExpr;
29///
30/// let filter: MpFilterExpr = "AS-FOO AND { 0.0.0.0/0^8-24, ::/0^16-48 }".parse()?;
31/// RpslEvaluator::new("whois.radb.net", 43)?
32///     .evaluate(filter)?
33///     .ranges()
34///     .for_each(|range| println!("{range}"));
35/// # Ok::<_, Box<dyn std::error::Error>>(())
36/// ```
37#[derive(Debug)]
38pub struct RpslEvaluator {
39    conn: Option<Connection>,
40}
41
42impl RpslEvaluator {
43    /// Construct a new [`Evaluator`].
44    ///
45    /// # Errors
46    ///
47    /// An [`Error::Irr`] is returned if the connection to the IRRd server cannot be established.
48    #[tracing::instrument(level = "debug")]
49    pub fn new(host: &str, port: u16) -> Result<Self, Error> {
50        let addr = format!("{host}:{port}");
51        let conn = IrrClient::new(addr).connect()?;
52        Ok(Self { conn: Some(conn) })
53    }
54
55    fn with_connection<F, T, E>(&mut self, f: F) -> Result<T, Error>
56    where
57        F: Fn(&mut Self, &mut Connection) -> Result<T, E>,
58        E: Into<Error>,
59    {
60        let mut conn = self.conn.take().ok_or(Error::AcquireConnection)?;
61        let result = f(self, &mut conn).map_err(Into::into);
62        self.conn = Some(conn);
63        result
64    }
65
66    /// Evaluate an RPSL expression.
67    ///
68    /// This method wraps [`Evaluator::evaluate`], and is provided as a convenience so that the
69    /// underlying trait does not have to be brought into scope explicitly.
70    ///
71    /// # Errors
72    ///
73    /// See [`Evaluator`] for error handling details.
74    #[tracing::instrument(skip(self, expr), fields(%expr), level = "debug")]
75    pub fn evaluate<'a, T>(
76        &mut self,
77        expr: T,
78    ) -> Result<<Self as Evaluator<'a>>::Output<T>, <Self as Evaluator<'a>>::Error>
79    where
80        T: Evaluate<'a, Self> + Display,
81    {
82        tracing::info!("evaluating RPSL mp-filter expression '{expr}'");
83        <Self as Evaluator>::evaluate(self, expr)
84    }
85}
86
87impl<'a> Evaluator<'a> for RpslEvaluator {
88    type Output<T> = <T as Evaluate<'a, Self>>::Output
89    where
90        T: Evaluate<'a, Self>;
91
92    type Error = Error;
93
94    fn finalise<T>(&mut self, output: T::Output) -> Result<Self::Output<T>, Self::Error>
95    where
96        T: Evaluate<'a, Self>,
97    {
98        Ok(output)
99    }
100
101    fn sink_error(&mut self, err: &(dyn std::error::Error + Send + Sync + 'static)) -> bool {
102        if let Some(irrc::Error::ResponseErr(
103            Query::Ipv4Routes(_) | Query::Ipv6Routes(_),
104            irrc::error::Response::KeyNotFound,
105        )) = err.downcast_ref()
106        {
107            tracing::debug!("{err:#}");
108        } else {
109            tracing::warn!("{err:#}");
110        }
111        true
112    }
113}
114
115impl Resolver<'_, FilterSet, MpFilterExpr> for RpslEvaluator {
116    type IError = Error;
117
118    #[tracing::instrument(skip(self), level = "debug")]
119    fn resolve(&mut self, filter_set: &FilterSet) -> Result<MpFilterExpr, Self::IError> {
120        self.with_connection(|this, conn| {
121            conn.pipeline()
122                // TODO: this is a bad API - we should be able to determine the required object
123                // class from the type of `filter_set`.
124                .push(Query::RpslObject(
125                    irrc::RpslObjectClass::FilterSet,
126                    filter_set.to_string(),
127                ))
128                .map_err(Error::from)
129                .and_then(|pipeline| {
130                    pipeline
131                        .responses()
132                        .find_map(|resp| {
133                            this.collect_result(resp.map_err(Error::from).and_then(|item| {
134                                let obj = item.into_content();
135                                if let RpslObject::FilterSet(ref filter_set_obj) = obj {
136                                    filter_set_obj
137                                        .attrs()
138                                        .into_iter()
139                                        .find_map(|attr| {
140                                            if let RpslAttribute::MpFilter(expr) = attr {
141                                                // TODO: shouldn't need to clone here either!
142                                                Some(expr.clone())
143                                            } else {
144                                                None
145                                            }
146                                        })
147                                        .ok_or_else(|| {
148                                            Error::FindAttribute(AttributeType::MpFilter, obj)
149                                        })
150                                } else {
151                                    Err(Error::RpslObjectClass(obj))
152                                }
153                            }))
154                            .transpose()
155                        })
156                        .unwrap_or_else(|| Ok("NOT ANY".parse()?))
157                })
158        })
159    }
160}
161
162impl Resolver<'_, AsSet, PrefixSet<Any>> for RpslEvaluator {
163    type IError = Error;
164
165    #[tracing::instrument(skip(self), fields(%as_set), level = "debug")]
166    fn resolve(&mut self, as_set: &AsSet) -> Result<PrefixSet<Any>, Self::IError> {
167        self.with_connection(|this, conn| {
168            // TODO: shouldn't need to clone here
169            conn.pipeline_from_initial(Query::AsSetMembersRecursive(as_set.clone()), |resp| {
170                this.collect_result::<_, _, Error>(resp.map(|item| {
171                    let autnum = item.into_content();
172                    [Query::Ipv4Routes(autnum), Query::Ipv6Routes(autnum)]
173                }))
174                // TODO: we want a way of providing our own error handling closure
175                .unwrap_or_else(|err| {
176                    _ = this.sink_error(&err);
177                    None
178                })
179            })
180            .and_then(|mut pipeline| {
181                this.collect_results(
182                    pipeline
183                        .responses::<'_, Prefix<Any>>()
184                        .map(|resp| resp.map(ResponseItem::into_content)),
185                )
186            })
187        })
188    }
189}
190
191impl Resolver<'_, RouteSet, PrefixSet<Any>> for RpslEvaluator {
192    type IError = Error;
193
194    #[tracing::instrument(skip(self), level = "debug")]
195    fn resolve(&mut self, route_set: &RouteSet) -> Result<PrefixSet<Any>, Self::IError> {
196        self.with_connection(|this, conn| {
197            conn.pipeline()
198                // TODO: shouldn't need to clone here
199                .push(Query::RouteSetMembersRecursive(route_set.clone()))
200                .map_err(Error::from)
201                .and_then(|pipeline| {
202                    this.collect_results(
203                        pipeline
204                            .responses::<'_, Prefix<Any>>()
205                            .map(|response| response.map(ResponseItem::into_content)),
206                    )
207                })
208        })
209    }
210}
211
212impl Resolver<'_, AutNum, PrefixSet<Any>> for RpslEvaluator {
213    type IError = Error;
214
215    #[tracing::instrument(skip(self), fields(%autnum), level = "debug")]
216    fn resolve(&mut self, autnum: &AutNum) -> Result<PrefixSet<Any>, Self::IError> {
217        self.with_connection(|this, conn| {
218            conn.pipeline()
219                .push(Query::Ipv4Routes(*autnum))?
220                .push(Query::Ipv6Routes(*autnum))
221                .map_err(Error::from)
222                .and_then(|pipeline| {
223                    this.collect_results(
224                        pipeline
225                            .responses::<'_, Prefix<Any>>()
226                            .map(|response| response.map(ResponseItem::into_content)),
227                    )
228                })
229        })
230    }
231}
232
233impl Resolver<'_, PeerAs, PrefixSet<Any>> for RpslEvaluator {
234    type IError = Error;
235
236    #[tracing::instrument(skip(self), level = "debug")]
237    fn resolve(&mut self, _: &PeerAs) -> Result<PrefixSet<Any>, Self::IError> {
238        unimplemented!()
239    }
240}