//! Local exceptions per RFC 8416 aka SLURM. use std::{error, fmt, fs, io}; use std::path::Path; use std::str::FromStr; use std::sync::Arc; use log::error; use rpki::rtr::payload::{RouteOrigin, RouterKey}; use rpki::slurm::{BgpsecFilter, PrefixFilter, SlurmFile}; use crate::config::Config; use crate::error::Failed; //------------ LocalExceptions ----------------------------------------------- #[derive(Clone, Debug, Default)] pub struct LocalExceptions { origin_filters: Vec, router_key_filters: Vec, origin_assertions: Vec<(RouteOrigin, Arc)>, router_key_assertions: Vec<(RouterKey, Arc)>, } impl LocalExceptions { pub fn empty() -> Self { Self::default() } pub fn load(config: &Config, keep_comments: bool) -> Result { let mut res = LocalExceptions::empty(); let mut ok = true; for path in &config.exceptions { if let Err(err) = res.extend_from_file(path, keep_comments) { error!( "Failed to load exceptions file {}: {}", path.display(), err ); ok = false; } } if ok { Ok(res) } else { Err(Failed) } } pub fn from_json( json: &str, keep_comments: bool ) -> Result { let mut res = LocalExceptions::empty(); res.extend_from_json(json, keep_comments)?; Ok(res) } pub fn from_file>( path: P, keep_comments: bool ) -> Result { let mut res = Self::empty(); res.extend_from_file(path, keep_comments)?; Ok(res) } pub fn extend_from_json( &mut self, json: &str, keep_comments: bool ) -> Result<(), serde_json::Error> { self.extend_from_parsed( SlurmFile::from_str(json)?, None, keep_comments ); Ok(()) } pub fn extend_from_file>( &mut self, path: P, keep_comments: bool ) -> Result<(), LoadError> { let buf = fs::read_to_string(&path)?; self.extend_from_parsed( SlurmFile::from_str(&buf)?, Some(path.as_ref().into()), keep_comments ); Ok(()) } fn extend_from_parsed( &mut self, json: SlurmFile, path: Option>, keep_comments: bool, ) { // If we don’t keep comments, we can have one info value for // everything and save a bit of memory. let info = (!keep_comments).then(|| { Arc::new(ExceptionInfo { path: path.clone(), comment: None }) }); let info = info.as_ref(); // So we can use info.cloned() below. self.origin_filters.extend( json.filters.prefix.into_iter().map(|mut item| { if !keep_comments { item.comment = None } item }) ); self.router_key_filters.extend( json.filters.bgpsec.into_iter().map(|mut item| { if !keep_comments { item.comment = None } item }) ); self.origin_assertions.extend( json.assertions.prefix.into_iter().map(|item| { ( RouteOrigin::new(item.prefix, item.asn), info.cloned().unwrap_or_else(|| { Arc::new(ExceptionInfo { path: path.clone(), comment: item.comment, }) }) ) }) ); self.router_key_assertions.extend( json.assertions.bgpsec.into_iter().map(|item| { ( RouterKey::new( item.ski, item.asn, item.router_public_key.into() ), info.cloned().unwrap_or_else(|| { Arc::new(ExceptionInfo { path: path.clone(), comment: item.comment, }) }) ) }) ); } pub fn drop_origin(&self, origin: RouteOrigin) -> bool { self.origin_filters.iter().any(|filter| filter.drop_origin(origin)) } pub fn drop_router_key(&self, key: &RouterKey) -> bool { self.router_key_filters.iter().any(|filter| { filter.drop_router_key(key) }) } pub fn origin_assertions( &self ) -> impl Iterator)> + '_ { self.origin_assertions.iter().map(|(origin, info)| { (*origin, info.clone()) }) } pub fn router_key_assertions( &self ) -> impl Iterator)> + '_ { self.router_key_assertions.iter().map(|(key, info)| { (key.clone(), info.clone()) }) } } //------------ ExceptionInfo ------------------------------------------------- #[derive(Clone, Debug, Default)] pub struct ExceptionInfo { pub path: Option>, pub comment: Option, } #[cfg(feature = "arbitrary")] impl<'a> arbitrary::Arbitrary<'a> for ExceptionInfo { fn arbitrary( u: &mut arbitrary::Unstructured<'a> ) -> arbitrary::Result { Ok(Self { path: if bool::arbitrary(u)? { Some( std::path::PathBuf::arbitrary(u)?.into_boxed_path().into() ) } else { None }, comment: Option::arbitrary(u)?, }) } } //------------ LoadError ---------------------------------------------------- #[derive(Debug)] pub enum LoadError { Io(io::Error), Json(serde_json::Error), } impl From for LoadError { fn from(err: io::Error) -> LoadError { LoadError::Io(err) } } impl From for LoadError { fn from(err: serde_json::Error) -> LoadError { LoadError::Json(err) } } impl fmt::Display for LoadError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { LoadError::Io(ref err) => err.fmt(f), LoadError::Json(ref err) => err.fmt(f), } } } impl error::Error for LoadError { }