Re: [PATCH access-control/cluster/manager/network/proxmox{-ve-rs,-perl-rs} v5 00/46] Add support for route maps / prefix lists to SDN

Gabriel Goller
2026-05-07T15:18:29Z

On 07.05.2026 13:57, Lukas Sichert wrote:
> On 2026-05-05 17:36, Stefan Hanreich <s.hanreich@proxmox.com> wrote:
>
> > ## Introduction
> >
> > This patch adds support for managing route maps and prefix lists to the SDN
> > stack. With this patch series, route maps can be applied to the BGP and EVPN
> > controller for incoming / outgoing route filtering. Additionally, prefix lists
> > can be used to filter routes that should be installed by a fabric into the
> > kernel routing table, overriding the default behavior of only installing routes
> > from the configured IP prefix. There are currently some other features in
> > development that would make use of route maps as well, namely:
> >
> > * VRF route leaking
> > * Route Redistribution for Fabrics
>
> On my host there are two SimpleZones configured. In the first SimpleZone
> there is a VM, which shall be called tm1 from now on. In the VNet there
> is a cluster consisting of the nodes cl1, cl2 and cl3. All the nodes can
> reach each other. On the cluster I configured an EVPN with an OpenFabric
> underlay, configured cl1 as the exit node and enabled 'Advertise
> Subnets'. In the EVPN Zone I created the 10.0.10.0/24 subnet.
>
> Then on cl1 I added a bgp controller with EBGP and 'ebgp-multihop' set
> to 10 and the ip of tm1 set as peer. On tm1 I also added a bgp
> controller with the same configuration, but with cl1 as peer and a
> different ASN. In both cases I ran into a problem where 'delete' was not
> defined in the schema, but this could be resolved by applying [1].
> EBGP multihop was enabled here, because the traffic between the
> SimpleZones was routed via my host, which did not have a BGP controller
> configured.
>
> I could then verify, that the 10.0.10.0/24 subnet was correctly
> adveritsed from cl1 to tm1. After configuring prefix lists and
> route-maps on both cl1 and tm1 I could verify that both incoming and
> outgoing route-maps work, as the 10.0.10.0/24 was not advertised
> anymore.
>
> Here I ran into a problem though. I configured a prefix list with
> Prefix: 10.0.10.0/22
> Prefix<=:21
> This is of course wrong, but it is not checked, so applying it is
> possible. Here maybe a check in the frontend would be useful.
> After applying I got a warning and even after correcting the prefix list
> the warning did not go away. After deleting and reconfiguring the list
> it worked again, but apparently, when adding Prefix<=:, it is only
> possible to alter the value, not delete it. Deleting will just be
> ignored.

Quick patch that should fix this -- not sure if this still applies on top of
pve-manager after the last api-changes:

diff --git a/www/manager6/sdn/PrefixListPanel.js b/www/manager6/sdn/PrefixListPanel.js
index 8becf83435..bd4ce491dd 100644
--- a/www/manager6/sdn/PrefixListPanel.js
+++ b/www/manager6/sdn/PrefixListPanel.js
@@ -10,7 +10,16 @@

Ext.define('PVE.sdn.PrefixListEntry', {
extend: 'Ext.data.Model',
- fields: ['id', 'action', 'prefix', 'le', 'ge', 'pending'],
+ fields: [
+ 'id',
+ 'action',
+ 'prefix',
+ // keep empty optional fields as '' so clearing a value does not mark
+ // untouched empty fields as dirty when the form returns ''.
+ { name: 'le', defaultValue: '' },
+ { name: 'ge', defaultValue: '' },
+ 'pending',
+ ],
});

Ext.define('PVE.sdn.EditPrefixListWindow', {
@@ -52,6 +61,48 @@

isCreate: false,

+ validatePrefixRange: function(name) {
+ let me = this;
+ let prefix = me.down('[name=prefix]').getValue();
+ let value = me.down(`[name=${name}]`).getValue();
+
+ if (value === '') {
+ return true;
+ }
+
+ let match = Proxmox.Utils.IP64_cidr_match.exec(prefix);
+ if (!match) {
+ return true;
+ }
+
+ // IP64_cidr_match stores the IPv6 prefix length in match[1],
+ // and the IPv4 prefix length in match[2].
+ let isIPv6 = match[1] !== undefined;
+ let maskLength = Number(isIPv6 ? match[1] : match[2]);
+ let maxLength = isIPv6 ? 128 : 32;
+ let label = name === 'le' ? gettext('Prefix <=') : gettext('Prefix >=');
+ let number = Number(value);
+
+ if (!/^\d+$/.test(value) || number < maskLength || number > maxLength) {
+ return Ext.String.format(
+ gettext('{0} must be between {1} and {2}'),
+ label,
+ maskLength,
+ maxLength,
+ );
+ }
+
+ // only report the cross-field difference on the 'ge' input, so a valid
+ // 'le' value does not get marked invalid because of an invalid 'ge'.
+ let le = me.down('[name=le]').getValue();
+ let ge = me.down('[name=ge]').getValue();
+ if (name === 'ge' && le !== '' && Number(ge) > Number(le)) {
+ return gettext('Prefix >= must be less than or equal to Prefix <=');
+ }
+
+ return true;
+ },
+
items: [
{
xtype: 'proxmoxKVComboBox',
@@ -68,16 +119,27 @@
fieldLabel: gettext('Prefix'),
name: 'prefix',
vtype: 'IP64CIDRAddress',
+ allowBlank: false,
},
{
xtype: 'proxmoxtextfield',
fieldLabel: gettext('Prefix <='),
name: 'le',
+ skipEmptyText: false,
+ maskRe: /[0-9]/,
+ validator: function() {
+ return this.up('window').validatePrefixRange('le');
+ },
},
{
xtype: 'proxmoxtextfield',
fieldLabel: gettext('Prefix >='),
name: 'ge',
+ skipEmptyText: false,
+ maskRe: /[0-9]/,
+ validator: function() {
+ return this.up('window').validatePrefixRange('ge');
+ },
},
],

@@ -294,8 +356,10 @@
.getData()
.items
.map((item) => {
- let data = item.data;
+ // Clone record data before dropping grid-only metadata for serialization.
+ let data = Ext.apply({}, item.data);
delete data.id;
+ delete data.pending;

return PVE.Parser.printPropertyString(data);
});

One other thing I noticed is that we don't allow prefixes lower than /8 on ipv4
and lower than /64 on ipv6. This is weird, because in frr 0.0.0.0/0 is usually
used as a "allow-all" prefix. Not sure if we can change the existing input
format or if that will break something (its also used in migration and
replication). Will check that tomorrow.