use std::{
cmp::Ordering,
collections::{BTreeSet, HashMap, HashSet},
iter::once,
net::Ipv4Addr,
};
use bimap::BiMap;
use ipnet::Ipv4Net;
use itertools::Itertools;
use crate::{
bgp::Community,
config::{ConfigExpr, ConfigModifier},
network::Network,
ospf::{InternalEdge, OspfArea, OspfImpl, OspfProcess},
route_map::{
RouteMap, RouteMapDirection as RmDir, RouteMapFlow, RouteMapMatch, RouteMapMatchAsPath,
RouteMapSet, RouteMapState,
},
router::{Router, StaticRoute},
types::{Prefix, PrefixMap, PrefixSet, RouterId, ASN},
};
use super::{
cisco_frr_generators::{
enable_bgp, enable_ospf, loopback_iface, AsPathList, CommunityList, Interface, PrefixList,
RouteMapItem, RouterBgp, RouterBgpNeighbor, RouterOspf, StaticRoute as StaticRouteGen,
Target,
},
Addressor, CfgGen, ExportError,
};
#[derive(Debug)]
pub struct CiscoFrrCfgGen {
target: Target,
ifaces: Vec<String>,
router: RouterId,
asn: ASN,
loopback_prefixes: BiMap<u8, Ipv4Net>,
local_area: Option<OspfArea>,
mac_addresses: HashMap<String, [u8; 6]>,
ospf_params: (Option<u16>, Option<u16>),
route_maps: HashMap<(RouterId, RmDir), Vec<(i16, RouteMapState)>>,
}
impl CiscoFrrCfgGen {
pub fn new<P: Prefix, Q, Ospf: OspfImpl>(
net: &Network<P, Q, Ospf>,
router: RouterId,
target: Target,
ifaces: Vec<String>,
) -> Result<Self, ExportError> {
let asn = net.get_router(router)?.asn();
let r = net.get_router(router)?;
let route_maps = r
.bgp
.get_sessions()
.iter()
.flat_map(|(n, _)| [(*n, RmDir::Incoming), (*n, RmDir::Outgoing)])
.map(|(n, dir)| {
(
(n, dir),
r.bgp
.get_route_maps(n, dir)
.iter()
.map(|x| (x.order(), x.state()))
.chain(once((i16::MAX, RouteMapState::Allow))) .collect_vec(),
)
})
.collect();
Ok(Self {
target,
ifaces,
router,
asn,
loopback_prefixes: Default::default(),
local_area: Default::default(),
mac_addresses: Default::default(),
ospf_params: (Some(1), Some(5)),
route_maps,
})
}
pub fn local_area(&self) -> Option<OspfArea> {
self.local_area
}
pub fn iface_name(&self, idx: usize) -> Result<&str, ExportError> {
if let Some(iface) = self.ifaces.get(idx) {
Ok(iface.as_str())
} else {
Err(ExportError::NotEnoughInterfaces(self.router))
}
}
pub fn iface_idx(&self, name: impl AsRef<str>) -> Result<usize, ExportError> {
let name = name.as_ref();
self.ifaces
.iter()
.enumerate()
.find(|(_, x)| x.as_str() == name)
.map(|(x, _)| x)
.ok_or_else(|| ExportError::InterfaceNotFound(self.router, name.to_string()))
}
pub fn set_mac_address(&mut self, iface_name: impl AsRef<str>, mac_address: [u8; 6]) {
self.mac_addresses
.insert(iface_name.as_ref().to_string(), mac_address);
}
pub fn set_ospf_parameters(&mut self, hello_interval: Option<u16>, dead_interval: Option<u16>) {
self.ospf_params = (hello_interval, dead_interval);
}
fn iface<P: Prefix, A: Addressor<P>>(
&self,
a: RouterId,
b: RouterId,
addressor: &mut A,
) -> Result<&str, ExportError> {
if a == self.router {
self.iface_name(addressor.iface_index(a, b)?)
} else if b == self.router {
self.iface_name(addressor.iface_index(b, a)?)
} else {
Err(ExportError::ModifierDoesNotAffectRouter)
}
}
fn pec_config<P: Prefix, A: Addressor<P>>(&mut self, addressor: &A) -> String {
if addressor.get_pecs().iter().next().is_none() {
return String::new();
}
let mut config = String::from("!\n! Prefix Equivalence Classes\n!\n");
for (prefix, networks) in addressor.get_pecs().iter() {
let mut pl = PrefixList::new(pec_pl_name(*prefix));
if let Some((aggregates, prefix_len)) = aggregate_pec(networks) {
for net in aggregates {
pl.prefix_eq(net, prefix_len);
}
} else {
for net in networks {
pl.prefix(*net);
}
}
config.push_str(&pl.build());
config.push_str("!\n");
}
config
}
fn iface_config<P: Prefix, A: Addressor<P>, Q, Ospf: OspfImpl>(
&mut self,
net: &Network<P, Q, Ospf>,
addressor: &mut A,
) -> Result<String, ExportError> {
let mut config = String::new();
let r = self.router;
let router = net.get_router(self.router)?;
config.push_str("!\n! Interfaces\n!\n");
for edge in net.ospf.neighbors(r).sorted_by_key(|e| e.dst()) {
let n = edge.dst();
let iface_name = self.iface(r, n, addressor)?;
let mut iface = Interface::new(iface_name);
iface.no_switchport();
iface.ip_address(addressor.iface_address_full(r, n)?);
iface.no_shutdown();
if let Some(mac) = self.mac_addresses.get(iface_name) {
iface.mac_address(*mac);
}
if let Some(InternalEdge { weight, area, .. }) = edge.internal() {
iface.cost(weight);
if let Some(hello) = self.ospf_params.0 {
iface.hello_interval(hello);
}
if let Some(dead) = self.ospf_params.1 {
iface.dead_interval(dead);
}
iface.area(area);
self.local_area = Some(self.local_area.map(|x| x.min(area)).unwrap_or(area));
}
config.push_str(&iface.build(self.target));
config.push_str("!\n");
}
let mut lo = Interface::new(loopback_iface(self.target, 0));
lo.ip_address(Ipv4Net::new(addressor.router_address(r)?, 32)?);
lo.no_shutdown();
if let Some(area) = self.local_area {
lo.cost(1.0);
lo.area(area);
}
let mut additional_lo = Vec::new();
for route in router.bgp.get_advertised_routes() {
for address in addressor.prefix_address(route.prefix)? {
if loopback_iface(self.target, 0) == loopback_iface(self.target, 1) {
lo.ip_address(address);
} else {
let mut add_lo = Interface::new(self.get_loopback_iface(address)?);
add_lo.ip_address(address);
additional_lo.push(add_lo);
}
}
}
config.push_str(&lo.build(self.target));
for add_lo in additional_lo {
config.push_str(&add_lo.build(self.target));
}
Ok(config)
}
fn static_route_config<P: Prefix, A: Addressor<P>, Q, Ospf: OspfImpl>(
&self,
net: &Network<P, Q, Ospf>,
router: &Router<P, Ospf::Process>,
addressor: &mut A,
) -> Result<String, ExportError> {
let mut config = String::from("!\n! Static Routes\n!\n");
for (p, sr) in router.sr.get_table().iter() {
for sr in self.static_route(net, addressor, *p, *sr)? {
config.push_str(&sr.build(self.target));
}
}
Ok(config)
}
fn static_route<P: Prefix, A: Addressor<P>, Q, Ospf: OspfImpl>(
&self,
net: &Network<P, Q, Ospf>,
addressor: &mut A,
prefix: P,
sr: StaticRoute,
) -> Result<Vec<StaticRouteGen>, ExportError> {
addressor
.prefix(prefix)?
.to_vec()
.into_iter()
.map(|p| {
let mut static_route = StaticRouteGen::new(p);
match sr {
StaticRoute::Direct(r) => {
static_route.via_interface(self.iface(self.router, r, addressor)?)
}
StaticRoute::Indirect(r) => {
static_route.via_address(self.router_id_to_ip(r, net, addressor)?)
}
StaticRoute::Drop => static_route.blackhole(),
};
Ok(static_route)
})
.collect()
}
fn ospf_config<P: Prefix, A: Addressor<P>, Ospf: OspfProcess>(
&self,
router: &Router<P, Ospf>,
addressor: &mut A,
) -> Result<String, ExportError> {
let mut config = String::new();
let mut router_ospf = RouterOspf::new();
router_ospf.router_id(addressor.router_address(self.router)?);
router_ospf.maximum_paths(if router.do_load_balancing { 16 } else { 1 });
config.push_str("!\n! OSPF\n!\n");
config.push_str(&router_ospf.build(self.target));
Ok(config)
}
fn bgp_config<P: Prefix, A: Addressor<P>, Q, Ospf: OspfImpl>(
&self,
net: &Network<P, Q, Ospf>,
router: &Router<P, Ospf::Process>,
addressor: &mut A,
) -> Result<String, ExportError> {
let mut config = String::new();
let mut default_rm = String::new();
let r = self.router;
let mut router_bgp = RouterBgp::new(self.asn);
router_bgp.router_id(addressor.router_address(r)?);
router_bgp.network(addressor.as_network(self.asn)?);
for (n, (_, client, _)) in router.bgp.get_sessions().iter().sorted_by_key(|(x, _)| *x) {
let rm_name = rm_name(net, *n);
router_bgp.neighbor(self.bgp_neighbor_config(net, addressor, *n, *client, &rm_name)?);
default_rm.push_str(
&RouteMapItem::new(format!("{rm_name}-in"), u16::MAX, true).build(self.target),
);
default_rm.push_str(
&RouteMapItem::new(format!("{rm_name}-out"), u16::MAX, true).build(self.target),
);
}
for route in router.bgp.get_advertised_routes() {
empty_route(&route.as_path, route.med, &route.community)?;
for network in addressor.prefix_address(route.prefix)? {
router_bgp.network(network);
}
}
config.push_str("!\n! BGP\n!\n");
config.push_str(&default_rm);
config.push_str("!\n");
config.push_str(&router_bgp.build(self.target));
config.push_str("!\n");
config.push_str(
&StaticRouteGen::new(addressor.as_network(self.asn)?)
.blackhole()
.build(self.target),
);
Ok(config)
}
fn bgp_neighbor_config<P: Prefix, A: Addressor<P>, Q, Ospf: OspfImpl>(
&self,
net: &Network<P, Q, Ospf>,
addressor: &mut A,
n: RouterId,
is_client: bool,
rm_name: &str,
) -> Result<RouterBgpNeighbor, ExportError> {
let r = self.router;
let mut bgp_neighbor = RouterBgpNeighbor::new(self.router_id_to_ip(n, net, addressor)?);
let neighbor_asn = net.get_router(n)?.asn();
if neighbor_asn == self.asn {
bgp_neighbor.remote_as(neighbor_asn);
bgp_neighbor.update_source(loopback_iface(self.target, 0));
bgp_neighbor.send_community();
} else {
bgp_neighbor.remote_as(neighbor_asn);
bgp_neighbor.update_source(self.iface(r, n, addressor)?);
}
bgp_neighbor.weight(100);
bgp_neighbor.route_map_in(format!("{rm_name}-in"));
bgp_neighbor.route_map_out(format!("{rm_name}-out"));
bgp_neighbor.next_hop_self();
bgp_neighbor.soft_reconfiguration_inbound();
if is_client {
bgp_neighbor.route_reflector_client();
}
Ok(bgp_neighbor)
}
fn route_map_config<P: Prefix, A: Addressor<P>, Q, Ospf: OspfImpl>(
&mut self,
net: &Network<P, Q, Ospf>,
addressor: &mut A,
) -> Result<String, ExportError> {
let mut config = String::new();
let rm_order = |(r1, t1): &(RouterId, RmDir), (r2, t2): &(RouterId, RmDir)| match r1.cmp(r2)
{
Ordering::Equal => match (t1, t2) {
(RmDir::Incoming, RmDir::Outgoing) => Ordering::Less,
(RmDir::Outgoing, RmDir::Incoming) => Ordering::Greater,
_ => Ordering::Equal,
},
x => x,
};
let r = net.get_router(self.router)?;
let route_maps: HashMap<_, _> = r
.bgp
.route_maps_in
.iter()
.map(|(n, maps)| ((*n, RmDir::Incoming), maps.clone()))
.chain(
r.bgp
.route_maps_out
.iter()
.map(|(n, maps)| ((*n, RmDir::Outgoing), maps.clone())),
)
.collect();
config.push_str("!\n! Route-Maps\n");
if route_maps.is_empty() {
config.push_str("!\n");
}
for ((n, ty), maps) in route_maps.iter().sorted_by(|(a, _), (b, _)| rm_order(a, b)) {
let name = format!(
"{}-{}",
rm_name(net, *n),
if matches!(ty, RmDir::Incoming) {
"in"
} else {
"out"
}
);
for rm in maps {
let next_ord = self.next_ord(*n, *ty, rm.order(), rm.state());
let route_map_item = self.route_map_item(&name, rm, next_ord, net, addressor)?;
config.push_str("!\n");
config.push_str(&route_map_item.build(self.target));
}
}
Ok(config)
}
fn next_ord(
&mut self,
neighbor: RouterId,
direction: RmDir,
ord: i16,
state: RouteMapState,
) -> Option<i16> {
let rms = self
.route_maps
.entry((neighbor, direction))
.or_insert_with(|| vec![(i16::MAX, RouteMapState::Allow)]);
let pos = match rms.binary_search_by(|(probe, _)| probe.cmp(&ord)) {
Ok(pos) => pos,
Err(pos) => {
rms.insert(pos, (ord, state));
pos
}
};
rms.get_mut(pos).unwrap().1 = state;
rms.get(pos + 1).map(|(x, _)| *x)
}
fn next_ord_remove(&mut self, neighbor: RouterId, direction: RmDir, ord: i16) -> Option<i16> {
let rms = self.route_maps.get_mut(&(neighbor, direction))?;
let pos = rms.binary_search_by(|(probe, _)| probe.cmp(&ord)).ok()?;
rms.remove(pos);
rms.get(pos).map(|(x, _)| *x)
}
fn route_map_item<P: Prefix, A: Addressor<P>, Q, Ospf: OspfImpl>(
&self,
name: &str,
rm: &RouteMap<P>,
next_ord: Option<i16>,
net: &Network<P, Q, Ospf>,
addressor: &mut A,
) -> Result<RouteMapItem, ExportError> {
let ord = order(rm.order);
let mut route_map_item = RouteMapItem::new(name, ord, rm.state().is_allow());
if let Some(prefixes) = rm_match_prefix_list(rm) {
let prefixes = prefixes.iter().copied().sorted().collect_vec();
if prefixes.len() == 1 && addressor.get_pecs().contains_key(&prefixes[0]) {
route_map_item.match_global_prefix_list(pec_pl_name(prefixes[0]));
} else {
let mut pl = PrefixList::new(format!("{name}-{ord}-pl"));
let networks = prefixes
.into_iter()
.flat_map(|n| addressor.prefix(n).unwrap())
.collect();
if let Some((aggregates, prefix_len)) = aggregate_pec(&networks) {
for net in aggregates {
pl.prefix_eq(net, prefix_len);
}
} else {
for net in networks {
pl.prefix(net);
}
}
route_map_item.match_prefix_list(pl);
}
}
if let Some((communities, deny_communities)) = rm_match_community_list(rm) {
let mut cl = CommunityList::new(format!("{name}-{ord}-cl"));
for c in communities {
cl.community(c);
}
for c in deny_communities {
cl.deny(c);
}
route_map_item.match_community_list(cl);
}
if let Some(asn) = rm_match_as_path_list(rm) {
route_map_item
.match_as_path_list(AsPathList::new(format!("{name}-{ord}-asl")).contains_as(asn));
}
if let Some(nh) = rm_match_next_hop(rm) {
route_map_item.match_next_hop(
PrefixList::new(format!("{name}-{ord}-nh"))
.prefix(Ipv4Net::new(self.router_id_to_ip(nh, net, addressor)?, 32)?),
);
}
if let Some(communities) = rm_delete_community_list(rm) {
let mut cl = CommunityList::new(format!("{name}-{ord}-del-cl"));
for c in communities {
cl.community(c);
}
route_map_item.delete_community_list(cl);
}
for x in rm.set.iter() {
_ = match x {
RouteMapSet::NextHop(nh) => {
route_map_item.set_next_hop(self.router_id_to_ip(*nh, net, addressor)?)
}
RouteMapSet::Weight(Some(w)) => route_map_item.set_weight(*w as u16),
RouteMapSet::Weight(None) => route_map_item.set_weight(100),
RouteMapSet::LocalPref(Some(lp)) => route_map_item.set_local_pref(*lp),
RouteMapSet::LocalPref(None) => route_map_item.set_local_pref(100),
RouteMapSet::Med(Some(m)) => route_map_item.set_med(*m),
RouteMapSet::Med(None) => route_map_item.set_med(0),
RouteMapSet::IgpCost(_) => {
unimplemented!("Changing the IGP cost is not implemented yet!")
}
RouteMapSet::SetCommunity(c) => route_map_item.set_community(*c),
RouteMapSet::DelCommunity(_) => &mut route_map_item, RouteMapSet::PrependASPath(as_path) => route_map_item.prepend_as_path(as_path),
};
}
if rm.state().is_allow() {
if let Some(next_ord) = match rm.flow {
RouteMapFlow::Exit => None,
RouteMapFlow::Continue => next_ord,
RouteMapFlow::ContinueAt(x) => Some(x),
} {
route_map_item.continues(order(next_ord));
}
}
Ok(route_map_item)
}
fn fix_prev_rm_continue<P: Prefix, Q, Ospf: OspfImpl>(
&self,
net: &Network<P, Q, Ospf>,
neighbor: RouterId,
direction: RmDir,
ord: i16,
) -> Option<String> {
let rms = self.route_maps.get(&(neighbor, direction))?;
let pos = rms.binary_search_by(|(probe, _)| probe.cmp(&ord)).ok()?;
let (last_ord, last_state) = rms.get(pos.checked_sub(1)?)?;
if last_state.is_allow() {
let name = full_rm_name(net, neighbor, direction);
Some(
RouteMapItem::new(name, order(*last_ord), true)
.continues(order(ord))
.build(self.target),
)
} else {
None
}
}
fn router_id_to_ip<P: Prefix, A: Addressor<P>, Q, Ospf: OspfImpl>(
&self,
r: RouterId,
net: &Network<P, Q, Ospf>,
addressor: &mut A,
) -> Result<Ipv4Addr, ExportError> {
if net.get_router(r)?.asn() == net.get_router(self.router)?.asn() {
addressor.router_address(r)
} else {
addressor.iface_address(r, self.router)
}
}
fn get_loopback_iface(&mut self, addr: Ipv4Net) -> Result<String, ExportError> {
let idx = if let Some(idx) = self.loopback_prefixes.get_by_right(&addr) {
*idx
} else {
let idx = (1..255u8)
.find(|x| -> bool { !self.loopback_prefixes.contains_left(x) })
.ok_or(ExportError::NotEnoughLoopbacks(self.router))?;
self.loopback_prefixes.insert(idx, addr);
idx
};
Ok(loopback_iface(self.target, idx))
}
fn remove_loopback_iface(&mut self, addr: Ipv4Net) -> Option<String> {
self.loopback_prefixes
.remove_by_right(&addr)
.map(|(idx, _)| loopback_iface(self.target, idx))
}
}
fn full_rm_name<P: Prefix, Q, Ospf: OspfImpl>(
net: &Network<P, Q, Ospf>,
router: RouterId,
direction: RmDir,
) -> String {
let dir = match direction {
RmDir::Incoming => "in",
RmDir::Outgoing => "out",
};
if let Ok(d) = net.get_router(router) {
format!("neighbor-{}-{dir}", d.name())
} else {
format!("neighbor-id-{}-{dir}", router.index())
}
}
fn rm_name<P: Prefix, Q, Ospf: OspfImpl>(net: &Network<P, Q, Ospf>, router: RouterId) -> String {
if let Ok(d) = net.get_router(router) {
format!("neighbor-{}", d.name())
} else {
format!("neighbor-id-{}", router.index())
}
}
impl<P: Prefix, A: Addressor<P>, Q, Ospf: OspfImpl> CfgGen<P, Q, Ospf, A> for CiscoFrrCfgGen {
fn generate_config(
&mut self,
net: &Network<P, Q, Ospf>,
addressor: &mut A,
) -> Result<String, ExportError> {
let mut config = String::new();
let router = net.get_router(self.router)?;
config.push_str("!\n");
config.push_str(enable_bgp(self.target));
config.push_str(enable_ospf(self.target));
config.push_str(&self.pec_config(addressor));
config.push_str(&self.iface_config(net, addressor)?);
config.push_str(&self.static_route_config(net, router, addressor)?);
config.push_str(&self.ospf_config(router, addressor)?);
config.push_str(&self.bgp_config(net, router, addressor)?);
config.push_str(&self.route_map_config(net, addressor)?);
Ok(config)
}
fn generate_command(
&mut self,
net: &Network<P, Q, Ospf>,
addressor: &mut A,
cmd: ConfigModifier<P>,
) -> Result<String, ExportError> {
match cmd {
ConfigModifier::Insert(c) => match c {
ConfigExpr::IgpLinkWeight {
source,
target,
weight,
} => Ok(Interface::new(self.iface(source, target, addressor)?)
.cost(weight)
.build(self.target)),
ConfigExpr::OspfArea {
source,
target,
area,
} => Ok(Interface::new(self.iface(source, target, addressor)?)
.area(area)
.build(self.target)),
ConfigExpr::BgpSession {
source,
target,
target_is_client,
} => {
let (neighbor_is_client, neighbor) = if source == self.router {
(target_is_client, target)
} else if target == self.router {
(false, source)
} else {
return Err(ExportError::ModifierDoesNotAffectRouter);
};
let rm_name = rm_name(net, neighbor);
Ok(format!(
"{}{}{}",
RouterBgp::new(self.asn)
.neighbor(self.bgp_neighbor_config(
net,
addressor,
neighbor,
neighbor_is_client,
&rm_name
)?)
.build(self.target),
RouteMapItem::new(format!("{rm_name}-in"), u16::MAX, true)
.build(self.target),
RouteMapItem::new(format!("{rm_name}-out"), u16::MAX, true)
.build(self.target),
))
}
ConfigExpr::BgpRouteMap {
neighbor,
direction,
map,
..
} => {
let next_ord = self.next_ord(neighbor, direction, map.order(), map.state());
Ok(format!(
"{}{}",
self.route_map_item(
&full_rm_name(net, neighbor, direction),
&map,
next_ord,
net,
addressor,
)?
.build(self.target),
self.fix_prev_rm_continue(net, neighbor, direction, map.order())
.unwrap_or_default()
))
}
ConfigExpr::StaticRoute { prefix, target, .. } => Ok(self
.static_route(net, addressor, prefix, target)?
.into_iter()
.map(|sr| sr.build(self.target))
.collect()),
ConfigExpr::LoadBalancing { .. } => {
Ok(RouterOspf::new().maximum_paths(16).build(self.target))
}
ConfigExpr::AdvertiseRoute {
prefix,
as_path,
med,
community,
..
} => self.advertise_route(addressor, prefix, as_path, med, community),
},
ConfigModifier::Remove(c) => match c {
ConfigExpr::IgpLinkWeight { source, target, .. } => {
Ok(Interface::new(self.iface(source, target, addressor)?)
.no_cost()
.shutdown()
.build(self.target))
}
ConfigExpr::OspfArea { source, target, .. } => {
Ok(Interface::new(self.iface(source, target, addressor)?)
.area(0)
.build(self.target))
}
ConfigExpr::BgpSession { source, target, .. } => Ok(RouterBgp::new(self.asn)
.no_neighbor(RouterBgpNeighbor::new(self.router_id_to_ip(
if source == self.router {
target
} else {
source
},
net,
addressor,
)?))
.build(self.target)),
ConfigExpr::BgpRouteMap {
neighbor,
direction,
map,
..
} => {
let next_ord = self.next_ord_remove(neighbor, direction, map.order());
Ok(format!(
"{}{}",
self.route_map_item(
&full_rm_name(net, neighbor, direction),
&map,
next_ord,
net,
addressor,
)?
.no(self.target),
next_ord
.and_then(|ord| {
self.fix_prev_rm_continue(net, neighbor, direction, ord)
})
.unwrap_or_default(),
))
}
ConfigExpr::StaticRoute { prefix, target, .. } => Ok(self
.static_route(net, addressor, prefix, target)?
.into_iter()
.map(|sr| sr.no(self.target))
.collect()),
ConfigExpr::LoadBalancing { .. } => {
Ok(RouterOspf::new().maximum_paths(1).build(self.target))
}
ConfigExpr::AdvertiseRoute { prefix, .. } => self.withdraw_route(addressor, prefix),
},
ConfigModifier::Update { from, to } => match to {
ConfigExpr::IgpLinkWeight {
source,
target,
weight,
} => Ok(Interface::new(self.iface(source, target, addressor)?)
.cost(weight)
.build(self.target)),
ConfigExpr::OspfArea {
source,
target,
area,
} => Ok(Interface::new(self.iface(source, target, addressor)?)
.area(area)
.build(self.target)),
ConfigExpr::BgpSession {
source,
target,
target_is_client,
} => {
let mut neighbor =
RouterBgpNeighbor::new(self.router_id_to_ip(target, net, addressor)?);
if target_is_client && source == self.router {
neighbor.route_reflector_client();
} else if !target_is_client && source == self.router {
neighbor.no_route_reflector_client();
} else {
return Ok(String::new());
}
Ok(RouterBgp::new(self.asn)
.neighbor(neighbor)
.build(self.target))
}
ConfigExpr::BgpRouteMap {
neighbor,
direction,
map,
..
} => {
if let ConfigExpr::BgpRouteMap { map: old_map, .. } = from {
let rm_name = full_rm_name(net, neighbor, direction);
let next_ord = self.next_ord(neighbor, direction, map.order(), map.state());
Ok(format!(
"{}{}",
self.route_map_item(&rm_name, &old_map, next_ord, net, addressor)?
.no(self.target),
self.route_map_item(&rm_name, &map, next_ord, net, addressor)?
.build(self.target)
))
} else {
unreachable!("Config Modifier must update the same kind of expression")
}
}
ConfigExpr::StaticRoute { prefix, target, .. } => {
if let ConfigExpr::StaticRoute { target: old_sr, .. } = from {
Ok(format!(
"{}{}",
self.static_route(net, addressor, prefix, old_sr)?
.into_iter()
.map(|sr| sr.no(self.target))
.collect::<String>(),
self.static_route(net, addressor, prefix, target)?
.into_iter()
.map(|sr| sr.build(self.target))
.collect::<String>(),
))
} else {
unreachable!("Config Modifier must update the same kind of expression")
}
}
ConfigExpr::LoadBalancing { .. } => unreachable!(),
ConfigExpr::AdvertiseRoute {
prefix,
as_path,
med,
community,
..
} => self.advertise_route(addressor, prefix, as_path, med, community),
},
ConfigModifier::BatchRouteMapEdit { router, updates } => updates
.into_iter()
.map(|u| u.into_modifier(router))
.map(|c| self.generate_command(net, addressor, c))
.collect::<Result<String, _>>(),
}
}
}
fn empty_route(
as_path: &[ASN],
med: Option<u32>,
community: &BTreeSet<Community>,
) -> Result<(), ExportError> {
if !as_path.is_empty() {
Err(ExportError::NotSupported("Cisco routers can only originate `empty' routes. Use route-maps to customize the AS path!"))
} else if med.is_some() {
Err(ExportError::NotSupported("Cisco routers can only originate `empty' routes. Use route-maps to customize the advertised MED value!"))
} else if !community.is_empty() {
Err(ExportError::NotSupported("Cisco routers can only originate `empty' routes. Use route-maps to customize the advertised community!"))
} else {
Ok(())
}
}
impl CiscoFrrCfgGen {
fn advertise_route<P: Prefix, A: Addressor<P>>(
&mut self,
addressor: &mut A,
prefix: P,
as_path: Vec<ASN>,
med: Option<u32>,
community: BTreeSet<Community>,
) -> Result<String, ExportError> {
empty_route(&as_path, med, &community)?;
let mut config = String::new();
let mut bgp_config = RouterBgp::new(self.asn);
for address in addressor.prefix_address(prefix)? {
if loopback_iface(self.target, 0) == loopback_iface(self.target, 1) {
let mut iface = Interface::new(loopback_iface(self.target, 0));
iface.ip_address(address);
config.push_str(&iface.build(self.target))
} else {
config.push_str(
&Interface::new(self.get_loopback_iface(address)?)
.ip_address(address)
.build(self.target),
);
}
}
for prefix_net in addressor.prefix(prefix)? {
bgp_config.network(prefix_net);
}
config.push_str(&bgp_config.build(self.target));
Ok(config)
}
fn withdraw_route<P: Prefix, A: Addressor<P>>(
&mut self,
addressor: &mut A,
prefix: P,
) -> Result<String, ExportError> {
let mut config = String::new();
for network in addressor.prefix_address(prefix)? {
if loopback_iface(self.target, 0) == loopback_iface(self.target, 1) {
let mut iface = Interface::new(loopback_iface(self.target, 0));
iface.no_ip_address(network);
config.push_str(&iface.build(self.target))
} else {
config.push_str(
&Interface::new(
self.remove_loopback_iface(network)
.ok_or(ExportError::WithdrawUnadvertisedRoute)?,
)
.no(),
);
}
}
let mut bgp_config = RouterBgp::new(self.asn);
for net in addressor.prefix(prefix)? {
bgp_config.no_network(net);
}
config.push_str(&bgp_config.build(self.target));
Ok(config)
}
}
fn order(old: i16) -> u16 {
((old as i32) - (i16::MIN as i32)) as u16
}
fn rm_match_prefix_list<P: Prefix>(rm: &RouteMap<P>) -> Option<P::Set> {
let mut prefixes: Option<P::Set> = None;
for cond in rm.conds.iter() {
if let RouteMapMatch::Prefix(pl) = cond {
if let Some(prefixes) = &mut prefixes {
prefixes.retain(|p| pl.contains(p));
} else {
prefixes = Some(pl.clone())
}
}
}
prefixes
}
fn rm_match_community_list<P: Prefix>(
rm: &RouteMap<P>,
) -> Option<(HashSet<Community>, HashSet<Community>)> {
let mut communities = HashSet::new();
let mut deny_communities = HashSet::new();
for cond in rm.conds.iter() {
match cond {
RouteMapMatch::Community(comm) => communities.insert(*comm),
RouteMapMatch::DenyCommunity(comm) => deny_communities.insert(*comm),
_ => false,
};
}
if communities.is_empty() && deny_communities.is_empty() {
None
} else {
Some((communities, deny_communities))
}
}
fn rm_match_as_path_list<P: Prefix>(rm: &RouteMap<P>) -> Option<ASN> {
let mut contained_ases = Vec::new();
for cond in rm.conds.iter() {
if let RouteMapMatch::AsPath(RouteMapMatchAsPath::Contains(asn)) = cond {
contained_ases.push(asn)
};
}
match contained_ases.as_slice() {
[] => None,
[asn] => Some(**asn),
_ => unimplemented!("More complex AS path constraints are not implemented yet!"),
}
}
fn rm_match_next_hop<P: Prefix>(rm: &RouteMap<P>) -> Option<RouterId> {
let mut next_hop: Option<RouterId> = None;
for cond in rm.conds.iter() {
if let RouteMapMatch::NextHop(nh) = cond {
if next_hop.is_none() {
next_hop = Some(*nh);
} else if next_hop != Some(*nh) {
panic!("Multiple different next-hops matched in a route-map!")
}
}
}
next_hop
}
fn rm_delete_community_list<P: Prefix>(rm: &RouteMap<P>) -> Option<HashSet<Community>> {
let mut communities = HashSet::new();
for set in rm.set.iter() {
if let RouteMapSet::DelCommunity(c) = set {
communities.insert(*c);
}
}
if communities.is_empty() {
None
} else {
Some(communities)
}
}
fn pec_pl_name<P: Prefix>(prefix: P) -> String {
let id: u32 = prefix.into();
format!("prefix-{id}-equivalence-class-pl")
}
fn aggregate_pec(networks: &Vec<Ipv4Net>) -> Option<(Vec<Ipv4Net>, u8)> {
if networks.is_empty() {
return None;
}
let prefix_len = networks.first().unwrap().prefix_len();
if networks.iter().any(|p| p.prefix_len() != prefix_len) {
return None;
}
let mut aggregates = Ipv4Net::aggregate(networks);
aggregates.sort();
Some((aggregates, prefix_len))
}