From: Gabriel Goller <g.goller@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH pve-network 06/10] sdn: adjust frr.conf.local merging to rust template types
Date: Tue, 3 Feb 2026 17:01:24 +0100 [thread overview]
Message-ID: <20260203160246.353351-18-g.goller@proxmox.com> (raw)
In-Reply-To: <20260203160246.353351-1-g.goller@proxmox.com>
The frr object in perl which stores the whole frr config is now also
modeled in rust, so it changed a bit. Adjust the frr.conf.local merging
code so that the frr.conf.local is still merged correctly. This makes
use of the `custom_frr_config` properties scattered in many rust types.
So if we encounter a line in frr.conf.local that we need to merge, we
just throw it into this string vec and render it as-is.
Co-authored-by: Stefan Hanreich <s.hanreich@proxmox.com>
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
src/PVE/Network/SDN/Controllers/EvpnPlugin.pm | 4 +-
src/PVE/Network/SDN/Frr.pm | 204 +++++++++++++++---
2 files changed, 171 insertions(+), 37 deletions(-)
diff --git a/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm b/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm
index 3ea3ce2f033a..1f221807cb7b 100644
--- a/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm
+++ b/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm
@@ -377,10 +377,10 @@ sub generate_zone_frr_config {
my $main_bgp_router = $config->{frr}->{bgp}->{vrf_router}->{'default'};
if ($main_bgp_router) {
$main_bgp_router->{address_families}->{ipv4_unicast} //= {};
- push(@ {$main_bgp_router->{address_families}->{ipv4_unicast}->{import_vrf} }, $vrf);
+ push(@{ $main_bgp_router->{address_families}->{ipv4_unicast}->{import_vrf} }, $vrf);
$main_bgp_router->{address_families}->{ipv6_unicast} //= {};
- push(@ {$main_bgp_router->{address_families}->{ipv6_unicast}->{import_vrf} }, $vrf);
+ push(@{ $main_bgp_router->{address_families}->{ipv6_unicast}->{import_vrf} }, $vrf);
}
# Redistribute connected in VRF router
diff --git a/src/PVE/Network/SDN/Frr.pm b/src/PVE/Network/SDN/Frr.pm
index f084ad5a578f..99002c6b65bf 100644
--- a/src/PVE/Network/SDN/Frr.pm
+++ b/src/PVE/Network/SDN/Frr.pm
@@ -197,7 +197,7 @@ numbers to be consecutive, starting from 1 and incrementing by 1 for each entry.
sub fix_routemap_seqs {
my ($frr_config) = @_;
- my $routemaps = $frr_config->{'frr'}->{'bgp'}->{'routemaps'};
+ my $routemaps = $frr_config->{'frr'}->{'routemaps'};
foreach my $id (sort keys %$routemaps) {
my $routemap = $routemaps->{$id};
@@ -270,70 +270,204 @@ sub append_local_config {
return if !$local_config;
my $section = \$frr_config->{""};
- my $router = undef;
+ my $isis_router_name = undef;
+ my $bgp_router_asn = undef;
+ my $bgp_router_vrf = undef;
my $routemap = undef;
- my $routemap_config = ();
- my $routemap_action = undef;
+ my $interface = undef;
+ my $vrf = undef;
+ my $new_block = 0;
+ my $new_af_block = 0;
- while ($local_config =~ /^\s*(.+?)\s*$/gm) {
+ while ($local_config =~ /^(.+?)\s*$/gm) {
my $line = $1;
- $line =~ s/^\s+|\s+$//g;
-
- if ($line =~ m/^router (.+)$/) {
- $router = $1;
- $section = \$frr_config->{'frr'}->{'router'}->{$router}->{""};
+ $line =~ s/\s+$//g;
+
+ if ($line =~ m/^router isis (.+)$/) {
+ $isis_router_name = $1;
+ if (defined $frr_config->{'frr'}->{'isis'}->{'router'}->{$isis_router_name}) {
+ $section =
+ \($frr_config->{'frr'}->{'isis'}->{'router'}->{$isis_router_name}
+ ->{'custom_frr_config'} //= []);
+ } else {
+ $new_block = 1;
+ push(
+ $frr_config->{'frr'}->{'custom_frr_config'}->@*,
+ "router isis $isis_router_name",
+ );
+ $section = \$frr_config->{'frr'}->{'custom_frr_config'};
+ }
+ next;
+ } elsif ($line =~ m/^router bgp (\S+)(?: vrf (.+))?$/) {
+ $bgp_router_asn = $1;
+ $bgp_router_vrf = $2 // 'default';
+
+ my $config_line =
+ defined($2)
+ ? "router bgp $bgp_router_asn vrf $bgp_router_vrf"
+ : "router bgp $bgp_router_asn";
+
+ if (
+ defined $frr_config->{'frr'}->{'bgp'}->{'vrf_router'}->{$bgp_router_vrf}
+ and $frr_config->{'frr'}->{'bgp'}->{'vrf_router'}->{$bgp_router_vrf}->{'asn'}
+ eq $bgp_router_asn
+ ) {
+ $section =
+ \($frr_config->{'frr'}->{'bgp'}->{'vrf_router'}->{$bgp_router_vrf}
+ ->{'custom_frr_config'} //= []);
+ } else {
+ $new_block = 1;
+ push(
+ $frr_config->{'frr'}->{'custom_frr_config'}->@*, $config_line,
+ );
+ $section = \$frr_config->{'frr'}->{'custom_frr_config'};
+ }
next;
} elsif ($line =~ m/^vrf (.+)$/) {
- $section = \$frr_config->{'frr'}->{'vrf'}->{$1};
+ $vrf = $1;
+ if (defined $frr_config->{'frr'}->{'bgp'}->{'vrfs'}->{$vrf}) {
+ $section = \$frr_config->{'frr'}->{'bgp'}->{'vrfs'}->{$vrf}->{'custom_frr_config'};
+ } else {
+ $new_block = 1;
+ push($frr_config->{'frr'}->{'custom_frr_config'}->@*, "vrf $vrf");
+ $section = \$frr_config->{'frr'}->{'custom_frr_config'};
+ }
next;
} elsif ($line =~ m/^interface (.+)$/) {
- $section = \$frr_config->{'frr_interfaces'}->{$1};
+ $interface = $1;
+ if (defined $frr_config->{'frr'}->{'isis'}->{'interfaces'}->{$interface}) {
+ $section = \($frr_config->{'frr'}->{'isis'}->{'interfaces'}->{$interface}
+ ->{'custom_frr_config'} //= []);
+ } else {
+ $new_block = 1;
+ push(
+ $frr_config->{'frr'}->{'custom_frr_config'}->@*, "interface $interface",
+ );
+ $section = \$frr_config->{'frr'}->{'custom_frr_config'};
+ }
next;
} elsif ($line =~ m/^bgp community-list (.+)$/) {
- push(@{ $frr_config->{'frr_bgp_community_list'} }, $line);
+ push(@{ $frr_config->{'frr'}->{'custom_frr_config'} }, $line);
next;
} elsif ($line =~ m/address-family (.+)$/) {
- $section = \$frr_config->{'frr'}->{'router'}->{$router}->{'address-family'}->{$1};
+ # convert the address family from frr (e.g. l2vpn evpn) into the rust property (e.g. l2vpn_evpn)
+ my $address_family_unchanged = $1;
+ my $address_family = $1 =~ s/ /_/gr;
+
+ if (
+ defined $frr_config->{'frr'}->{'bgp'}->{'vrf_router'}->{$bgp_router_vrf}
+ and $frr_config->{'frr'}->{'bgp'}->{'vrf_router'}->{$bgp_router_vrf}->{'asn'}
+ eq $bgp_router_asn
+ ) {
+ if (
+ defined $frr_config->{'frr'}->{'bgp'}->{'vrf_router'}->{$bgp_router_vrf}
+ ->{'address_families'}->{$address_family}
+ ) {
+ $section =
+ \($frr_config->{'frr'}->{'bgp'}->{'vrf_router'}->{$bgp_router_vrf}
+ ->{'address_families'}->{$address_family}->{custom_frr_config} //= []);
+ } else {
+ $new_af_block = 1;
+ push(
+ $frr_config->{'frr'}->{'bgp'}->{'vrf_router'}->{$bgp_router_vrf}
+ ->{'custom_frr_config'}->@*,
+ " address-family $address_family_unchanged",
+ );
+ $section = \$frr_config->{'frr'}->{'bgp'}->{'vrf_router'}->{$bgp_router_vrf}
+ ->{'custom_frr_config'};
+ }
+ } else {
+ $new_af_block = 1;
+ push(
+ $frr_config->{'frr'}->{'custom_frr_config'}->@*,
+ " address-family $address_family_unchanged",
+ );
+ $section = \$frr_config->{'frr'}->{'custom_frr_config'};
+ }
next;
} elsif ($line =~ m/^route-map (.+) (permit|deny) (\d+)/) {
$routemap = $1;
- $routemap_config = ();
- $routemap_action = $2;
- $section = \$frr_config->{'frr_routemap'}->{$routemap};
+ my $routemap_action = $2;
+ my $seq_number = $3;
+ if (defined $frr_config->{'frr'}->{'routemaps'}->{$routemap}) {
+ my $index = 0;
+ foreach my $single_routemap ($frr_config->{'frr'}->{'routemaps'}->{$routemap}->@*) {
+ if (
+ $single_routemap->{'seq'} == $seq_number
+ && $single_routemap->{'action'} eq $routemap_action
+ ) {
+ last;
+ }
+ $index++;
+ }
+ if ($index < scalar @{ $frr_config->{'frr'}->{'routemaps'}->{$routemap} }) {
+ $section = \($frr_config->{'frr'}->{'routemaps'}->{$routemap}->[$index]
+ ->{'custom_frr_config'} //= []);
+ } else {
+ $new_block = 1;
+ push(
+ $frr_config->{'frr'}->{'custom_frr_config'}->@*,
+ "route-map $routemap $routemap_action $seq_number",
+ );
+ $section = \$frr_config->{'frr'}->{'custom_frr_config'};
+ }
+ } else {
+ $new_block = 1;
+ push(
+ $frr_config->{'frr'}->{'custom_frr_config'}->@*,
+ "route-map $routemap $routemap_action $seq_number",
+ );
+ $section = \$frr_config->{'frr'}->{'custom_frr_config'};
+ }
next;
} elsif ($line =~ m/^access-list (.+) seq (\d+) (.+)$/) {
- $frr_config->{'frr_access_list'}->{$1}->{$2} = $3;
+ push($frr_config->{'frr'}->{'custom_frr_config'}->@*, $line);
next;
} elsif ($line =~ m/^ip prefix-list (.+) seq (\d+) (.*)$/) {
- $frr_config->{'frr_prefix_list'}->{$1}->{$2} = $3;
+ push($frr_config->{'frr'}->{'custom_frr_config'}->@*, $line);
next;
} elsif ($line =~ m/^ipv6 prefix-list (.+) seq (\d+) (.*)$/) {
- $frr_config->{'frr_prefix_list_v6'}->{$1}->{$2} = $3;
+ push($frr_config->{'frr'}->{'custom_frr_config'}->@*, $line);
next;
- } elsif ($line =~ m/^exit-address-family$/) {
+ } elsif ($line =~ m/exit-address-family$/) {
+ if ($new_af_block) {
+ push(@{$$section}, $line);
+ $section = \$frr_config->{'frr'}->{'bgp'}->{'custom_frr_config'};
+ } else {
+ $section =
+ \($frr_config->{'frr'}->{'bgp'}->{'vrf_router'}->{$bgp_router_vrf}
+ ->{'custom_frr_config'} //= []);
+ }
+ $new_af_block = 0;
next;
- } elsif ($line =~ m/^exit$/) {
- if ($router) {
- $section = \$frr_config->{''};
- $router = undef;
- } elsif ($routemap) {
- push(@{$$section}, { rule => $routemap_config, action => $routemap_action });
- $section = \$frr_config->{''};
+ } elsif ($line =~ m/^exit/) {
+ if ($bgp_router_vrf || $vrf || $interface || $routemap || $isis_router_name) {
+ # this means we just added a new router/vrf/interface/routemap
+ if ($new_block) {
+ push(@{$$section}, $line);
+ push(@{$$section}, "!");
+ }
+ $section = \$frr_config->{'frr'}->{'custom_frr_config'};
+ # we can't stack these, so exit out of all of them (technically we can have a vrf inside of a router bgp block, but we don't support that)
+ $isis_router_name = undef;
+ $bgp_router_vrf = undef;
+ $bgp_router_asn = undef;
+ $vrf = undef;
+ $interface = undef;
$routemap = undef;
- $routemap_action = undef;
- $routemap_config = ();
+ } else {
+ $section = \$frr_config->{'frr'}->{'custom_frr_config'};
+ push(@{$$section}, $line);
+ push(@{$$section}, "!");
}
+ $new_block = 0;
next;
} elsif ($line =~ m/!/) {
next;
}
next if !$section;
- if ($routemap) {
- push(@{$routemap_config}, $line);
- } else {
- push(@{$$section}, $line);
- }
+ push(@{$$section}, $line);
}
}
--
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 ` [PATCH proxmox-ve-rs 8/9] frr: add bgp support with templates and serialization Gabriel Goller
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 ` Gabriel Goller [this message]
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-18-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