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
next prev 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