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

Stefan Hanreich
2026-05-05T15:42:28Z

## 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

Prefix Lists can also be used for matching inside route map match statements.
FRR would also provide access lists, which provide a subset of the functionality
of prefix lists. For that reason, we decided to omit access lists for now, since
everything can be modeled with prefix lists as well.

Ran this against the e2e test suite, with all tests passing!

## Motivation

There are a lot of use-cases for enabling users to create their own route-maps,
which was currently only possible by utilizing frr.conf.local - which was clunky
and prone to issues. Route maps can be used for filtering in/outgoing routes and
modify them, so users could e.g. only selectively advertise some routes via BGP
or only import certain EVPN routes from outside.

It also allows us to programmatically manage route maps and prefix lists via the
API/UI, e.g. for deeper EVPN integration in PDM. This opens up a lot of
possibilities for new features.

## Configuration Format

This patch series adds two new configuration files, route-maps.cfg and
prefix-lists.cfg in /etc/pve/sdn.

### route-maps.cfg

An example route map configuration looks as follows:

route-map-entry: example_123
action permit
match key=vni,value=23487
set key=tag,value=23487

This would create the following FRR route map entry:

route-map example permit 123
match evpn vni 23487
set tag 23487

Every entry in route-maps.cfg maps to an entry in a route map. They are
identified by their name as well as their ordering number. `example_123`
specifies the 123th entry in the route map 'example'. The main reason for
choosing this format is, that having a single section for one route-map would be
quite unwieldy. It'd require some format like this, which is pretty awkward to
handle / validate:

route-map-entry: example
action permit,seq=123
match key=vni,value=23487,seq=123
set key=tag,value=23487,seq=123

>From a UI POV editing singular route map entries seems better as well, and with
the mapping of section entries to route map entries, a suitable API design
follows quite naturally and easily maps to the respective section config
entries, without too much data mangling required.

### prefix-lists.cfg

An example prefix list configuration looks as follows:

prefix-list: example-1
entries action=permit,prefix=192.0.2.0/24
entries action=permit,prefix=192.0.2.0/24,le=32
entries action=permit,prefix=192.0.2.0/24,le=32,ge=24,seq=123

This would create the following FRR prefix list:

ip prefix-list example-1 permit 192.0.2.0/24
ip prefix-list example-1 permit 192.0.2.0/24 le 32
ip prefix-list example-1 seq 123 permit 192.0.2.0/24 le 32 ge 24

## API endpoints

This patch series introduces the following API endpoints in the /cluster/sdn
subfolder:

### Route Maps

GET /route-maps - lists all route map entries
GET /route-maps/<id> - lists all route map entries for the route map <id>
GET /route-maps/<id>/<order> - gets the order'th entry in route map <id>
POST /route-maps - creates a new route map entry
PUT /route-maps/<id>/<order> - updates the order'th entry in route map <id>
DELETE /route-maps/<id>/<order> - deletes the order'th entry in route map <id>

### Prefix Lists

GET /prefix-lists - lists all prefix lists
GET /prefix-lists/<id> - get prefix list <id>
POST /prefix-lists - create a new prefix list
PUT /prefix-lists/<id> - update prefix list <id>
DELETE /prefix-lists/<id> - delete prefix list <id>

## Open questions

How should we handle overriding the auto-generated route maps (e.g. in the EVPN
controller) and prefix lists?

Currently this patch series disallows creating any route map / prefix list that
have the same name as PVE auto-generated ones via the API. They can be
overridden by creating a new route map and then selecting it in the respective
entity (e.g. via route-map-in in the EVPN controller). Pre-defined prefix-lists
cannot currently be overridden, since this usually makes little sense, as they
are used in the auto-generated route maps, which can be overridden anyway.
This is the most restrictive option, which leaves the possibility of re-thinking
our approach depending on if this comes up in the future.

How should we handle setting custom route maps on exit nodes?

For exit nodes a special route map entry is generated that disallows importing
default routes to avoid traffic loops between exit nodes. With the current
implementation, those entries still get created and executed in order to make it
easy for users to use route maps on EVPN exit nodes. This also makes it
impossible to override this behavior, since a route map terminates with the
first matching entry. The proposed solution for overriding this behavior is a
future patch series, that allows defining multiple EVPN controllers and limit
them to specific nodes. Users could then manually build what we currently do on
exit nodes together with this patch series.

## Dependencies

proxmox-frr depends on proxmox-frr-templates
proxmox-frr depends on proxmox-sdn-types
proxmox-ve-config depends on proxmox-sdn-types
proxmox-ve-config depends on proxmox-frr
proxmox-perl-rs depends on proxmox-ve-config
pve-network depends on pve-cluster
pve-network depends on pve-access-control
pve-network depends on proxmox-perl-rs
pve-network depends on pve-cluster
pve-manager depends on pve-network

## Changelog

Changes from v4 (Thanks @Hannes):
* Properly persist route maps / prefix lists to running config
* Consider route maps / prefix lists when rolling back
* Consider route maps / prefix lists when doing a check if FRR needs to be
reloaded
* Fix change detection for exit action field in the UI
* Fix issue with adding a prefix list if one had already been added in the UI
* Fix exit policy rendering in route map overview
* Add default setting to exit policy dropdown
* fix error message when failing to delete prefix list
* fix check_reference using wrong property string format when deleting prefix
lists

Changes from v3:
* added 3 commits in ve-rs that were missing due to layer-8 errors in rebasing

Changes from v2(Thanks @Wolfgang, @Gabriel, @Hannes):
* Add UI integration for prefix list / route map generation
* Add route filter based on prefix lists to openfabric / OSPF
* integrate routemap in / out parameters in BGP / EVPN controller UI
* generate route maps / prefix lists in FRR dry-run
* improve validation in the backend considerably
* add protected flag to API endpoints that require elevated privileges
* fix jinja templates for FRR config due to minijinja whitespace handling
changes
* refactored IntegerWithSign into ModifyNumber

Changes from v1 (Thanks @Gabriel, @Hannes, @Wolfgang):
* rebase on top of current master
* fix newly introduced vtysh tests
* include missing access-control patch
* fix an error in the permission API path of GET /route-maps/{route-map-id}
* fix permission check in list route maps / prefix lists endpoint
* implement From instead of Into for section config to frr conversions
* replace core::* imports with std::*
* improve comments in both pve-rs modules
* use get() instead of iter().find() in get methods of both pve-rs modules
* use entry API when creating new entities in both pve-rs modules
* removed duplicate PrefixList implementation block
* fixed pending parameter in GET endpoints
* add route maps / prefix lists to has_pending_changes method
* fixed change detection for newly introduced fields in prefix lists / route
maps
* fixed reserved id 'loopbacks_ips' for prefix lists (instead of reserving
loopback_ips)
* properly pass delete parameter to the route map update pve-rs method
* remove additional prefix list / route map rendering methods and just use dump
instead in the ve-config FRR integration tests
* improved documentation of the FRR route map generation logic, so it better
explains *how* the configuration gets merged.
* added another test-case for EVPN zones with a controller with custom route-map
+ exit nodes
* implement exit action and call features of route maps
* jump into user-supplied route maps instead of replacing them directly, to
avoid breaking exit-node setups if users do not recreate the auto-generated
route map
* improve indentation of FRR template
* update tests to reflect changes w.r.t. FRR config generation
* improve error message on trying to GET non-existing route map entry
* move the tests from the frr module in route maps / prefix lists to
the integration tests in proxmox-ve-config
* make order u16 instead of u32, because in FRR it is an u16 as well
* add unit tests to some new types
* change route map merging logic to overwrite existing route maps, if an entry
with the same route map name exists in the section config
* added separate patch for PrefixListName::new, since the vtysh patch from
gabriel hasn't been applied yet, but this patch series requires the new
function

pve-cluster:

Stefan Hanreich (2):
cfs: add 'sdn/route-maps.cfg' to observed files
cfs: add 'sdn/prefix-lists.cfg' to observed files

src/PVE/Cluster.pm | 2 ++
src/pmxcfs/status.c | 2 ++
2 files changed, 4 insertions(+)

pve-access-control:

Stefan Hanreich (1):
permissions: add ACL path for prefix-lists and route-maps

src/PVE/AccessControl.pm | 4 ++++
1 file changed, 4 insertions(+)

proxmox-ve-rs:

Stefan Hanreich (16):
frr: add constructor to prefix list name
sdn-types: add common route-map helper types
frr: change order type to u16
frr: implement routemap match/set statements via adjacent tagging
frr: implement support for call and exit action
frr-templates: change route maps template to adapt to new frr types
ve-config: fabrics: adapt frr config generation
ve-config: add prefix list section config
ve-config: frr: implement frr config generation for prefix lists
ve-config: add route map section config
ve-config: frr: implement frr config generation for route maps
ve-config: add prefix lists integration tests
ve-config: add route maps integration tests
fabrics: ospf: fix deserializing OspfDeletableProperties
fabrics: ospf: openfabric: allow user-defined route filter
frr: fabrics: apply route_filter setting

.../templates/route_maps.jinja | 19 +-
proxmox-frr/Cargo.toml | 2 +-
proxmox-frr/debian/control | 2 +
proxmox-frr/src/ser/mod.rs | 2 +-
proxmox-frr/src/ser/route_map.rs | 108 ++-
proxmox-sdn-types/src/bgp.rs | 62 ++
proxmox-sdn-types/src/lib.rs | 107 +++
proxmox-ve-config/src/sdn/fabric/frr.rs | 275 +++----
proxmox-ve-config/src/sdn/fabric/mod.rs | 19 +-
.../section_config/protocol/openfabric.rs | 8 +
.../fabric/section_config/protocol/ospf.rs | 13 +-
proxmox-ve-config/src/sdn/mod.rs | 2 +
proxmox-ve-config/src/sdn/prefix_list.rs | 222 ++++++
proxmox-ve-config/src/sdn/route_map.rs | 728 ++++++++++++++++++
proxmox-ve-config/tests/prefix_lists/main.rs | 112 +++
proxmox-ve-config/tests/route_maps/main.rs | 146 ++++
16 files changed, 1630 insertions(+), 197 deletions(-)
create mode 100644 proxmox-sdn-types/src/bgp.rs
create mode 100644 proxmox-ve-config/src/sdn/prefix_list.rs
create mode 100644 proxmox-ve-config/src/sdn/route_map.rs
create mode 100644 proxmox-ve-config/tests/prefix_lists/main.rs
create mode 100644 proxmox-ve-config/tests/route_maps/main.rs

proxmox-perl-rs:

Stefan Hanreich (3):
pve-rs: sdn: add route maps module
pve-rs: sdn: add prefix lists module
sdn: add prefix list / route maps to frr config generation helper

pve-rs/Cargo.toml | 1 +
pve-rs/Makefile | 2 +
pve-rs/src/bindings/sdn/mod.rs | 30 ++-
pve-rs/src/bindings/sdn/prefix_lists.rs | 192 +++++++++++++++++
pve-rs/src/bindings/sdn/route_maps.rs | 262 ++++++++++++++++++++++++
5 files changed, 484 insertions(+), 3 deletions(-)
create mode 100644 pve-rs/src/bindings/sdn/prefix_lists.rs
create mode 100644 pve-rs/src/bindings/sdn/route_maps.rs

pve-network:

Stefan Hanreich (16):
controller: bgp: evpn: adapt to new match / set frr config syntax
sdn: add prefix lists module
sdn: add route map module
api2: add prefix list module
api2: add route maps module
api2: add route map module
api2: add route map entry module
evpn controller: add route_map_{in,out} parameter
bgp controller: allow configuring custom route maps
sdn: commit route map / prefix list configuration on sdn apply
sdn: frr: consider route maps and prefix lists in dry-run
fabrics: ospf: openfabric: add route_filter property
tests: add simple route map test case
tests: add bgp evpn route map/prefix list testcase
tests: add route map with prefix list testcase
tests: add exit node with custom route map testcase

src/PVE/API2/Network/SDN.pm | 29 +-
src/PVE/API2/Network/SDN/Makefile | 13 +-
src/PVE/API2/Network/SDN/PrefixLists.pm | 258 ++++++++++++++++++
src/PVE/API2/Network/SDN/RouteMaps.pm | 141 ++++++++++
src/PVE/API2/Network/SDN/RouteMaps/Makefile | 9 +
.../API2/Network/SDN/RouteMaps/RouteMap.pm | 93 +++++++
.../Network/SDN/RouteMaps/RouteMapEntry.pm | 145 ++++++++++
src/PVE/Network/SDN.pm | 37 ++-
src/PVE/Network/SDN/Controllers/BgpPlugin.pm | 37 ++-
src/PVE/Network/SDN/Controllers/EvpnPlugin.pm | 54 ++--
src/PVE/Network/SDN/Controllers/Plugin.pm | 14 +
src/PVE/Network/SDN/Fabrics.pm | 15 +-
src/PVE/Network/SDN/Makefile | 14 +-
src/PVE/Network/SDN/PrefixLists.pm | 166 +++++++++++
src/PVE/Network/SDN/RouteMaps.pm | 217 +++++++++++++++
.../expected_controller_config | 80 ++++++
.../expected_sdn_interfaces | 41 +++
.../bgp_evpn_routemap_prefix_list/interfaces | 7 +
.../bgp_evpn_routemap_prefix_list/sdn_config | 86 ++++++
.../evpn/routemap/expected_controller_config | 68 +++++
.../evpn/routemap/expected_sdn_interfaces | 41 +++
src/test/zones/evpn/routemap/interfaces | 7 +
src/test/zones/evpn/routemap/sdn_config | 70 +++++
.../expected_controller_config | 101 +++++++
.../expected_sdn_interfaces | 41 +++
.../zones/evpn/routemap_exit_node/interfaces | 7 +
.../zones/evpn/routemap_exit_node/sdn_config | 71 +++++
.../expected_controller_config | 53 ++++
.../expected_sdn_interfaces | 41 +++
.../evpn/routemap_prefix_list/interfaces | 7 +
.../evpn/routemap_prefix_list/sdn_config | 58 ++++
31 files changed, 1984 insertions(+), 37 deletions(-)
create mode 100644 src/PVE/API2/Network/SDN/PrefixLists.pm
create mode 100644 src/PVE/API2/Network/SDN/RouteMaps.pm
create mode 100644 src/PVE/API2/Network/SDN/RouteMaps/Makefile
create mode 100644 src/PVE/API2/Network/SDN/RouteMaps/RouteMap.pm
create mode 100644 src/PVE/API2/Network/SDN/RouteMaps/RouteMapEntry.pm
create mode 100644 src/PVE/Network/SDN/PrefixLists.pm
create mode 100644 src/PVE/Network/SDN/RouteMaps.pm
create mode 100644 src/test/zones/evpn/bgp_evpn_routemap_prefix_list/expected_controller_config
create mode 100644 src/test/zones/evpn/bgp_evpn_routemap_prefix_list/expected_sdn_interfaces
create mode 100644 src/test/zones/evpn/bgp_evpn_routemap_prefix_list/interfaces
create mode 100644 src/test/zones/evpn/bgp_evpn_routemap_prefix_list/sdn_config
create mode 100644 src/test/zones/evpn/routemap/expected_controller_config
create mode 100644 src/test/zones/evpn/routemap/expected_sdn_interfaces
create mode 100644 src/test/zones/evpn/routemap/interfaces
create mode 100644 src/test/zones/evpn/routemap/sdn_config
create mode 100644 src/test/zones/evpn/routemap_exit_node/expected_controller_config
create mode 100644 src/test/zones/evpn/routemap_exit_node/expected_sdn_interfaces
create mode 100644 src/test/zones/evpn/routemap_exit_node/interfaces
create mode 100644 src/test/zones/evpn/routemap_exit_node/sdn_config
create mode 100644 src/test/zones/evpn/routemap_prefix_list/expected_controller_config
create mode 100644 src/test/zones/evpn/routemap_prefix_list/expected_sdn_interfaces
create mode 100644 src/test/zones/evpn/routemap_prefix_list/interfaces
create mode 100644 src/test/zones/evpn/routemap_prefix_list/sdn_config

pve-manager:

Stefan Hanreich (8):
ui: sdn: add route map selector
ui: sdn: add prefix list selector
ui: sdn: add panel for managing prefix lists
ui: sdn: add panel for managing route map entries
ui: sdn: bgp controller: allow configuring route maps
ui: sdn: evpn controller: allow configuring route maps
ui: sdn: openfabric: add route filter
ui: sdn: ospf: add route filter setting

www/manager6/Makefile | 4 +
www/manager6/dc/Config.js | 16 +
www/manager6/sdn/PrefixListPanel.js | 459 +++++++++
www/manager6/sdn/PrefixListSelector.js | 31 +
www/manager6/sdn/RouteMapPanel.js | 972 ++++++++++++++++++
www/manager6/sdn/RouteMapSelector.js | 55 +
www/manager6/sdn/controllers/BgpEdit.js | 14 +
www/manager6/sdn/controllers/EvpnEdit.js | 17 +
.../sdn/fabrics/openfabric/FabricEdit.js | 8 +
www/manager6/sdn/fabrics/ospf/FabricEdit.js | 8 +
10 files changed, 1584 insertions(+)
create mode 100644 www/manager6/sdn/PrefixListPanel.js
create mode 100644 www/manager6/sdn/PrefixListSelector.js
create mode 100644 www/manager6/sdn/RouteMapPanel.js
create mode 100644 www/manager6/sdn/RouteMapSelector.js

Summary over all repositories:
65 files changed, 5690 insertions(+), 237 deletions(-)

--
Generated by murpp 0.11.0