use crate::{
bgp::BgpRibEntry,
ospf::LinkWeight,
types::{AsId, Prefix, PrefixSet, RouterId},
};
use ordered_float::NotNan;
use serde::{Deserialize, Serialize};
use std::{cmp::Ordering, fmt};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(bound(deserialize = "P: for<'a> serde::Deserialize<'a>"))]
pub struct RouteMap<P: Prefix> {
pub order: i16,
pub state: RouteMapState,
pub conds: Vec<RouteMapMatch<P>>,
pub set: Vec<RouteMapSet>,
pub flow: RouteMapFlow,
}
impl<P: Prefix> RouteMap<P> {
pub fn new(
order: i16,
state: RouteMapState,
conds: Vec<RouteMapMatch<P>>,
set: Vec<RouteMapSet>,
flow: RouteMapFlow,
) -> Self {
Self {
order,
state,
conds,
set,
flow,
}
}
pub fn apply(&self, mut route: BgpRibEntry<P>) -> (RouteMapFlow, Option<BgpRibEntry<P>>) {
match self.conds.iter().all(|c| c.matches(&route)) {
true => {
if self.state.is_deny() {
(RouteMapFlow::Exit, None)
} else {
self.set.iter().for_each(|s| s.apply(&mut route));
(self.flow, Some(route))
}
}
false => (RouteMapFlow::Continue, Some(route)), }
}
pub fn order(&self) -> i16 {
self.order
}
pub fn state(&self) -> RouteMapState {
self.state
}
pub fn conds(&self) -> &Vec<RouteMapMatch<P>> {
&self.conds
}
pub fn actions(&self) -> &Vec<RouteMapSet> {
&self.set
}
pub fn matches(&self, route: &BgpRibEntry<P>) -> bool {
self.conds.iter().all(|c| c.matches(route))
}
}
pub trait RouteMapList<P: Prefix> {
fn apply(self, route: BgpRibEntry<P>) -> Option<BgpRibEntry<P>>;
}
impl<'a, P, I> RouteMapList<P> for I
where
P: Prefix + 'a,
I: IntoIterator<Item = &'a RouteMap<P>>,
{
fn apply(self, mut entry: BgpRibEntry<P>) -> Option<BgpRibEntry<P>> {
let mut wait_for = None;
for map in self {
if let Some(x) = wait_for {
match map.order.cmp(&x) {
Ordering::Less => continue,
Ordering::Equal => {}
Ordering::Greater => return Some(entry),
}
}
match map.apply(entry) {
(cont, Some(e)) => {
entry = e;
match cont {
RouteMapFlow::Exit => return Some(entry),
RouteMapFlow::Continue => wait_for = None,
RouteMapFlow::ContinueAt(x) => wait_for = Some(x),
}
}
(_, None) => return None,
}
}
Some(entry)
}
}
#[derive(Debug)]
pub struct RouteMapBuilder<P: Prefix> {
order: Option<i16>,
state: Option<RouteMapState>,
conds: Vec<RouteMapMatch<P>>,
set: Vec<RouteMapSet>,
prefix_conds: P::Set,
has_prefix_conds: bool,
flow: RouteMapFlow,
}
impl<P: Prefix> Default for RouteMapBuilder<P> {
fn default() -> Self {
Self {
order: None,
state: None,
conds: Vec::new(),
set: Vec::new(),
prefix_conds: Default::default(),
has_prefix_conds: false,
flow: RouteMapFlow::default(),
}
}
}
impl<P: Prefix> RouteMapBuilder<P> {
pub fn new() -> Self {
Self::default()
}
pub fn order(&mut self, order: u16) -> &mut Self {
self.order = Some(order as i16);
self
}
pub fn order_sgn(&mut self, order: i16) -> &mut Self {
self.order = Some(order);
self
}
pub fn state(&mut self, state: RouteMapState) -> &mut Self {
self.state = Some(state);
self
}
pub fn allow(&mut self) -> &mut Self {
self.state = Some(RouteMapState::Allow);
self
}
pub fn deny(&mut self) -> &mut Self {
self.state = Some(RouteMapState::Deny);
self
}
pub fn cond(&mut self, cond: RouteMapMatch<P>) -> &mut Self {
self.conds.push(cond);
self
}
pub fn match_prefix(&mut self, prefix: P) -> &mut Self {
self.prefix_conds.insert(prefix);
self.has_prefix_conds = true;
self
}
pub fn match_as_path_contains(&mut self, as_id: AsId) -> &mut Self {
self.conds
.push(RouteMapMatch::AsPath(RouteMapMatchAsPath::Contains(as_id)));
self
}
pub fn match_as_path_length(&mut self, as_path_len: usize) -> &mut Self {
self.conds
.push(RouteMapMatch::AsPath(RouteMapMatchAsPath::Length(
RouteMapMatchClause::Equal(as_path_len),
)));
self
}
pub fn match_as_path_length_range(&mut self, from: usize, to: usize) -> &mut Self {
self.conds
.push(RouteMapMatch::AsPath(RouteMapMatchAsPath::Length(
RouteMapMatchClause::Range(from, to),
)));
self
}
pub fn match_next_hop(&mut self, next_hop: RouterId) -> &mut Self {
self.conds.push(RouteMapMatch::NextHop(next_hop));
self
}
pub fn match_community(&mut self, community: u32) -> &mut Self {
self.conds.push(RouteMapMatch::Community(community));
self
}
pub fn match_deny_community(&mut self, community: u32) -> &mut Self {
self.conds.push(RouteMapMatch::DenyCommunity(community));
self
}
pub fn add_set(&mut self, set: RouteMapSet) -> &mut Self {
self.set.push(set);
self
}
pub fn set_next_hop(&mut self, next_hop: RouterId) -> &mut Self {
self.set.push(RouteMapSet::NextHop(next_hop));
self
}
pub fn set_weight(&mut self, weight: u32) -> &mut Self {
self.set.push(RouteMapSet::Weight(Some(weight)));
self
}
pub fn reset_weight(&mut self) -> &mut Self {
self.set.push(RouteMapSet::Weight(None));
self
}
pub fn set_local_pref(&mut self, local_pref: u32) -> &mut Self {
self.set.push(RouteMapSet::LocalPref(Some(local_pref)));
self
}
pub fn reset_local_pref(&mut self) -> &mut Self {
self.set.push(RouteMapSet::LocalPref(None));
self
}
pub fn set_med(&mut self, med: u32) -> &mut Self {
self.set.push(RouteMapSet::Med(Some(med)));
self
}
pub fn reset_med(&mut self) -> &mut Self {
self.set.push(RouteMapSet::Med(None));
self
}
pub fn set_igp_cost(&mut self, cost: LinkWeight) -> &mut Self {
self.set.push(RouteMapSet::IgpCost(cost));
self
}
pub fn set_community(&mut self, community: u32) -> &mut Self {
self.set.push(RouteMapSet::SetCommunity(community));
self
}
pub fn remove_community(&mut self, community: u32) -> &mut Self {
self.set.push(RouteMapSet::DelCommunity(community));
self
}
pub fn exit(&mut self) -> &mut Self {
self.flow = RouteMapFlow::Exit;
self
}
pub fn continue_next(&mut self) -> &mut Self {
self.flow = RouteMapFlow::Continue;
self
}
pub fn continue_at(&mut self, order: i16) -> &mut Self {
self.flow = RouteMapFlow::ContinueAt(order);
self
}
pub fn build(&self) -> RouteMap<P> {
let order = match self.order {
Some(o) => o,
None => panic!("Order was not set for a Route-Map!"),
};
let state = match self.state {
Some(s) => s,
None => panic!("State was not set for a Route-Map!"),
};
if let RouteMapFlow::ContinueAt(continue_at) = self.flow {
assert!(
continue_at > order,
"The order of the next route map must be larger than the order!"
);
}
let mut conds = self.conds.clone();
if self.has_prefix_conds {
conds.push(RouteMapMatch::Prefix(self.prefix_conds.clone()));
}
let set = if state.is_deny() {
vec![]
} else {
self.set.clone()
};
RouteMap::new(order, state, conds, set, self.flow)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum RouteMapState {
Allow,
Deny,
}
impl RouteMapState {
pub fn is_allow(&self) -> bool {
self == &Self::Allow
}
pub fn is_deny(&self) -> bool {
self == &Self::Deny
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum RouteMapMatch<P: Prefix> {
Prefix(P::Set),
AsPath(RouteMapMatchAsPath),
NextHop(RouterId),
Community(u32),
DenyCommunity(u32),
}
impl<P: Prefix> RouteMapMatch<P> {
pub fn matches(&self, entry: &BgpRibEntry<P>) -> bool {
match self {
Self::Prefix(prefixes) => prefixes.contains(&entry.route.prefix),
Self::AsPath(clause) => clause.matches(&entry.route.as_path),
Self::NextHop(nh) => entry.route.next_hop == *nh,
Self::Community(com) => entry.route.community.contains(com),
Self::DenyCommunity(com) => !entry.route.community.contains(com),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum RouteMapMatchClause<T> {
Range(T, T),
RangeExclusive(T, T),
Equal(T),
}
impl<T> RouteMapMatchClause<T>
where
T: PartialOrd + PartialEq,
{
pub fn matches(&self, val: &T) -> bool {
match self {
Self::Range(min, max) => val >= min && val <= max,
Self::RangeExclusive(min, max) => val >= min && val < max,
Self::Equal(x) => val == x,
}
}
}
impl<T> fmt::Display for RouteMapMatchClause<T>
where
T: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RouteMapMatchClause::Range(a, b) => f.write_fmt(format_args!("in ({a}..{b})")),
RouteMapMatchClause::RangeExclusive(a, b) => {
f.write_fmt(format_args!("in ({a}..{b}])"))
}
RouteMapMatchClause::Equal(a) => f.write_fmt(format_args!("== {a}")),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum RouteMapMatchAsPath {
Contains(AsId),
Length(RouteMapMatchClause<usize>),
}
impl RouteMapMatchAsPath {
pub fn matches(&self, path: &[AsId]) -> bool {
match self {
Self::Contains(as_id) => path.contains(as_id),
Self::Length(clause) => clause.matches(&path.len()),
}
}
}
impl fmt::Display for RouteMapMatchAsPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RouteMapMatchAsPath::Contains(as_id) => {
f.write_fmt(format_args!("{} in AsPath", as_id.0))
}
RouteMapMatchAsPath::Length(c) => f.write_fmt(format_args!("len(AsPath) {c}")),
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum RouteMapSet {
NextHop(RouterId),
Weight(Option<u32>),
LocalPref(Option<u32>),
Med(Option<u32>),
IgpCost(LinkWeight),
SetCommunity(u32),
DelCommunity(u32),
}
impl RouteMapSet {
pub fn apply<P: Prefix>(&self, entry: &mut BgpRibEntry<P>) {
match self {
Self::NextHop(nh) => {
entry.route.next_hop = *nh;
entry.igp_cost = None
}
Self::Weight(w) => entry.weight = w.unwrap_or(100),
Self::LocalPref(lp) => entry.route.local_pref = Some(lp.unwrap_or(100)),
Self::Med(med) => entry.route.med = Some(med.unwrap_or(0)),
Self::IgpCost(w) => entry.igp_cost = Some(NotNan::new(*w).unwrap()),
Self::SetCommunity(c) => {
entry.route.community.insert(*c);
}
Self::DelCommunity(c) => {
entry.route.community.remove(c);
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum RouteMapDirection {
Incoming,
Outgoing,
}
impl fmt::Display for RouteMapDirection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RouteMapDirection::Incoming => write!(f, "in"),
RouteMapDirection::Outgoing => write!(f, "out"),
}
}
}
impl RouteMapDirection {
pub fn incoming(&self) -> bool {
matches!(self, Self::Incoming)
}
pub fn outgoing(&self) -> bool {
matches!(self, Self::Outgoing)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum RouteMapFlow {
Exit,
Continue,
ContinueAt(i16),
}
impl Default for RouteMapFlow {
fn default() -> Self {
Self::Continue
}
}
impl fmt::Display for RouteMapFlow {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
RouteMapFlow::Exit => write!(f, "break"),
RouteMapFlow::Continue => write!(f, "continue"),
RouteMapFlow::ContinueAt(c) => write!(f, "continue at {c}"),
}
}
}