Add a FabricConfig builder which iterates through nodes and generates the frr config for the specified current_node. This part also distributes the fabric options on all the interfaces – e.g. the hello-interval option on the fabric will be added to all interfaces here.
We mainly need to add these objects to FRR: * interfaces We simply iterate through all configured interfaces and add them FRR with a short config line telling the daemon to enable openfabric/ospf on this interface. * routers The tell the FRR daemon to initiate the openfabric/ospf daemon on every node. * access-lists We throw all the router-ips of all the other nodes in the same fabric in access-list. This way we can simply use a route-map to match on it. * route-maps We add a route-map to every fabric so that we rewrite the source address to the current router-ip which is on the local dummy_interface. * ip-protocol statements These add the route-map to the protocol and all the routes from the protocol are going through the route-map. Signed-off-by: Gabriel Goller <g.gol...@proxmox.com> --- proxmox-ve-config/Cargo.toml | 7 + proxmox-ve-config/debian/control | 37 ++- proxmox-ve-config/src/sdn/fabric/mod.rs | 416 ++++++++++++++++++++++++ 3 files changed, 454 insertions(+), 6 deletions(-) diff --git a/proxmox-ve-config/Cargo.toml b/proxmox-ve-config/Cargo.toml index 3f7639efa153..231e237fb82f 100644 --- a/proxmox-ve-config/Cargo.toml +++ b/proxmox-ve-config/Cargo.toml @@ -24,3 +24,10 @@ proxmox-serde = { version = "0.1.2" } proxmox-sys = "0.6.4" proxmox-sortable-macro = "0.1.3" proxmox-network-types = { version = "0.1", path = "../proxmox-network-types/" } +proxmox-frr = { version = "0.1", path = "../proxmox-frr/", optional = true } + +[features] +frr = ["dep:proxmox-frr" ] + +[dev-dependencies] +similar-asserts = "1" diff --git a/proxmox-ve-config/debian/control b/proxmox-ve-config/debian/control index 60ebcbc40e1c..5556ba747b8a 100644 --- a/proxmox-ve-config/debian/control +++ b/proxmox-ve-config/debian/control @@ -2,22 +2,26 @@ Source: rust-proxmox-ve-config Section: rust Priority: optional Build-Depends: debhelper-compat (= 13), - dh-sequence-cargo, - cargo:native <!nocheck>, + dh-sequence-cargo +Build-Depends-Arch: cargo:native <!nocheck>, rustc:native <!nocheck>, libstd-rust-dev <!nocheck>, librust-anyhow-1+default-dev <!nocheck>, librust-log-0.4+default-dev <!nocheck>, librust-nix-0.26+default-dev <!nocheck>, + librust-proxmox-network-types-0.1+default-dev <!nocheck>, librust-proxmox-schema-4+default-dev <!nocheck>, + librust-proxmox-section-config-2+default-dev (>= 2.1.1-~~) <!nocheck>, + librust-proxmox-serde-0.1+default-dev (>= 0.1.2-~~) <!nocheck>, librust-proxmox-sortable-macro-0.1+default-dev (>= 0.1.3-~~) <!nocheck>, librust-proxmox-sys-0.6+default-dev (>= 0.6.4-~~) <!nocheck>, librust-serde-1+default-dev <!nocheck>, librust-serde-1+derive-dev <!nocheck>, librust-serde-json-1+default-dev <!nocheck>, librust-serde-plain-1+default-dev <!nocheck>, - librust-serde-with-3+default-dev <!nocheck>, - librust-thiserror-1+default-dev (>= 1.0.59-~~) <!nocheck> + librust-serde-with-3+default-dev (>= 3.8.1-~~) <!nocheck>, + librust-thiserror-2+default-dev <!nocheck>, + librust-tracing-0.1+default-dev <!nocheck> Maintainer: Proxmox Support Team <supp...@proxmox.com> Standards-Version: 4.7.0 Vcs-Git: git://git.proxmox.com/git/proxmox-ve-rs.git @@ -33,15 +37,21 @@ Depends: librust-anyhow-1+default-dev, librust-log-0.4+default-dev, librust-nix-0.26+default-dev, + librust-proxmox-network-types-0.1+default-dev, librust-proxmox-schema-4+default-dev, + librust-proxmox-section-config-2+default-dev (>= 2.1.1-~~), + librust-proxmox-serde-0.1+default-dev (>= 0.1.2-~~), librust-proxmox-sortable-macro-0.1+default-dev (>= 0.1.3-~~), librust-proxmox-sys-0.6+default-dev (>= 0.6.4-~~), librust-serde-1+default-dev, librust-serde-1+derive-dev, librust-serde-json-1+default-dev, librust-serde-plain-1+default-dev, - librust-serde-with-3+default-dev, - librust-thiserror-1+default-dev (>= 1.0.59-~~) + librust-serde-with-3+default-dev (>= 3.8.1-~~), + librust-thiserror-2+default-dev, + librust-tracing-0.1+default-dev +Suggests: + librust-proxmox-ve-config+frr-dev (= ${binary:Version}) Provides: librust-proxmox-ve-config+default-dev (= ${binary:Version}), librust-proxmox-ve-config-0-dev (= ${binary:Version}), @@ -52,3 +62,18 @@ Provides: librust-proxmox-ve-config-0.2.2+default-dev (= ${binary:Version}) Description: Rust crate "proxmox-ve-config" - Rust source code Source code for Debianized Rust crate "proxmox-ve-config" + +Package: librust-proxmox-ve-config+frr-dev +Architecture: any +Multi-Arch: same +Depends: + ${misc:Depends}, + librust-proxmox-ve-config-dev (= ${binary:Version}), + librust-proxmox-frr-0.1+default-dev +Provides: + librust-proxmox-ve-config-0+frr-dev (= ${binary:Version}), + librust-proxmox-ve-config-0.2+frr-dev (= ${binary:Version}), + librust-proxmox-ve-config-0.2.2+frr-dev (= ${binary:Version}) +Description: Rust crate "proxmox-ve-config" - feature "frr" + This metapackage enables feature "frr" for the Rust proxmox-ve-config crate, by + pulling in any additional dependencies needed by that feature. diff --git a/proxmox-ve-config/src/sdn/fabric/mod.rs b/proxmox-ve-config/src/sdn/fabric/mod.rs index 949486a86355..5dd4866e33bb 100644 --- a/proxmox-ve-config/src/sdn/fabric/mod.rs +++ b/proxmox-ve-config/src/sdn/fabric/mod.rs @@ -3,12 +3,32 @@ pub mod ospf; use openfabric::OpenFabricSectionConfig; use ospf::OspfSectionConfig; +use proxmox_network_types::net::Net; use proxmox_section_config::typed::ApiSectionDataEntry; use proxmox_section_config::typed::SectionConfigData; +use std::net::{IpAddr, Ipv4Addr}; use std::ops::Deref; +use std::collections::HashMap; use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +#[cfg(feature = "frr")] +use { + anyhow::anyhow, + proxmox_frr::{ + ospf::Area, + route_map::{ + AccessAction, AccessList, AccessListName, AccessListRule, ProtocolRouteMap, + RouteMap, RouteMapName, RouteMapSet, RouteMapMatch, ProtocolType + }, + FrrConfig, FrrWord, Interface, InterfaceName, Router, RouterName, + }, + proxmox_network_types::hostname::Hostname, + std::collections::BTreeMap, +}; #[derive(Debug, Clone)] pub struct Valid<T>(SectionConfigData<T>); @@ -42,3 +62,399 @@ where } } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Error)] +pub enum ConfigError { + #[error("node id has invalid format")] + InvalidNodeId, +} + +#[derive(Default, Clone)] +pub struct FabricConfig { + openfabric: Option<Valid<OpenFabricSectionConfig>>, + ospf: Option<Valid<OspfSectionConfig>>, +} + +impl FabricConfig { + pub fn new(raw_openfabric: &str, raw_ospf: &str) -> Result<Self, anyhow::Error> { + let openfabric = <Valid<OpenFabricSectionConfig>>::parse_section_config( + "openfabric.cfg", + raw_openfabric, + )?; + let ospf = <Valid<OspfSectionConfig>>::parse_section_config("ospf.cfg", raw_ospf)?; + + Ok(Self { + openfabric: Some(openfabric), + ospf: Some(ospf), + }) + } + + pub fn openfabric(&self) -> &Option<Valid<OpenFabricSectionConfig>> { + &self.openfabric + } + pub fn ospf(&self) -> &Option<Valid<OspfSectionConfig>> { + &self.ospf + } + + pub fn with_openfabric(config: Valid<OpenFabricSectionConfig>) -> FabricConfig { + Self { + openfabric: Some(config), + ospf: None, + } + } + + pub fn with_ospf(config: Valid<OspfSectionConfig>) -> FabricConfig { + Self { + ospf: Some(config), + openfabric: None, + } + } +} + +pub trait FromSectionConfig +where + Self: Sized + TryFrom<SectionConfigData<Self::Section>>, + <Self as TryFrom<SectionConfigData<Self::Section>>>::Error: std::fmt::Debug, +{ + type Section: ApiSectionDataEntry + DeserializeOwned; + + fn from_section_config(raw: &str) -> Result<Self, anyhow::Error> { + let section_config_data = Self::Section::section_config() + .parse(Self::filename(), raw)? + .try_into()?; + + let output = Self::try_from(section_config_data).unwrap(); + Ok(output) + } + + fn filename() -> String; +} + +/// Builder that helps building the FrrConfig. +#[derive(Default)] +#[cfg(feature = "frr")] +pub struct FrrConfigBuilder { + fabrics: FabricConfig, +} + +#[cfg(feature = "frr")] +impl FrrConfigBuilder { + /// Add fabrics to the builder + pub fn add_fabrics(mut self, fabric: FabricConfig) -> FrrConfigBuilder { + self.fabrics = fabric; + self + } + + /// Build the complete [`FrrConfig`] from this builder configuration given the hostname of the + /// node for which we want to build the config. We also inject the common fabric-level options + /// into the interfaces here. (e.g. the fabric-level "hello-interval" gets added to every + /// interface if there isn't a more specific one.) + pub fn build(self, current_node: Hostname) -> Result<FrrConfig, anyhow::Error> { + let mut router: BTreeMap<RouterName, Router> = BTreeMap::new(); + let mut interfaces: BTreeMap<InterfaceName, Interface> = BTreeMap::new(); + let mut access_lists: BTreeMap<AccessListName, AccessList> = BTreeMap::new(); + let mut routemaps: Vec<RouteMap> = Vec::new(); + let mut protocol_routemaps: Vec<ProtocolRouteMap> = Vec::new(); + + if let Some(openfabric) = self.fabrics.openfabric { + let mut fabrics = HashMap::new(); + let mut local_configuration = Vec::new(); + + for (_, section) in openfabric.iter() { + match section { + OpenFabricSectionConfig::Fabric(fabric) => { + fabrics.insert(fabric.fabric_id.clone(), fabric); + }, + OpenFabricSectionConfig::Node(node) => { + if node.node_id.node == current_node { + local_configuration.push(node); + } + } + } + } + + let mut routemap_seq = 100; + + for node in local_configuration { + let fabric = fabrics.get(&node.node_id.fabric_id) + .ok_or_else(|| anyhow!("could not find fabric: {}", node.node_id.fabric_id))?; + + let (router_name, router_item) = Self::build_openfabric_router( + &node.node_id.fabric_id, + &node.router_id.into(), + )?; + router.insert(router_name, router_item); + + let (interface, interface_name) = Self::build_openfabric_dummy_interface( + &node.node_id.fabric_id, + node.router_id, + )?; + + if interfaces.insert(interface_name, interface).is_some() { + tracing::error!( + "An interface with the same name as the dummy interface exists" + ); + } + + for interface in node.interface.iter() { + let (interface, interface_name) = Self::build_openfabric_interface( + &node.node_id.fabric_id, + interface, + fabric, + node.router_id, + )?; + + if interfaces.insert(interface_name, interface).is_some() { + tracing::warn!( + "An interface cannot be in multiple openfabric fabrics" + ); + } + } + + let access_list_name = AccessListName::new(format!( + "openfabric_{}_ips", + node.node_id.fabric_id + )); + + let rule = AccessListRule { + action: AccessAction::Permit, + network: fabric.loopback_prefix, + seq: None, + }; + + access_lists + .entry(access_list_name.clone()) + .and_modify(|l| l.rules.push(rule.clone())) + .or_insert(AccessList { + name: access_list_name, + rules: vec![rule], + }); + + let routemap = Self::build_openfabric_dummy_routemap( + &node.node_id.fabric_id, + node.router_id, + routemap_seq + )?; + + routemap_seq += 10; + + routemaps.push(routemap); + + let protocol_routemap = ProtocolRouteMap { + protocol: ProtocolType::OpenFabric, + routemap_name: RouteMapName::new("openfabric".to_owned()), + }; + + protocol_routemaps.push(protocol_routemap); + } + } + + if let Some(ospf) = self.fabrics.ospf { + let mut fabrics = HashMap::new(); + let mut local_configuration = Vec::new(); + + for (_, section) in ospf.iter() { + match section { + OspfSectionConfig::Fabric(fabric) => { + fabrics.insert(fabric.area.clone(), fabric); + }, + OspfSectionConfig::Node(node) => { + if node.node_id.node == current_node { + local_configuration.push(node); + } + } + } + } + + for node in local_configuration { + let fabric = fabrics.get(&node.node_id.area) + .ok_or_else(|| anyhow!("could not find fabric: {}", node.node_id.area))?; + + let (router_name, router_item) = + Self::build_ospf_router(&node.node_id.area, node)?; + router.insert(router_name, router_item); + + // Add dummy interface + let (interface, interface_name) = + Self::build_ospf_dummy_interface(&node.node_id.area)?; + + if interfaces.insert(interface_name, interface).is_some() { + tracing::error!( + "An interface with the same name as the dummy interface exists" + ); + } + + for interface in node.interface.iter() { + let (interface, interface_name) = Self::build_ospf_interface( + &node.node_id.area, + interface, + )?; + + if interfaces.insert(interface_name, interface).is_some() { + tracing::warn!( + "An interface cannot be in multiple openfabric fabrics" + ); + } + } + + let access_list_name = AccessListName::new(format!( + "ospf_{}_ips", + node.node_id.area + )); + + let rule = AccessListRule { + action: AccessAction::Permit, + network: fabric.loopback_prefix.into(), + seq: None, + }; + + access_lists + .entry(access_list_name.clone()) + .and_modify(|l| l.rules.push(rule.clone())) + .or_insert(AccessList { + name: access_list_name, + rules: vec![rule], + }); + + let routemap = Self::build_ospf_dummy_routemap( + &node.node_id.area, + node.router_id, + )?; + routemaps.push(routemap); + + let protocol_routemap = ProtocolRouteMap { + protocol: ProtocolType::Ospf, + routemap_name: RouteMapName::new("ospf".to_owned()), + }; + + protocol_routemaps.push(protocol_routemap); + } + } + + Ok(FrrConfig { + router, + interfaces, + access_lists, + routemaps, + protocol_routemaps, + }) + } + + fn build_ospf_router( + area: &ospf::Area, + node_config: &ospf::NodeSection, + ) -> Result<(RouterName, Router), anyhow::Error> { + let ospf_router: proxmox_frr::ospf::OspfRouter = node_config.to_owned().into(); + let router_item = Router::Ospf(ospf_router); + let frr_word_id = FrrWord::new(area.to_string())?; + let router_name = RouterName::Ospf(proxmox_frr::ospf::OspfRouterName::from(Area::new( + frr_word_id, + )?)); + Ok((router_name, router_item)) + } + + fn build_openfabric_router( + fabric_id: &openfabric::FabricId, + net: &Net, + ) -> Result<(RouterName, Router), anyhow::Error> { + let ofr = proxmox_frr::openfabric::OpenFabricRouter { net: net.clone() }; + let router_item = Router::OpenFabric(ofr); + let frr_word_id = FrrWord::new(fabric_id.to_string())?; + let router_name = RouterName::OpenFabric(frr_word_id.into()); + Ok((router_name, router_item)) + } + + fn build_ospf_interface( + area: &ospf::Area, + interface: &ospf::InterfaceProperties, + ) -> Result<(Interface, InterfaceName), anyhow::Error> { + let frr_interface: proxmox_frr::ospf::OspfInterface = interface.to_frr_interface(area)?; + + let interface_name = InterfaceName::Ospf(interface.name.parse()?); + Ok((frr_interface.into(), interface_name)) + } + + fn build_ospf_dummy_interface( + fabric_id: &ospf::Area, + ) -> Result<(Interface, InterfaceName), anyhow::Error> { + let frr_word = FrrWord::new(fabric_id.to_string())?; + let frr_interface = proxmox_frr::ospf::OspfInterface { + area: frr_word.try_into()?, + passive: Some(true), + network_type: None, + }; + let interface_name = InterfaceName::OpenFabric(format!("dummy_{}", fabric_id).parse()?); + Ok((frr_interface.into(), interface_name)) + } + + fn build_openfabric_interface( + fabric_id: &openfabric::FabricId, + interface: &openfabric::InterfaceProperties, + fabric_config: &openfabric::FabricSection, + router_id: IpAddr, + ) -> Result<(Interface, InterfaceName), anyhow::Error> { + let mut frr_interface: proxmox_frr::openfabric::OpenFabricInterface = + interface.to_frr_interface(fabric_id, router_id.is_ipv6())?; + // If no specific hello_interval is set, get default one from fabric + // config + if frr_interface.hello_interval().is_none() { + frr_interface.set_hello_interval(fabric_config.hello_interval); + } + let interface_name = InterfaceName::OpenFabric(interface.name.parse()?); + Ok((frr_interface.into(), interface_name)) + } + + fn build_openfabric_dummy_interface( + fabric_id: &openfabric::FabricId, + router_id: IpAddr, + ) -> Result<(Interface, InterfaceName), anyhow::Error> { + let frr_word = FrrWord::new(fabric_id.to_string())?; + let frr_interface = proxmox_frr::openfabric::OpenFabricInterface { + fabric_id: frr_word.into(), + hello_interval: None, + passive: Some(true), + csnp_interval: None, + hello_multiplier: None, + is_ipv6: router_id.is_ipv6(), + }; + let interface_name = InterfaceName::OpenFabric(format!("dummy_{}", fabric_id).parse()?); + Ok((frr_interface.into(), interface_name)) + } + + fn build_openfabric_dummy_routemap( + fabric_id: &openfabric::FabricId, + router_ip: IpAddr, + seq: u32 + ) -> Result<RouteMap, anyhow::Error> { + let routemap_name = RouteMapName::new("openfabric".to_owned()); + // create route-map + let routemap = RouteMap { + name: routemap_name.clone(), + seq, + action: AccessAction::Permit, + matches: vec![RouteMapMatch::IpAddress(AccessListName::new(format!( + "openfabric_{fabric_id}_ips" + )))], + sets: vec![RouteMapSet::IpSrc(router_ip)], + }; + Ok(routemap) + } + + fn build_ospf_dummy_routemap( + area: &ospf::Area, + router_ip: Ipv4Addr, + ) -> Result<RouteMap, anyhow::Error> { + let routemap_name = RouteMapName::new("ospf".to_owned()); + // create route-map + let routemap = RouteMap { + name: routemap_name.clone(), + seq: 10, + action: AccessAction::Permit, + matches: vec![RouteMapMatch::IpAddress(AccessListName::new(format!( + "ospf_{area}_ips" + )))], + sets: vec![RouteMapSet::IpSrc(IpAddr::from(router_ip))], + }; + + Ok(routemap) + } +} -- 2.39.5 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel