public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Gabriel Goller <g.goller@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH proxmox-ve-rs 8/9] frr: add bgp support with templates and serialization
Date: Tue,  3 Feb 2026 17:01:15 +0100	[thread overview]
Message-ID: <20260203160246.353351-9-g.goller@proxmox.com> (raw)
In-Reply-To: <20260203160246.353351-1-g.goller@proxmox.com>

Implements bgp routing configuration (rust types and templates)
including routers, neighbor groups, address families (IPv4/IPv6 unicast,
L2VPN EVPN), VRFs, route redistribution, and prefix lists.

Co-authored-by: Stefan Hanreich <s.hanreich@proxmox.com>
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
 .../templates/bgp_router.jinja                | 118 +++++++++++
 proxmox-frr-templates/templates/bgpd.jinja    |  35 ++++
 .../templates/frr.conf.jinja                  |   3 +
 .../templates/ip_routes.jinja                 |   8 +
 .../templates/prefix_lists.jinja              |   6 +
 proxmox-frr/src/ser/bgp.rs                    | 184 ++++++++++++++++++
 proxmox-frr/src/ser/mod.rs                    |  34 +++-
 proxmox-frr/src/ser/serializer.rs             |  12 ++
 8 files changed, 399 insertions(+), 1 deletion(-)
 create mode 100644 proxmox-frr-templates/templates/bgp_router.jinja
 create mode 100644 proxmox-frr-templates/templates/bgpd.jinja
 create mode 100644 proxmox-frr-templates/templates/ip_routes.jinja
 create mode 100644 proxmox-frr-templates/templates/prefix_lists.jinja
 create mode 100644 proxmox-frr/src/ser/bgp.rs

diff --git a/proxmox-frr-templates/templates/bgp_router.jinja b/proxmox-frr-templates/templates/bgp_router.jinja
new file mode 100644
index 000000000000..6f48d6ca17a4
--- /dev/null
+++ b/proxmox-frr-templates/templates/bgp_router.jinja
@@ -0,0 +1,118 @@
+{% macro address_family_common(common_address_family) -%}
+{% for vrf in common_address_family.import_vrf %}
+  import vrf {{ vrf }}
+{% endfor %}
+{% for neighbor in common_address_family.neighbors %}
+  neighbor {{ neighbor.name }} activate
+  {% if neighbor.soft_reconfiguration_inbound %}
+  neighbor {{ neighbor.name }} soft-reconfiguration inbound
+  {% endif %}
+  {% if neighbor.route_map_in %}
+  neighbor {{ neighbor.name }} route-map {{ neighbor.route_map_in }} in
+  {% endif %}
+  {% if neighbor.route_map_out %}
+  neighbor {{ neighbor.name }} route-map {{ neighbor.route_map_out }} out
+  {% endif %}
+{% endfor -%}
+{% for line in common_address_family.custom_frr_config %}
+{{ line }}
+{% endfor -%}
+{% endmacro -%}
+{% macro bgp_router(router_config) %}
+ bgp router-id {{ router_config.router_id }}
+ no bgp hard-administrative-reset
+{% if router_config.default_ipv4_unicast == false %}
+ no bgp default ipv4-unicast
+{% endif %}
+{% if router_config.coalesce_time %}
+ coalesce-time {{ router_config.coalesce_time }}
+{% endif %}
+ no bgp graceful-restart notification
+{% if router_config.disable_ebgp_connected_route_check %}
+ bgp disable-ebgp-connected-route-check
+{% endif %}
+{% if router_config.bestpath_as_path_multipath_relax %}
+ bgp bestpath as-path multipath-relax
+{% endif %}
+{% for neighbor_group in router_config.neighbor_groups %}
+ neighbor {{ neighbor_group.name }} peer-group
+ neighbor {{ neighbor_group.name }} remote-as {{ neighbor_group.remote_as }}
+{% if neighbor_group.bfd %}
+ neighbor {{ neighbor_group.name }} bfd
+{% endif %}
+{% if neighbor_group.ebgp_multihop %}
+ neighbor {{ neighbor_group.name }} ebgp-multihop {{ neighbor_group.ebgp_multihop }}
+{% endif %}
+{% if neighbor_group.update_source %}
+ neighbor {{ neighbor_group.name }} update-source {{ neighbor_group.update_source }}
+{% endif %}
+{% for ip in neighbor_group.ips %}
+ neighbor {{ ip }} peer-group {{ neighbor_group.name }}
+{% endfor %}
+{% for interface in neighbor_group.interfaces %}
+ neighbor {{ interface }} interface peer-group {{ neighbor_group.name }}
+{% endfor %}
+{% endfor %}
+{% for line in router_config.custom_frr_config %}
+{{ line }}
+{% endfor %}
+{% if router_config.address_families.ipv4_unicast %}
+ !
+ address-family ipv4 unicast
+{% for network in router_config.address_families.ipv4_unicast.networks %}
+  network {{ network }}
+{% endfor %}
+{{ address_family_common(router_config.address_families.ipv4_unicast) -}}
+{% for redistribute in router_config.address_families.ipv4_unicast.redistribute %}
+  redistribute {{ redistribute.protocol }}{{ (" metric " ~ redistribute.metric) if redistribute.metric }}{{ (" route-map " ~ redistribute.route_map) if redistribute.route_map }}
+{% endfor %}
+ exit-address-family
+{% endif %}
+{% if router_config.address_families.ipv6_unicast %}
+ !
+ address-family ipv6 unicast
+{% for network in router_config.address_families.ipv6_unicast.networks %}
+  network {{ network }}
+{% endfor %}
+{{ address_family_common(router_config.address_families.ipv6_unicast) -}}
+{% for redistribute in router_config.address_families.ipv6_unicast.redistribute %}
+  redistribute {{ redistribute.protocol }}{{ (" metric " ~ redistribute.metric) if redistribute.metric }}{{ (" route-map " ~ redistribute.route_map) if redistribute.route_map }}
+{% endfor %}
+ exit-address-family
+{% endif %}
+{% if router_config.address_families.l2vpn_evpn %}
+ !
+ address-family l2vpn evpn
+{{ address_family_common(router_config.address_families.l2vpn_evpn) -}}
+{% if router_config.address_families.l2vpn_evpn.advertise_all_vni %}
+  advertise-all-vni
+{% endif %}
+{% if router_config.address_families.l2vpn_evpn.advertise_default_gw %}
+  advertise-default-gw
+{% endif %}
+{% if router_config.address_families.l2vpn_evpn.autort_as %}
+  autort as {{ router_config.address_families.l2vpn_evpn.autort_as }}
+{% endif %}
+{% for default_originate in router_config.address_families.l2vpn_evpn.default_originate %}
+  default-originate {{ default_originate }}
+{% endfor %}
+{% if router_config.address_families.l2vpn_evpn.advertise_ipv4_unicast %}
+  advertise ipv4 unicast
+{% endif %}
+{% if router_config.address_families.l2vpn_evpn.advertise_ipv6_unicast %}
+  advertise ipv6 unicast
+{% endif %}
+{% if router_config.address_families.l2vpn_evpn.route_targets %}
+{% for import in router_config.address_families.l2vpn_evpn.route_targets.import %}
+  route-target import {{ import }}
+{% endfor %}
+{% for export in router_config.address_families.l2vpn_evpn.route_targets.export %}
+  route-target export {{ export }}
+{% endfor %}
+{% for both in router_config.address_families.l2vpn_evpn.route_targets.both %}
+  route-target both {{ both }}
+{% endfor %}
+{% endif %}
+ exit-address-family
+{% endif %}
+{% endmacro -%}
diff --git a/proxmox-frr-templates/templates/bgpd.jinja b/proxmox-frr-templates/templates/bgpd.jinja
new file mode 100644
index 000000000000..cdd0a50abf8c
--- /dev/null
+++ b/proxmox-frr-templates/templates/bgpd.jinja
@@ -0,0 +1,35 @@
+{% from "bgp_router.jinja" import bgp_router %}
+{% for vrf_name, vrf in bgp.vrfs|items %}
+!
+vrf {{ vrf_name }}
+{% if vrf.vni %}
+ vni {{ vrf.vni }}
+{% for ip_route in vrf.ip_routes %}
+{% if ip_route.vrf %}
+ {{ "ipv6" if ip_route.is_ipv6 else "ip" }} route {{ ip_route.prefix }} {{ ip_route.via }} {{ ip_route.vrf }}
+{% else %}
+ {{ "ipv6" if ip_route.is_ipv6 else "ip" }} route {{ ip_route.prefix }} {{ ip_route.via }}
+{% endif %}
+{% endfor %}
+{% endif %}
+{% for line in vrf.custom_frr_config %}
+{{ line }}
+{% endfor %}
+exit-vrf
+{% endfor %}
+{% for vrf_name, router_config in bgp.vrf_router|items %}
+!
+{% if vrf_name == "default" %}
+router bgp {{ router_config.asn }}
+{% else %}
+router bgp {{ router_config.asn }} vrf {{ vrf_name }}
+{% endif %}
+{{ bgp_router(router_config) -}}
+exit
+{% endfor %}
+{% for view_id, router_config in bgp.view_router|items %}
+!
+router bgp {{ router_config.asn }} view {{ view_id }}
+{{ bgp_router(router_config) -}}
+exit
+{% endfor %}
diff --git a/proxmox-frr-templates/templates/frr.conf.jinja b/proxmox-frr-templates/templates/frr.conf.jinja
index 6d60ad2a4c4c..f9ca85890710 100644
--- a/proxmox-frr-templates/templates/frr.conf.jinja
+++ b/proxmox-frr-templates/templates/frr.conf.jinja
@@ -1,8 +1,11 @@
+{% include "bgpd.jinja" %}
 {% include "fabricd.jinja" %}
 {% include "isisd.jinja" %}
 {% include "ospfd.jinja" %}
 {% include "access_lists.jinja" %}
+{% include "prefix_lists.jinja" %}
 {% include "route_maps.jinja" %}
+{% include "ip_routes.jinja" %}
 {% include "protocol_routemaps.jinja" %}
 {% for line in custom_frr_config %}
 {{ line }}
diff --git a/proxmox-frr-templates/templates/ip_routes.jinja b/proxmox-frr-templates/templates/ip_routes.jinja
new file mode 100644
index 000000000000..3e33a709e821
--- /dev/null
+++ b/proxmox-frr-templates/templates/ip_routes.jinja
@@ -0,0 +1,8 @@
+{% for ip_route in ip_routes %}
+!
+{% if ip_route.vrf %}
+{{ "ipv6" if ip_route.is_ipv6 else "ip" }} route {{ ip_route.prefix }} {{ ip_route.via }} {{ ip_route.vrf }}
+{% else %}
+{{ "ipv6" if ip_route.is_ipv6 else "ip" }} route {{ ip_route.prefix }} {{ ip_route.via }}
+{% endif %}
+{% endfor %}
diff --git a/proxmox-frr-templates/templates/prefix_lists.jinja b/proxmox-frr-templates/templates/prefix_lists.jinja
new file mode 100644
index 000000000000..f431958af354
--- /dev/null
+++ b/proxmox-frr-templates/templates/prefix_lists.jinja
@@ -0,0 +1,6 @@
+{% for name, prefix_list in prefix_lists | items %}
+!
+{%  for rule in prefix_list %}
+{{ "ipv6" if rule.is_ipv6 else "ip" }} prefix-list {{ name }} {{ ("seq " ~ rule.seq ~ " ") if rule.seq }}{{ rule.action }} {{ rule.network }}{{ (" le " ~ rule.le) if rule.le }}{{ (" ge " ~ rule.ge) if rule.ge }}
+{% endfor %}
+{% endfor %}
diff --git a/proxmox-frr/src/ser/bgp.rs b/proxmox-frr/src/ser/bgp.rs
new file mode 100644
index 000000000000..3ac014686339
--- /dev/null
+++ b/proxmox-frr/src/ser/bgp.rs
@@ -0,0 +1,184 @@
+use std::net::{IpAddr, Ipv4Addr};
+
+use bon::Builder;
+use proxmox_network_types::ip_address::{Ipv4Cidr, Ipv6Cidr};
+use serde::{Deserialize, Serialize};
+
+use crate::ser::route_map::RouteMapName;
+use crate::ser::{FrrWord, InterfaceName, IpRoute};
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
+pub struct BgpRouterName {
+    asn: u32,
+    vrf: Option<FrrWord>,
+}
+
+impl BgpRouterName {
+    pub fn new(asn: u32, vrf: Option<FrrWord>) -> Self {
+        Self { asn, vrf }
+    }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
+#[serde(rename_all = "lowercase")]
+pub enum NeighborRemoteAs {
+    Internal,
+    External,
+    #[serde(untagged)]
+    Asn(#[serde(deserialize_with = "proxmox_serde::perl::deserialize_u32")] u32),
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Builder, Deserialize)]
+pub struct NeighborGroup {
+    pub name: FrrWord,
+    #[serde(deserialize_with = "proxmox_serde::perl::deserialize_bool")]
+    pub bfd: bool,
+    pub local_as: Option<u32>,
+    pub remote_as: NeighborRemoteAs,
+    #[serde(default)]
+    pub ips: Vec<IpAddr>,
+    #[serde(default)]
+    pub interfaces: Vec<InterfaceName>,
+    pub ebgp_multihop: Option<i32>,
+    pub update_source: Option<InterfaceName>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Builder, Deserialize)]
+pub struct Ipv4UnicastAF {
+    #[serde(flatten)]
+    pub common_options: CommonAddressFamilyOptions,
+    #[serde(default)]
+    pub networks: Vec<Ipv4Cidr>,
+    #[serde(default)]
+    pub redistribute: Vec<Redistribution>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Builder, Deserialize)]
+pub struct Ipv6UnicastAF {
+    #[serde(flatten)]
+    pub common_options: CommonAddressFamilyOptions,
+    #[serde(default)]
+    pub networks: Vec<Ipv6Cidr>,
+    #[serde(default)]
+    pub redistribute: Vec<Redistribution>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Builder, Deserialize)]
+pub struct L2vpnEvpnAF {
+    #[serde(flatten)]
+    pub common_options: CommonAddressFamilyOptions,
+    #[serde(default, deserialize_with = "proxmox_serde::perl::deserialize_bool")]
+    pub advertise_all_vni: Option<bool>,
+    #[serde(default, deserialize_with = "proxmox_serde::perl::deserialize_bool")]
+    pub advertise_default_gw: Option<bool>,
+    #[serde(default)]
+    pub default_originate: Vec<DefaultOriginate>,
+    #[serde(default, deserialize_with = "proxmox_serde::perl::deserialize_bool")]
+    pub advertise_ipv4_unicast: Option<bool>,
+    #[serde(default, deserialize_with = "proxmox_serde::perl::deserialize_bool")]
+    pub advertise_ipv6_unicast: Option<bool>,
+    pub autort_as: Option<i32>,
+    pub route_targets: Option<RouteTargets>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
+#[serde(rename_all = "lowercase")]
+pub enum DefaultOriginate {
+    Ipv4,
+    Ipv6,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
+#[serde(rename_all = "lowercase")]
+pub enum RedistributeProtocol {
+    Connected,
+    Static,
+    Ospf,
+    Kernel,
+    Isis,
+    Ospf6,
+    Openfabric,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Builder, Deserialize)]
+pub struct Redistribution {
+    pub protocol: RedistributeProtocol,
+    pub metric: Option<u32>,
+    pub route_map: Option<RouteMapName>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Builder, Deserialize)]
+pub struct RouteTargets {
+    #[serde(default)]
+    import: Vec<FrrWord>,
+    #[serde(default)]
+    export: Vec<FrrWord>,
+    #[serde(default)]
+    both: Vec<FrrWord>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Builder, Deserialize)]
+pub struct AddressFamilyNeighbor {
+    pub name: String,
+    #[serde(
+        default,
+        skip_serializing_if = "Option::is_none",
+        deserialize_with = "proxmox_serde::perl::deserialize_bool"
+    )]
+    pub soft_reconfiguration_inbound: Option<bool>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub route_map_in: Option<RouteMapName>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub route_map_out: Option<RouteMapName>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Builder, Deserialize)]
+pub struct CommonAddressFamilyOptions {
+    #[serde(default)]
+    pub import_vrf: Vec<FrrWord>,
+    #[serde(default)]
+    pub neighbors: Vec<AddressFamilyNeighbor>,
+    #[serde(default)]
+    pub custom_frr_config: Vec<String>,
+}
+
+#[derive(
+    Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Builder, Deserialize, Default,
+)]
+pub struct AddressFamilies {
+    #[serde(skip_serializing_if = "Option::is_none")]
+    ipv4_unicast: Option<Ipv4UnicastAF>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    ipv6_unicast: Option<Ipv6UnicastAF>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    l2vpn_evpn: Option<L2vpnEvpnAF>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Builder, Deserialize)]
+pub struct Vrf {
+    pub vni: Option<u32>,
+    #[serde(default)]
+    pub ip_routes: Vec<IpRoute>,
+    #[serde(default)]
+    pub custom_frr_config: Vec<String>,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Builder, Deserialize)]
+pub struct BgpRouter {
+    pub asn: u32,
+    pub router_id: Ipv4Addr,
+    #[serde(default)]
+    pub coalesce_time: Option<u32>,
+    #[serde(default, deserialize_with = "proxmox_serde::perl::deserialize_bool")]
+    pub default_ipv4_unicast: Option<bool>,
+    #[serde(default, deserialize_with = "proxmox_serde::perl::deserialize_bool")]
+    pub disable_ebgp_connected_route_check: Option<bool>,
+    #[serde(default, deserialize_with = "proxmox_serde::perl::deserialize_bool")]
+    pub bestpath_as_path_multipath_relax: Option<bool>,
+    #[serde(default)]
+    pub neighbor_groups: Vec<NeighborGroup>,
+    #[serde(default)]
+    pub address_families: AddressFamilies,
+    #[serde(default)]
+    pub custom_frr_config: Vec<String>,
+}
diff --git a/proxmox-frr/src/ser/mod.rs b/proxmox-frr/src/ser/mod.rs
index 3baa0a318fb0..f3578ef1323a 100644
--- a/proxmox-frr/src/ser/mod.rs
+++ b/proxmox-frr/src/ser/mod.rs
@@ -1,3 +1,4 @@
+pub mod bgp;
 pub mod isis;
 pub mod openfabric;
 pub mod ospf;
@@ -8,7 +9,9 @@ use std::collections::BTreeMap;
 use std::net::IpAddr;
 use std::str::FromStr;
 
-use crate::ser::route_map::{AccessListName, AccessListRule, RouteMapEntry, RouteMapName};
+use crate::ser::route_map::{
+    AccessListName, AccessListRule, PrefixListName, PrefixListRule, RouteMapEntry, RouteMapName,
+};
 
 use bon::Builder;
 use proxmox_network_types::ip_address::Cidr;
@@ -159,6 +162,14 @@ pub struct IpProtocolRouteMap {
     pub v6: Option<RouteMapName>,
 }
 
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
+pub enum VrfName {
+    #[serde(rename = "default")]
+    Default,
+    #[serde(untagged)]
+    Custom(String),
+}
+
 /// Main FRR config.
 ///
 /// Contains the two main frr building blocks: routers and interfaces. It also holds other
@@ -174,8 +185,14 @@ pub struct FrrConfig {
     pub ospf: OspfFrrConfig,
     #[builder(default)]
     #[serde(default)]
+    pub bgp: BgpFrrConfig,
+    #[builder(default)]
+    #[serde(default)]
     pub isis: IsisFrrConfig,
 
+    #[builder(default)]
+    #[serde(default)]
+    pub ip_routes: Vec<IpRoute>,
     #[builder(default)]
     #[serde(default)]
     pub protocol_routemaps: BTreeMap<FrrProtocol, IpProtocolRouteMap>,
@@ -185,6 +202,10 @@ pub struct FrrConfig {
     #[builder(default)]
     #[serde(default)]
     pub access_lists: BTreeMap<AccessListName, Vec<AccessListRule>>,
+    #[builder(default)]
+    #[serde(default)]
+    pub prefix_lists: BTreeMap<PrefixListName, Vec<PrefixListRule>>,
+
     #[builder(default)]
     #[serde(default)]
     pub custom_frr_config: Vec<String>,
@@ -213,3 +234,14 @@ pub struct OspfFrrConfig {
     #[serde(default)]
     pub interfaces: BTreeMap<InterfaceName, Interface<ospf::OspfInterface>>,
 }
+
+#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize)]
+pub struct BgpFrrConfig {
+    #[serde(default)]
+    pub vrf_router: BTreeMap<VrfName, bgp::BgpRouter>,
+    #[serde(default)]
+    pub view_router: BTreeMap<u32, bgp::BgpRouter>,
+
+    #[serde(default)]
+    pub vrfs: BTreeMap<InterfaceName, bgp::Vrf>,
+}
diff --git a/proxmox-frr/src/ser/serializer.rs b/proxmox-frr/src/ser/serializer.rs
index 646b81ab6044..12e4190744eb 100644
--- a/proxmox-frr/src/ser/serializer.rs
+++ b/proxmox-frr/src/ser/serializer.rs
@@ -22,21 +22,33 @@ fn create_env<'a>() -> Environment<'a> {
                 "fabricd.jinja" => Ok(Some(
                     include_str!("/usr/share/proxmox-frr/templates/fabricd.jinja").to_owned(),
                 )),
+                "bgpd.jinja" => Ok(Some(
+                    include_str!("/usr/share/proxmox-frr/templates/bgpd.jinja").to_owned(),
+                )),
                 "isisd.jinja" => Ok(Some(
                     include_str!("/usr/share/proxmox-frr/templates/isisd.jinja").to_owned(),
                 )),
                 "ospfd.jinja" => Ok(Some(
                     include_str!("/usr/share/proxmox-frr/templates/ospfd.jinja").to_owned(),
                 )),
+                "bgp_router.jinja" => Ok(Some(
+                    include_str!("/usr/share/proxmox-frr/templates/bgp_router.jinja").to_owned(),
+                )),
                 "interface.jinja" => Ok(Some(
                     include_str!("/usr/share/proxmox-frr/templates/interface.jinja").to_owned(),
                 )),
                 "access_lists.jinja" => Ok(Some(
                     include_str!("/usr/share/proxmox-frr/templates/access_lists.jinja").to_owned(),
                 )),
+                "prefix_lists.jinja" => Ok(Some(
+                    include_str!("/usr/share/proxmox-frr/templates/prefix_lists.jinja").to_owned(),
+                )),
                 "route_maps.jinja" => Ok(Some(
                     include_str!("/usr/share/proxmox-frr/templates/route_maps.jinja").to_owned(),
                 )),
+                "ip_routes.jinja" => Ok(Some(
+                    include_str!("/usr/share/proxmox-frr/templates/ip_routes.jinja").to_owned(),
+                )),
                 "protocol_routemaps.jinja" => Ok(Some(
                     include_str!("/usr/share/proxmox-frr/templates/protocol_routemaps.jinja")
                         .to_owned(),
-- 
2.47.3





  parent reply	other threads:[~2026-02-03 16:05 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-02-03 16:01 [PATCH docs/manager/network/proxmox{-ve-rs,-perl-rs} 00/23] Generate frr config using jinja templates and rust types Gabriel Goller
2026-02-03 16:01 ` [PATCH proxmox-ve-rs 1/9] ve-config: firewall: cargo fmt Gabriel Goller
2026-02-03 16:01 ` [PATCH proxmox-ve-rs 2/9] frr: add proxmox-frr-templates package that contains templates Gabriel Goller
2026-02-03 16:01 ` [PATCH proxmox-ve-rs 3/9] ve-config: remove FrrConfigBuilder struct Gabriel Goller
2026-02-03 16:01 ` [PATCH proxmox-ve-rs 4/9] sdn-types: support variable-length NET identifier Gabriel Goller
2026-02-03 16:01 ` [PATCH proxmox-ve-rs 5/9] frr: add template serializer and serialize fabrics using templates Gabriel Goller
2026-02-03 16:01 ` [PATCH proxmox-ve-rs 6/9] frr: add isis configuration and templates Gabriel Goller
2026-02-03 16:01 ` [PATCH proxmox-ve-rs 7/9] frr: support custom frr configuration lines Gabriel Goller
2026-02-03 16:01 ` Gabriel Goller [this message]
2026-02-03 16:01 ` [PATCH proxmox-ve-rs 9/9] frr: store frr template content as a const map Gabriel Goller
2026-02-03 16:01 ` [PATCH proxmox-perl-rs 1/2] sdn: add function to generate the frr config for all daemons Gabriel Goller
2026-02-03 16:01 ` [PATCH proxmox-perl-rs 2/2] sdn: add method to get a frr template Gabriel Goller
2026-02-03 16:01 ` [PATCH pve-network 01/10] sdn: remove duplicate comment line '!' in frr config Gabriel Goller
2026-02-03 16:01 ` [PATCH pve-network 02/10] sdn: tests: add missing comment " Gabriel Goller
2026-02-03 16:01 ` [PATCH pve-network 03/10] tests: use Test::Differences to make test assertions Gabriel Goller
2026-02-03 16:01 ` [PATCH pve-network 04/10] sdn: write structured frr config that can be rendered using templates Gabriel Goller
2026-02-03 16:01 ` [PATCH pve-network 05/10] tests: rearrange some statements in the frr config Gabriel Goller
2026-02-03 16:01 ` [PATCH pve-network 06/10] sdn: adjust frr.conf.local merging to rust template types Gabriel Goller
2026-02-03 16:01 ` [PATCH pve-network 07/10] cli: add pvesdn cli tool for managing frr template overrides Gabriel Goller
2026-02-03 16:01 ` [PATCH pve-network 08/10] debian: handle user modifications to FRR templates via ucf Gabriel Goller
2026-02-03 16:01 ` [PATCH pve-network 09/10] api: add dry-run endpoint for sdn apply to preview changes Gabriel Goller
2026-02-03 16:01 ` [PATCH pve-network 10/10] test: add test for frr.conf.local merging Gabriel Goller
2026-02-03 16:01 ` [PATCH pve-manager 1/1] sdn: add dry-run view for sdn apply Gabriel Goller
2026-02-03 16:01 ` [PATCH pve-docs 1/1] docs: add man page for the `pvesdn` cli Gabriel Goller

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260203160246.353351-9-g.goller@proxmox.com \
    --to=g.goller@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal