use ipnet::Ipv4Net;
use itertools::Itertools;
use std::{fmt::Write, net::Ipv4Addr};
use crate::{
bgp::Community,
ospf::{LinkWeight, OspfArea},
types::ASN,
};
const ROUTER_OSPF_INSTANCE: u16 = 10;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Target {
CiscoNexus7000,
Frr,
}
#[derive(Debug)]
pub struct Interface {
iface_name: String,
ip_address: Vec<(Ipv4Net, bool)>,
cost: Option<u16>,
no_cost: bool,
area: Option<OspfArea>,
no_area: bool,
dead_interval: Option<u16>,
no_dead_interval: bool,
hello_interval: Option<u16>,
no_hello_interval: bool,
mac_address: Option<[u8; 6]>,
no_mac_address: bool,
shutdown: Option<bool>,
switchport: Option<bool>,
}
impl Interface {
pub fn new(name: impl Into<String>) -> Self {
Self {
iface_name: name.into(),
ip_address: Vec::new(),
cost: None,
no_cost: false,
area: None,
no_area: false,
shutdown: None,
switchport: None,
dead_interval: None,
no_dead_interval: false,
hello_interval: None,
no_hello_interval: false,
mac_address: None,
no_mac_address: false,
}
}
pub fn no(&self) -> String {
format!("no interface {}\n", self.iface_name)
}
pub fn ip_address(&mut self, addr: Ipv4Net) -> &mut Self {
self.ip_address.push((addr, true));
self
}
pub fn no_ip_address(&mut self, addr: Ipv4Net) -> &mut Self {
self.ip_address.push((addr, false));
self
}
pub fn cost(&mut self, cost: LinkWeight) -> &mut Self {
let cost = cost.round();
if cost > 0.0 && cost < u16::MAX as f64 {
self.cost = Some(cost as u16);
} else {
self.cost = None;
self.no_cost = true;
self.shutdown = Some(true);
}
self
}
pub fn no_cost(&mut self) -> &mut Self {
self.no_cost = true;
self
}
pub fn area(&mut self, area: impl Into<OspfArea>) -> &mut Self {
self.area = Some(area.into());
self
}
pub fn no_area(&mut self) -> &mut Self {
self.no_area = true;
self
}
pub fn dead_interval(&mut self, seconds: u16) -> &mut Self {
self.dead_interval = Some(seconds);
self
}
pub fn no_dead_interval(&mut self) -> &mut Self {
self.no_dead_interval = true;
self
}
pub fn hello_interval(&mut self, seconds: u16) -> &mut Self {
self.hello_interval = Some(seconds);
self
}
pub fn no_hello_interval(&mut self) -> &mut Self {
self.no_hello_interval = true;
self
}
pub fn mac_address(&mut self, addr: [u8; 6]) -> &mut Self {
self.mac_address = Some(addr);
self
}
pub fn no_mac_address(&mut self) -> &mut Self {
self.no_mac_address = true;
self
}
pub fn shutdown(&mut self) -> &mut Self {
self.shutdown = Some(true);
self
}
pub fn no_shutdown(&mut self) -> &mut Self {
self.shutdown = Some(false);
self
}
pub fn switchport(&mut self) -> &mut Self {
self.switchport = Some(true);
self
}
pub fn no_switchport(&mut self) -> &mut Self {
self.switchport = Some(false);
self
}
pub fn build(&self, target: Target) -> String {
let ospf_area_cmd = match target {
Target::CiscoNexus7000 => format!("ip router ospf {ROUTER_OSPF_INSTANCE} area"),
Target::Frr => String::from("ip ospf area"),
};
format!(
"\
interface {iface}\
{switchport}{addr}{cost}{area}{dead}{hello}{mac}{shutdown}
exit
",
switchport = match (target, self.switchport) {
(Target::CiscoNexus7000, Some(true)) => "\n switchport",
(Target::CiscoNexus7000, Some(false)) => "\n no switchport",
_ => "",
},
iface = self.iface_name,
addr = self
.ip_address
.iter()
.fold(String::new(), |mut s, (addr, state)| {
write!(
&mut s,
"\n {}ip address {}",
if *state { "" } else { "no " },
addr
)
.unwrap();
s
}),
cost = match (self.cost, self.no_cost) {
(Some(cost), false) => format!("\n ip ospf cost {cost}"),
(_, true) => String::from("\n no ip ospf cost"),
(None, false) => String::new(),
},
area = match (self.area, self.no_area) {
(Some(area), false) => format!("\n {} {}", ospf_area_cmd, area.0),
(_, true) => format!("\n no {ospf_area_cmd}"),
(None, false) => String::new(),
},
dead = match (self.dead_interval, self.no_dead_interval) {
(Some(seconds), false) => format!("\n ip ospf dead-interval {seconds}"),
(_, true) => String::from("\n no ip ospf dead-interval"),
(None, false) => String::new(),
},
hello = match (self.hello_interval, self.no_hello_interval) {
(Some(seconds), false) => format!("\n ip ospf hello-interval {seconds}"),
(_, true) => String::from("\n no ip ospf hello-interval"),
(None, false) => String::new(),
},
mac = match (self.mac_address, self.no_mac_address) {
(Some(mac), false) if target != Target::Frr => format!(
"\n mac-address {:02x}{:02x}.{:02x}{:02x}.{:02x}{:02x}",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5],
),
(_, true) if target != Target::Frr => String::from("\n no mac-address"),
_ => String::new(),
},
shutdown = match self.shutdown {
Some(true) => "\n shutdown",
Some(false) => "\n no shutdown",
None => "",
},
)
}
}
#[derive(Debug, Default)]
pub struct RouterOspf {
router_id: Option<Ipv4Addr>,
no_router_id: bool,
maximum_paths: Option<u8>,
no_maximum_paths: bool,
}
impl RouterOspf {
pub fn new() -> Self {
Self {
router_id: None,
no_router_id: false,
maximum_paths: None,
no_maximum_paths: false,
}
}
pub fn no(&self, target: Target) -> String {
match target {
Target::CiscoNexus7000 => format!("no router ospf {ROUTER_OSPF_INSTANCE}\n"),
Target::Frr => String::from("no router ospf\n"),
}
}
pub fn router_id(&mut self, id: Ipv4Addr) -> &mut Self {
self.router_id = Some(id);
self
}
pub fn no_router_id(&mut self) -> &mut Self {
self.no_router_id = true;
self
}
pub fn maximum_paths(&mut self, k: u8) -> &mut Self {
self.maximum_paths = Some(k);
self
}
pub fn no_maximum_paths(&mut self) -> &mut Self {
self.no_maximum_paths = true;
self
}
pub fn build(&self, target: Target) -> String {
let instance_str = match target {
Target::CiscoNexus7000 => format!(" {ROUTER_OSPF_INSTANCE}"),
Target::Frr => String::new(),
};
format!(
"\
router ospf{}\
{}{}
exit
",
instance_str,
match (self.router_id, self.no_router_id) {
(Some(id), false) => format!("\n router-id {id}"),
(_, true) => String::from("\n no router-id"),
(None, false) => String::new(),
},
match (self.maximum_paths, self.no_maximum_paths) {
(Some(k), false) => format!("\n maximum-paths {k}"),
(_, true) => String::from("\n no maximum-paths"),
(None, false) => String::new(),
}
)
}
}
#[derive(Debug)]
pub struct RouterBgp {
asn: ASN,
router_id: Option<Ipv4Addr>,
no_router_id: bool,
neighbors: Vec<(RouterBgpNeighbor, bool)>,
networks: Vec<(Ipv4Net, bool)>,
}
impl RouterBgp {
pub fn new(asn: impl Into<ASN>) -> Self {
Self {
asn: asn.into(),
router_id: Default::default(),
no_router_id: Default::default(),
neighbors: Default::default(),
networks: Default::default(),
}
}
pub fn no(&self) -> String {
format!("no router bgp {}\n", self.asn.0)
}
pub fn router_id(&mut self, id: Ipv4Addr) -> &mut Self {
self.router_id = Some(id);
self
}
pub fn no_router_id(&mut self) -> &mut Self {
self.no_router_id = true;
self
}
pub fn network(&mut self, network: Ipv4Net) -> &mut Self {
self.networks.push((network, true));
self
}
pub fn no_network(&mut self, network: Ipv4Net) -> &mut Self {
self.networks.push((network, false));
self
}
pub fn neighbor(&mut self, neighbor: impl Into<RouterBgpNeighbor>) -> &mut Self {
self.neighbors.push((neighbor.into(), true));
self
}
pub fn no_neighbor(&mut self, neighbor: impl Into<RouterBgpNeighbor>) -> &mut Self {
self.neighbors.push((neighbor.into(), false));
self
}
pub fn build(&self, target: Target) -> String {
let router_id_pfx = match target {
Target::CiscoNexus7000 => "",
Target::Frr => "bgp ",
};
let router_id = match (self.router_id, self.no_router_id) {
(Some(id), false) => format!(" {router_id_pfx}router-id {id}\n"),
(_, true) => format!(" no {router_id_pfx}router-id\n"),
(None, false) => String::new(),
};
let mut neighbor_code: String = self
.neighbors
.iter()
.map(|(n, mode)| if *mode { n.build(target) } else { n.no() })
.fold(String::new(), |acc, s| acc + &s);
let af_neighbor_code = match target {
Target::CiscoNexus7000 => String::new(),
Target::Frr => {
let lines = neighbor_code.lines();
let mut new_neighbor_code = String::new();
let mut af_neighbor_code = String::new();
let mut in_af: bool = false;
for line in lines {
if line == " address-family ipv4 unicast" && !in_af {
in_af = true;
} else if line == " exit" && in_af {
in_af = false;
} else if in_af {
af_neighbor_code.push_str(line);
af_neighbor_code.push('\n');
} else {
new_neighbor_code.push_str(line);
new_neighbor_code.push('\n');
}
}
neighbor_code = new_neighbor_code;
af_neighbor_code
}
};
let network_code: String = self
.networks
.iter()
.map(|(n, mode)| {
if *mode {
format!(" network {n}\n")
} else {
format!(" no network {n}\n")
}
})
.fold(String::new(), |acc, s| acc + &s);
let af = if network_code.is_empty() && af_neighbor_code.is_empty() {
String::new()
} else {
let exit_af = match target {
Target::CiscoNexus7000 => "",
Target::Frr => "-address-family",
};
format!(
" address-family ipv4 unicast\n{network_code}{af_neighbor_code} exit{exit_af}\n"
)
};
format!(
"\
router bgp {id}
{router_id}{neighbors}{af}\
exit
",
id = self.asn.0,
router_id = router_id,
neighbors = neighbor_code,
af = af
)
}
}
#[derive(Debug, Clone)]
pub struct RouterBgpNeighbor {
neighbor_id: Ipv4Addr,
remote_as: Option<ASN>,
weight: Option<u16>,
no_weight: bool,
update_source: Option<String>,
no_update_source: bool,
next_hop_self: Option<bool>,
route_reflector_client: Option<bool>,
route_map_in: Option<String>,
no_route_map_in: bool,
route_map_out: Option<String>,
no_route_map_out: bool,
send_community: Option<bool>,
soft_reconfiguration: Option<bool>,
}
impl RouterBgpNeighbor {
pub fn new(neighbor_id: Ipv4Addr) -> Self {
Self {
neighbor_id,
remote_as: Default::default(),
weight: Default::default(),
no_weight: Default::default(),
update_source: Default::default(),
no_update_source: Default::default(),
next_hop_self: Default::default(),
route_reflector_client: Default::default(),
route_map_in: Default::default(),
no_route_map_in: Default::default(),
route_map_out: Default::default(),
no_route_map_out: Default::default(),
send_community: Default::default(),
soft_reconfiguration: Default::default(),
}
}
pub fn no(&self) -> String {
format!(" no neighbor {}\n", self.neighbor_id)
}
pub fn remote_as(&mut self, remote_as: impl Into<ASN>) -> &mut Self {
self.remote_as = Some(remote_as.into());
self
}
pub fn weight(&mut self, weight: u16) -> &mut Self {
self.weight = Some(weight);
self
}
pub fn no_weight(&mut self) -> &mut Self {
self.no_weight = true;
self
}
pub fn update_source(&mut self, iface: impl Into<String>) -> &mut Self {
self.update_source = Some(iface.into());
self
}
pub fn no_update_source(&mut self) -> &mut Self {
self.no_update_source = true;
self
}
pub fn next_hop_self(&mut self) -> &mut Self {
self.next_hop_self = Some(true);
self
}
pub fn no_next_hop_self(&mut self) -> &mut Self {
self.next_hop_self = Some(false);
self
}
pub fn route_reflector_client(&mut self) -> &mut Self {
self.route_reflector_client = Some(true);
self
}
pub fn no_route_reflector_client(&mut self) -> &mut Self {
self.route_reflector_client = Some(false);
self
}
pub fn route_map_in(&mut self, name: impl Into<String>) -> &mut Self {
self.route_map_in = Some(name.into());
self
}
pub fn no_route_map_in(&mut self, name: impl Into<String>) -> &mut Self {
self.route_map_in = Some(name.into());
self.no_route_map_in = true;
self
}
pub fn route_map_out(&mut self, name: impl Into<String>) -> &mut Self {
self.route_map_out = Some(name.into());
self
}
pub fn no_route_map_out(&mut self, name: impl Into<String>) -> &mut Self {
self.route_map_out = Some(name.into());
self.no_route_map_out = true;
self
}
pub fn send_community(&mut self) -> &mut Self {
self.send_community = Some(true);
self
}
pub fn no_send_community(&mut self) -> &mut Self {
self.send_community = Some(false);
self
}
pub fn soft_reconfiguration_inbound(&mut self) -> &mut Self {
self.soft_reconfiguration = Some(true);
self
}
pub fn no_soft_reconfiguration_inbound(&mut self) -> &mut Self {
self.soft_reconfiguration = Some(false);
self
}
pub fn build(&self, target: Target) -> String {
let (mut cfg, pre, tab, finish) = match target {
Target::CiscoNexus7000 => (
match self.remote_as {
Some(id) => format!(" neighbor {} remote-as {}", self.neighbor_id, id.0),
None => format!(" neighbor {}", self.neighbor_id),
},
String::new(),
" ",
"\n exit\n",
),
Target::Frr => (
match self.remote_as {
Some(id) => format!(" neighbor {} remote-as {}", self.neighbor_id, id.0),
None => String::new(),
},
format!("neighbor {} ", self.neighbor_id),
"",
"\n",
),
};
let mut af = String::new();
match (self.weight, self.no_weight) {
(Some(w), false) => af.push_str(&format!("\n {tab}{pre}weight {w}")),
(_, true) => af.push_str(&format!("\n {tab}no {pre}weight")),
(None, false) => {}
}
match self.next_hop_self {
Some(true) => af.push_str(&format!("\n {tab}{pre}next-hop-self")),
Some(false) => af.push_str(&format!("\n {tab}no {pre}next-hop-self")),
None => {}
}
match self.route_reflector_client {
Some(true) => af.push_str(&format!("\n {tab}{pre}route-reflector-client")),
Some(false) => af.push_str(&format!("\n {tab}no {pre}route-reflector-client")),
None => {}
}
match (self.route_map_in.as_ref(), self.no_route_map_in) {
(Some(name), false) => af.push_str(&format!("\n {tab}{pre}route-map {name} in")),
(Some(name), true) => af.push_str(&format!("\n {tab}no {pre}route-map {name} in")),
(None, _) => {}
}
match (self.route_map_out.as_ref(), self.no_route_map_out) {
(Some(name), false) => af.push_str(&format!("\n {tab}{pre}route-map {name} out")),
(Some(name), true) => af.push_str(&format!("\n {tab}no {pre}route-map {name} out")),
(None, _) => {}
}
match (self.update_source.as_ref(), self.no_update_source) {
(Some(iface), false) => cfg.push_str(&format!("\n {tab}{pre}update-source {iface}")),
(_, true) => cfg.push_str(&format!("\n {tab}no {pre}update-source",)),
(None, false) => {}
}
let both = if matches!(target, Target::CiscoNexus7000) {
" both"
} else {
""
};
match self.send_community.as_ref() {
Some(true) => af.push_str(&format!("\n {tab}{pre}send-community{both}")),
Some(false) => af.push_str(&format!("\n {tab}no {pre}send-community{both}")),
_ => {}
}
match self.soft_reconfiguration.as_ref() {
Some(true) => af.push_str(&format!("\n {tab}{pre}soft-reconfiguration inbound")),
Some(false) => af.push_str(&format!("\n {tab}no {pre}soft-reconfiguration inbound")),
_ => {}
}
if !af.is_empty() {
cfg.push_str(&format!("\n {tab}address-family ipv4 unicast"));
cfg.push_str(&af);
cfg.push_str(&format!("\n {tab}exit"));
}
cfg.push_str(finish);
String::from(cfg.trim_start_matches('\n'))
}
}
impl From<&mut RouterBgpNeighbor> for RouterBgpNeighbor {
fn from(val: &mut RouterBgpNeighbor) -> Self {
val.clone()
}
}
#[derive(Debug)]
pub struct StaticRoute {
destination: Ipv4Net,
target: Option<String>,
pref: Option<u8>,
}
impl StaticRoute {
pub fn new(destination: Ipv4Net) -> Self {
Self {
destination,
target: Default::default(),
pref: Default::default(),
}
}
pub fn no(&self, target: Target) -> String {
format!("no {}", self.build(target))
}
pub fn via_address(&mut self, addr: Ipv4Addr) -> &mut Self {
self.target = Some(addr.to_string());
self
}
pub fn via_interface(&mut self, iface: impl Into<String>) -> &mut Self {
self.target = Some(iface.into());
self
}
pub fn blackhole(&mut self) -> &mut Self {
self.target = None;
self
}
pub fn preference(&mut self, pref: u8) -> &mut Self {
self.pref = Some(pref);
self
}
pub fn build(&self, target: Target) -> String {
let null = match target {
Target::CiscoNexus7000 => "null 0",
Target::Frr => "Null0",
};
let pref = self.pref.map(|p| format!(" {p}")).unwrap_or_default();
format!(
"ip route {} {}{}\n",
self.destination,
self.target.as_deref().unwrap_or(null),
pref
)
}
}
#[derive(Debug)]
pub struct RouteMapItem {
name: String,
order: u16,
mode: &'static str,
match_prefix_list: Vec<(PrefixList, bool)>,
match_global_prefix_list: Vec<(String, bool)>,
match_community_list: Vec<(CommunityList, bool)>,
match_as_path_list: Vec<(AsPathList, bool)>,
match_next_hop_pl: Vec<(PrefixList, bool)>,
set_next_hop: Option<(Ipv4Addr, bool)>,
set_weight: Option<(u16, bool)>,
set_local_pref: Option<(u32, bool)>,
set_med: Option<(u32, bool)>,
set_community: Vec<(String, bool)>,
delete_community: Vec<(CommunityList, bool)>,
prepend_as_path: Option<(Vec<ASN>, bool)>,
cont: Option<(u16, bool)>,
}
impl RouteMapItem {
pub fn new(name: impl Into<String>, order: u16, permit: bool) -> Self {
Self {
name: name.into(),
order,
mode: if permit { "permit" } else { "deny" },
match_prefix_list: Default::default(),
match_global_prefix_list: Default::default(),
match_community_list: Default::default(),
match_as_path_list: Default::default(),
match_next_hop_pl: Default::default(),
set_next_hop: Default::default(),
set_weight: Default::default(),
set_local_pref: Default::default(),
set_med: Default::default(),
set_community: Default::default(),
delete_community: Default::default(),
prepend_as_path: Default::default(),
cont: Default::default(),
}
}
pub fn match_prefix_list(&mut self, prefix_list: impl Into<PrefixList>) -> &mut Self {
self.match_prefix_list.push((prefix_list.into(), true));
self
}
pub fn no_match_prefix_list(&mut self, prefix_list: impl Into<PrefixList>) -> &mut Self {
self.match_prefix_list.push((prefix_list.into(), false));
self
}
pub fn match_global_prefix_list(&mut self, prefix_list: impl Into<String>) -> &mut Self {
self.match_global_prefix_list
.push((prefix_list.into(), true));
self
}
pub fn no_match_global_prefix_list(&mut self, prefix_list: impl Into<String>) -> &mut Self {
self.match_global_prefix_list
.push((prefix_list.into(), false));
self
}
pub fn match_community_list(&mut self, community_list: impl Into<CommunityList>) -> &mut Self {
self.match_community_list
.push((community_list.into(), true));
self
}
pub fn no_match_community_list(
&mut self,
community_list: impl Into<CommunityList>,
) -> &mut Self {
self.match_community_list
.push((community_list.into(), false));
self
}
pub fn match_as_path_list(&mut self, as_path_list: impl Into<AsPathList>) -> &mut Self {
self.match_as_path_list.push((as_path_list.into(), true));
self
}
pub fn no_match_as_path_list(&mut self, as_path_list: impl Into<AsPathList>) -> &mut Self {
self.match_as_path_list.push((as_path_list.into(), false));
self
}
pub fn match_next_hop(&mut self, prefix_list: impl Into<PrefixList>) -> &mut Self {
self.match_next_hop_pl.push((prefix_list.into(), true));
self
}
pub fn no_match_next_hop(&mut self, prefix_list: impl Into<PrefixList>) -> &mut Self {
self.match_next_hop_pl.push((prefix_list.into(), false));
self
}
pub fn set_next_hop(&mut self, next_hop: Ipv4Addr) -> &mut Self {
self.set_next_hop = Some((next_hop, true));
self
}
pub fn no_set_next_hop(&mut self) -> &mut Self {
self.set_next_hop = Some((Ipv4Addr::new(0, 0, 0, 0), false));
self
}
pub fn set_weight(&mut self, weight: u16) -> &mut Self {
self.set_weight = Some((weight, true));
self
}
pub fn no_set_weight(&mut self) -> &mut Self {
self.set_weight = Some((0, false));
self
}
pub fn set_local_pref(&mut self, local_pref: u32) -> &mut Self {
self.set_local_pref = Some((local_pref, true));
self
}
pub fn no_set_local_pref(&mut self) -> &mut Self {
self.set_local_pref = Some((0, false));
self
}
pub fn set_med(&mut self, med: u32) -> &mut Self {
self.set_med = Some((med, true));
self
}
pub fn no_set_med(&mut self) -> &mut Self {
self.set_med = Some((0, false));
self
}
pub fn set_community(&mut self, c: impl Into<Community>) -> &mut Self {
self.set_community.push((c.into().to_string(), true));
self
}
pub fn no_set_community(&mut self, c: impl Into<Community>) -> &mut Self {
self.set_community.push((c.into().to_string(), false));
self
}
pub fn delete_community_list(&mut self, community_list: impl Into<CommunityList>) -> &mut Self {
self.delete_community.push((community_list.into(), true));
self
}
pub fn no_remove_community_list(
&mut self,
community_list: impl Into<CommunityList>,
) -> &mut Self {
self.delete_community.push((community_list.into(), false));
self
}
pub fn prepend_as_path<As: Into<ASN>>(
&mut self,
path: impl IntoIterator<Item = As>,
) -> &mut Self {
self.prepend_as_path = Some((path.into_iter().map(|x| x.into()).collect(), true));
self
}
pub fn no_prepend_as_path(&mut self) -> &mut Self {
self.prepend_as_path = Some((Vec::new(), false));
self
}
pub fn continues(&mut self, next_seq: u16) -> &mut Self {
self.cont = Some((next_seq, true));
self
}
pub fn no_continues(&mut self) -> &mut Self {
self.cont = Some((0, false));
self
}
pub fn no(&self, target: Target) -> String {
let mut cfg = String::new();
for (pl, _) in self.match_prefix_list.iter() {
cfg.push_str(&pl.no());
}
for (cl, _) in self.match_community_list.iter() {
cfg.push_str(&cl.no(target));
}
for (asl, _) in self.match_as_path_list.iter() {
cfg.push_str(&asl.no(target));
}
for (pl, _) in self.match_next_hop_pl.iter() {
cfg.push_str(&pl.no());
}
for (cl, _) in self.delete_community.iter() {
cfg.push_str(&cl.no(target));
}
cfg.push_str(&format!(
"no route-map {} {} {}\n",
self.name, self.mode, self.order
));
cfg
}
pub fn build(&self, target: Target) -> String {
let mut cfg = String::new();
for (pl, mode) in self.match_prefix_list.iter() {
cfg.push_str(&if *mode { pl.build() } else { pl.no() });
}
for (cl, mode) in self.match_community_list.iter() {
cfg.push_str(&if *mode {
cl.build(target)
} else {
cl.no(target)
});
}
for (asl, mode) in self.match_as_path_list.iter() {
cfg.push_str(&if *mode {
asl.build(target)
} else {
asl.no(target)
});
}
for (pl, mode) in self.match_next_hop_pl.iter() {
cfg.push_str(&if *mode { pl.build() } else { pl.no() });
}
for (cl, mode) in self.delete_community.iter() {
cfg.push_str(&if *mode {
cl.build(target)
} else {
cl.no(target)
});
}
cfg.push_str(&format!(
"route-map {} {} {}\n",
self.name, self.mode, self.order
));
for (pl, mode) in self.match_prefix_list.iter() {
cfg.push_str(if *mode { " " } else { " no " });
cfg.push_str(&format!("match ip address prefix-list {}\n", pl.name));
}
for (pl, mode) in self.match_global_prefix_list.iter() {
cfg.push_str(if *mode { " " } else { " no " });
cfg.push_str(&format!("match ip address prefix-list {pl}\n"));
}
for (cl, mode) in self.match_community_list.iter() {
cfg.push_str(if *mode { " " } else { " no " });
cfg.push_str(&format!("match community {}\n", cl.name));
}
for (asl, mode) in self.match_as_path_list.iter() {
cfg.push_str(if *mode { " " } else { " no " });
cfg.push_str(&format!("match as-path {}\n", asl.name));
}
for (pl, mode) in self.match_next_hop_pl.iter() {
cfg.push_str(if *mode { " " } else { " no " });
cfg.push_str(&format!("match ip next-hop prefix-list {}\n", pl.name));
}
match self.set_next_hop {
Some((x, true)) => cfg.push_str(&format!(" set ip next-hop {x}\n")),
Some((_, false)) => cfg.push_str(" no set ip next-hop\n"),
None => {}
}
match self.set_weight {
Some((x, true)) => cfg.push_str(&format!(" set weight {x}\n")),
Some((_, false)) => cfg.push_str(" no set weight\n"),
None => {}
}
match self.set_local_pref {
Some((x, true)) => cfg.push_str(&format!(" set local-preference {x}\n")),
Some((_, false)) => cfg.push_str(" no set local-preference\n"),
None => {}
}
match self.set_med {
Some((x, true)) => cfg.push_str(&format!(" set metric {x}\n")),
Some((_, false)) => cfg.push_str(" no set metric\n"),
None => {}
}
let additive = match target {
Target::CiscoNexus7000 => "additive ",
Target::Frr => "",
};
for (c, mode) in self.set_community.iter() {
cfg.push_str(if *mode { " " } else { " no " });
cfg.push_str(&format!("set community {additive}{c}\n"));
}
for (c, mode) in self.delete_community.iter() {
cfg.push_str(if *mode { " " } else { " no " });
cfg.push_str(&format!("set comm-list {} delete\n", c.name));
}
match self.prepend_as_path.as_ref() {
Some((path, true)) => cfg.push_str(&format!(
" set as-path prepend {}\n",
path.iter().map(|x| x.0).join(" ")
)),
Some((_, false)) => cfg.push_str(" no set as-path prepend\n"),
None => {}
}
match self.cont {
Some((x, true)) => cfg.push_str(&format!(" continue {x}\n")),
Some((_, false)) => cfg.push_str(" no continue\n"),
None => {}
}
cfg.push_str("exit\n");
cfg
}
}
#[derive(Debug, Clone)]
pub struct PrefixList {
name: String,
prefixes: Vec<(Ipv4Net, Option<(&'static str, u8)>)>,
}
impl PrefixList {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
prefixes: Default::default(),
}
}
pub fn no(&self) -> String {
format!("no ip prefix-list {}\n", self.name)
}
pub fn prefix(&mut self, prefix: Ipv4Net) -> &mut Self {
self.prefixes.push((prefix, None));
self
}
pub fn prefix_eq(&mut self, prefix: Ipv4Net, len: u8) -> &mut Self {
let plen = prefix.prefix_len();
if len == plen {
self.prefixes.push((prefix, None))
} else {
assert!(len > plen, "{len} > {plen}");
self.prefixes.push((prefix, Some(("eq", len))));
}
self
}
pub fn prefix_le(&mut self, prefix: Ipv4Net, len: u8) -> &mut Self {
assert!(len > prefix.prefix_len());
self.prefixes.push((prefix, Some(("le", len))));
self
}
pub fn prefix_ge(&mut self, prefix: Ipv4Net, len: u8) -> &mut Self {
assert!(len > prefix.prefix_len());
self.prefixes.push((prefix, Some(("ge", len))));
self
}
pub fn build(&self) -> String {
self.prefixes
.iter()
.enumerate()
.map(|(i, (net, opt))| match opt {
None => format!("ip prefix-list {} seq {} permit {net}\n", self.name, i + 1),
Some((dir, len)) => format!(
"ip prefix-list {} seq {} permit {net} {dir} {len}\n",
self.name,
i + 1
),
})
.join("")
}
}
impl From<&mut PrefixList> for PrefixList {
fn from(val: &mut PrefixList) -> Self {
val.clone()
}
}
#[derive(Debug, Clone)]
pub struct CommunityList {
name: String,
communities: Vec<String>,
deny_communities: Vec<String>,
}
impl CommunityList {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
communities: Default::default(),
deny_communities: Default::default(),
}
}
pub fn no(&self, target: Target) -> String {
let root = match target {
Target::CiscoNexus7000 => "ip",
Target::Frr => "bgp",
};
format!("no {} community-list standard {}\n", root, self.name)
}
pub fn community(&mut self, community: impl Into<Community>) -> &mut Self {
self.communities.push(community.into().to_string());
self
}
pub fn deny(&mut self, community: impl Into<Community>) -> &mut Self {
self.deny_communities.push(community.into().to_string());
self
}
pub fn build(&self, target: Target) -> String {
let root = match target {
Target::CiscoNexus7000 => "ip",
Target::Frr => "bgp",
};
let permit = format!(
"{} community-list standard {} permit {}\n",
root,
self.name,
self.communities.iter().join(" ")
);
let deny = self
.deny_communities
.iter()
.map(|c| format!("{root} community-list standard {} deny {c}\n", self.name))
.join("");
format!("{deny}{permit}")
}
}
impl From<&mut CommunityList> for CommunityList {
fn from(val: &mut CommunityList) -> Self {
val.clone()
}
}
#[derive(Debug, Clone)]
pub struct AsPathList {
name: String,
regex: String,
}
impl AsPathList {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
regex: String::new(),
}
}
pub fn no(&self, target: Target) -> String {
let root = match target {
Target::CiscoNexus7000 => "ip",
Target::Frr => "bgp",
};
format!("no {} as-path access-list {}\n", root, self.name)
}
pub fn contains_as(&mut self, asn: impl Into<ASN>) -> &mut Self {
self.regex = format!("_{}_", asn.into().0);
self
}
pub fn build(&self, target: Target) -> String {
let root = match target {
Target::CiscoNexus7000 => "ip",
Target::Frr => "bgp",
};
format!(
"{} as-path access-list {} permit {}\n",
root, self.name, self.regex
)
}
}
impl From<&mut AsPathList> for AsPathList {
fn from(val: &mut AsPathList) -> Self {
val.clone()
}
}
pub fn enable_bgp(target: Target) -> &'static str {
match target {
Target::CiscoNexus7000 => "feature bgp\n",
Target::Frr => "",
}
}
pub fn enable_ospf(target: Target) -> &'static str {
match target {
Target::CiscoNexus7000 => "feature ospf\n",
Target::Frr => "",
}
}
pub fn loopback_iface(target: Target, idx: u8) -> String {
match target {
Target::CiscoNexus7000 => format!("Loopback{idx}"),
Target::Frr => String::from("lo"),
}
}