* [RFC docs/manager/network/proxmox-ve-rs 00/16] Extend EVPN controller functionality
@ 2026-04-14 16:32 Stefan Hanreich
2026-04-14 16:32 ` [PATCH proxmox-ve-rs 01/16] frr: add local-as setting Stefan Hanreich
` (15 more replies)
0 siblings, 16 replies; 17+ messages in thread
From: Stefan Hanreich @ 2026-04-14 16:32 UTC (permalink / raw)
To: pve-devel
This patch series is based on the route-maps / prefix-lists series [1].
## Introduction
This patch series lifts several existing restrictions on the EVPN controller:
* Allow defining multiple EVPN controllers in a cluster (which is effectively
defining multiple EVPN peer-groups in the FRR configuration)
* Allow restricting EVPN controllers to specific nodes
* Allow overriding the type of BGP session for EVPN controllers
## Motivation
The functionality implemented in this patch series allows exit nodes to act
essentially as a ASBR router for the whole cluster or, alternatively, certain
PVE nodes as route-server. The core motivation behind this patch series is that
enables the possibility to define a second EVPN controller that handles
advertising routes to another AS, independent of the intra-AS peering. This is
particularly useful with the route-maps / prefix-lists series that allows
attaching route-maps to EVPN controllers. That way users can create route-maps
for the EVPN controller that connects to another AS (filtering routes,
manipulating next-hops, ..). This patch series enables the PVE stack to
implement setups similar to the Inter-AS Option B/C (with full-mesh peering
inside the AS instead of RRs) from RFC 4364 based on the EVPN AF (instead of the
VPNv4 AF) [2].
Due to the modular nature of all the components involved, it is possible to
replace every component in a complex setup with a physical router / route
reflector / ... - giving users flexibility in how they want to build their
network. By providing virtual equivalents of every critical component, it is
also possible for us to create and manage a DCI EVPN cluster completely via the
Proxmox VE stack and its API. Users can still choose to use physical equivalents
- with the Proxmox VE stack only handling announcing the routes for an EVPN
zone.
By implementing and providing this functionality via the API, there is less need
for users to run their own hand-crafted frr.conf[.local] files for implementing
different types of EVPN setups or resort to running custom, external, FRR
instances for mangling routes and then re-advertising them to an uplink. In
addition, this API can be used internally or by other products (e.g. PDM) to
setup more complex setups automatically, but afterwards still provide users with
the ability to customize the pre-generated setups, since a lot of options are
more explicit now.
## Future Work
Future work should additionally allow specific PVE nodes to act as route
reflectors for the cluster. This is similar to how e.g. Calico implements route
reflectors [3]. This would make it possible to have the whole route handling
manageable via the Proxmox VE stack, without the scaling issues attached to
full-mesh peering and without the nodes itself having to be in the data path.
This would fully enable Inter-AS Option B & C as defined in RFC 4364 [2] with
the PVE exit nodes acting as RRs and either the PVE node or an external router
acting as ASBR. Inter-AS peering between the Route Reflectors of different
clusters in the Option C could then happen directly e.g. via the Wireguard
fabric and/or exchanging the loopback IPs of the RRs via the ASBRs.
This patch series adds basic documentation to the newly introduced options. With
the addition of this and other recent patch series, a lot of possibilities for
configuring the EVPN stack are exposed to users, that can be leveraged to create
sophisticated {intra,inter}-AS setups. To give users an idea on how to leverage
the new features and which setups are recommended by us, we will create
documentation for example setups and. Since this depends not only on this patch
series and the progress we make in the coming weeks, they will be added as a
separate patch series - if everything goes as planned - depending on the state
of the EVPN stack and the outstanding patch series, as well as the progress we
make on PDM side.
## Discussion / Open questions for Reviewers
A primary controller still needs to be assigned to an EVPN zone, as that is
required for generating the VTEP IPs as well as auto-generating the MTU for the
VNet. This means that the controller responsible for announcing the EVPN routes
*inside* the cluster needs to be selected, in order for the SDN stack to
generate correct configurations. This could be a bit confusing to users if they
want to utilize multiple controllers, but aren't sure which one to pick as their
'primary' controller. Simply allowing users to configure multiple EVPN
controllers for a zone doesn't work - since we cannot know which controller to
use for auto-generating VTEP IPs.
The 'legacy' mode might better be called 'Auto', since it automatically inherits
the BGP session from a given BGP controller. This could also be used in
conjunction with the unnumbered BGP fabric - enabling easy re-use of the BGP
session established there, while still giving users the possibility to run iBGP
sessions instead.
## Backwards Compatibility
Great care has been taken to ensure that existing EVPN controllers / zones and
BGP controller still work the same as before, every existing test case still
works the same without any adjustments. This is possible because existing SDN
configs should just be a subset of the now possible EVPN configurations. The new
capabilities can only be used with completely new configuration options that
need to be set explicitly. Additionally, when generating the FRR configuration,
the case of only one EVPN controller existing is special-cased and generating
filtering of outgoing routes does not take place then.
Backwards Compatibility has been verified via the integration tests, new
additional integration tests as well as the e2e tests.
## FRR issues
This series suffers from a bug in the FRR version that is currently contained in
our repositories (10.4.1), but has been fixed in a subsequent release (10.6).
When leaking routes into the default routing table (which happens when defining
PVE nodes as exit nodes), the Route Targets for EVPN routes sometimes get
assigned the wrong ASN (1). This breaks the route filtering implemented in this
patch series when multiple EVPN controllers are configured. Existing EVPN setups
are *not* affected by this, since this has been special cased and no route
filtering takes place.
The bug should be addressed before merging this patch series, either by
backporting the fix or upgrading the existing FRR version.
## Dependencies
proxmox-frr depends on proxmox-frr-templates
proxmox-ve-config depends on proxmox-frr
proxmox-perl-rs needs to be bumped with the new proxmox-ve-config version
pve-network depends on proxmox-perl-rs
pve-manager depends on pve-network
[1] https://lore.proxmox.com/all/20260401143957.386809-1-s.hanreich@proxmox.com/
[2] https://www.rfc-editor.org/rfc/rfc4364.html#section-10
[3] www.tigera.io/blog/configuring-route-reflectors-in-calico/
proxmox-ve-rs:
Stefan Hanreich (4):
frr: add local-as setting
frr: add support for extcommunity lists
frr-templates: render local-as setting
frr-templates: render community lists in templates
.../templates/bgp_router.jinja | 4 +
proxmox-frr-templates/templates/bgpd.jinja | 6 +
proxmox-frr/src/ser/bgp.rs | 103 +++++++++++++++++-
proxmox-frr/src/ser/mod.rs | 23 +++-
proxmox-frr/src/ser/route_map.rs | 43 ++++++--
5 files changed, 166 insertions(+), 13 deletions(-)
pve-network:
Stefan Hanreich (9):
evpn controller: make nodes configurable
evpn controller: allow multiple evpn controllers in a cluster
evpn controller: add bgp-mode setting
evpn zone: add secondary-controllers and rt filtering
evpn controller: add ebgp-multihop setting
test: evpn: add test for ibgp + ebgp evpn controller
test: evpn: add legacy test
tests: evpn: force ibgp over ebgp bgp controller with ebgp wan session
tests: test route filtering mechanism with multiple zones/controllers
src/PVE/API2/Network/SDN/Controllers.pm | 13 ++
src/PVE/API2/Network/SDN/Zones.pm | 11 +
src/PVE/Network/SDN.pm | 1 +
src/PVE/Network/SDN/Controllers.pm | 71 +++++++
src/PVE/Network/SDN/Controllers/BgpPlugin.pm | 22 +-
src/PVE/Network/SDN/Controllers/EvpnPlugin.pm | 188 +++++++++++++++---
src/PVE/Network/SDN/Controllers/Plugin.pm | 82 ++++++++
src/PVE/Network/SDN/Zones/EvpnPlugin.pm | 39 +++-
.../expected_controller_config | 87 ++++++++
.../expected_sdn_interfaces | 41 ++++
.../ebgp_evpn_ibgp_wan_evpn_ebgp/interfaces | 11 +
.../ebgp_evpn_ibgp_wan_evpn_ebgp/sdn_config | 59 ++++++
.../expected_controller_config | 66 ++++++
.../expected_sdn_interfaces | 41 ++++
.../evpn_cluster_ibgp_uplink_ebgp/interfaces | 7 +
.../evpn_cluster_ibgp_uplink_ebgp/sdn_config | 51 +++++
.../expected_controller_config | 78 ++++++++
.../expected_sdn_interfaces | 81 ++++++++
.../interfaces | 7 +
.../sdn_config | 67 +++++++
.../legacy_mode/expected_controller_config | 61 ++++++
.../evpn/legacy_mode/expected_sdn_interfaces | 41 ++++
src/test/zones/evpn/legacy_mode/interfaces | 7 +
src/test/zones/evpn/legacy_mode/sdn_config | 51 +++++
24 files changed, 1143 insertions(+), 40 deletions(-)
create mode 100644 src/test/zones/evpn/ebgp_evpn_ibgp_wan_evpn_ebgp/expected_controller_config
create mode 100644 src/test/zones/evpn/ebgp_evpn_ibgp_wan_evpn_ebgp/expected_sdn_interfaces
create mode 100644 src/test/zones/evpn/ebgp_evpn_ibgp_wan_evpn_ebgp/interfaces
create mode 100644 src/test/zones/evpn/ebgp_evpn_ibgp_wan_evpn_ebgp/sdn_config
create mode 100644 src/test/zones/evpn/evpn_cluster_ibgp_uplink_ebgp/expected_controller_config
create mode 100644 src/test/zones/evpn/evpn_cluster_ibgp_uplink_ebgp/expected_sdn_interfaces
create mode 100644 src/test/zones/evpn/evpn_cluster_ibgp_uplink_ebgp/interfaces
create mode 100644 src/test/zones/evpn/evpn_cluster_ibgp_uplink_ebgp/sdn_config
create mode 100644 src/test/zones/evpn/evpn_multiple_zones_cluster_ibgp_uplink_ebgp/expected_controller_config
create mode 100644 src/test/zones/evpn/evpn_multiple_zones_cluster_ibgp_uplink_ebgp/expected_sdn_interfaces
create mode 100644 src/test/zones/evpn/evpn_multiple_zones_cluster_ibgp_uplink_ebgp/interfaces
create mode 100644 src/test/zones/evpn/evpn_multiple_zones_cluster_ibgp_uplink_ebgp/sdn_config
create mode 100644 src/test/zones/evpn/legacy_mode/expected_controller_config
create mode 100644 src/test/zones/evpn/legacy_mode/expected_sdn_interfaces
create mode 100644 src/test/zones/evpn/legacy_mode/interfaces
create mode 100644 src/test/zones/evpn/legacy_mode/sdn_config
pve-manager:
Stefan Hanreich (1):
sdn: evpn: zone: controller: add new advanced fields
www/manager6/sdn/controllers/EvpnEdit.js | 39 ++++++++++++++++++++++++
www/manager6/sdn/zones/EvpnEdit.js | 14 ++++++++-
2 files changed, 52 insertions(+), 1 deletion(-)
pve-docs:
Stefan Hanreich (2):
sdn: evpn: document new zone / controller options
sdn: fix typo in bgp controller
pvesdn.adoc | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 70 insertions(+), 4 deletions(-)
Summary over all repositories:
32 files changed, 1431 insertions(+), 58 deletions(-)
--
Generated by murpp 0.11.0
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH proxmox-ve-rs 01/16] frr: add local-as setting
2026-04-14 16:32 [RFC docs/manager/network/proxmox-ve-rs 00/16] Extend EVPN controller functionality Stefan Hanreich
@ 2026-04-14 16:32 ` Stefan Hanreich
2026-04-14 16:32 ` [PATCH proxmox-ve-rs 02/16] frr: add support for extcommunity lists Stefan Hanreich
` (14 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Stefan Hanreich @ 2026-04-14 16:32 UTC (permalink / raw)
To: pve-devel
This can be used for overriding the ASN for a given neighbor group.
This is particularly useful when running BGP as routing protocol both
for the overlay and the underlay, but utilizing different ASNs for
each.
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
proxmox-frr/src/ser/bgp.rs | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/proxmox-frr/src/ser/bgp.rs b/proxmox-frr/src/ser/bgp.rs
index dcad3b8..79bc920 100644
--- a/proxmox-frr/src/ser/bgp.rs
+++ b/proxmox-frr/src/ser/bgp.rs
@@ -27,11 +27,33 @@ pub enum NeighborRemoteAs {
Asn(#[serde(deserialize_with = "proxmox_serde::perl::deserialize_u32")] u32),
}
+// Each flag requires the previous flag to be set. It is not possible to set replace-as without
+// no-prepend.
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
+#[serde(rename_all = "kebab-case")]
+pub enum LocalAsFlags {
+ NoPrepend,
+ #[serde(rename = "no-prepend replace-as")]
+ ReplaceAs,
+ #[serde(rename = "no-prepend replace-as dual-as")]
+ DualAs,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
+#[serde(rename_all = "lowercase")]
+pub struct LocalAsSettings {
+ asn: u32,
+ #[serde(default)]
+ mode: Option<LocalAsFlags>,
+}
+
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct NeighborGroup {
pub name: FrrWord,
#[serde(deserialize_with = "proxmox_serde::perl::deserialize_bool")]
pub bfd: bool,
+ #[serde(default)]
+ pub local_as: Option<LocalAsSettings>,
pub remote_as: NeighborRemoteAs,
#[serde(default)]
pub ips: Vec<IpAddr>,
--
2.47.3
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH proxmox-ve-rs 02/16] frr: add support for extcommunity lists
2026-04-14 16:32 [RFC docs/manager/network/proxmox-ve-rs 00/16] Extend EVPN controller functionality Stefan Hanreich
2026-04-14 16:32 ` [PATCH proxmox-ve-rs 01/16] frr: add local-as setting Stefan Hanreich
@ 2026-04-14 16:32 ` Stefan Hanreich
2026-04-14 16:33 ` [PATCH proxmox-ve-rs 03/16] frr-templates: render local-as setting Stefan Hanreich
` (13 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Stefan Hanreich @ 2026-04-14 16:32 UTC (permalink / raw)
To: pve-devel
Extended Communities are used for encoding Route Targets in the EVPN
AF, among other things. FRR provides a mechanism to match based on
them via the extcommunity lists. Implement support for creating them,
so they can be used by the SDN stack for matching extended communities
in route maps. Initially, this will be used to filter outgoing routes
in EVPN controllers, but in the future it is planned to expose
creating extcommunity lists via our API / UI as well.
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
proxmox-frr/src/ser/bgp.rs | 81 +++++++++++++++++++++++++++++++-
proxmox-frr/src/ser/mod.rs | 23 ++++++++-
proxmox-frr/src/ser/route_map.rs | 43 +++++++++++++----
3 files changed, 134 insertions(+), 13 deletions(-)
diff --git a/proxmox-frr/src/ser/bgp.rs b/proxmox-frr/src/ser/bgp.rs
index 79bc920..0bf4a1d 100644
--- a/proxmox-frr/src/ser/bgp.rs
+++ b/proxmox-frr/src/ser/bgp.rs
@@ -1,10 +1,11 @@
+use std::fmt::Display;
use std::net::{IpAddr, Ipv4Addr};
use proxmox_network_types::ip_address::{Ipv4Cidr, Ipv6Cidr};
use serde::{Deserialize, Serialize};
use crate::ser::route_map::RouteMapName;
-use crate::ser::{FrrWord, InterfaceName, IpRoute};
+use crate::ser::{AccessAction, FrrWord, InterfaceName, IpRoute};
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub struct BgpRouterName {
@@ -195,3 +196,81 @@ pub struct BgpRouter {
#[serde(default)]
pub custom_frr_config: Vec<String>,
}
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
+pub struct CommunityListName(String);
+
+impl Display for CommunityListName {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ Display::fmt(&self.0, f)
+ }
+}
+
+impl CommunityListName {
+ pub fn new(name: String) -> Self {
+ Self(name)
+ }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+pub struct ExtCommunityRouteTarget {
+ asn: u16,
+ value: u32,
+}
+
+impl std::str::FromStr for ExtCommunityRouteTarget {
+ type Err = anyhow::Error;
+
+ fn from_str(value: &str) -> Result<Self, Self::Err> {
+ if let Some((asn, value)) = value.split_once(':') {
+ return Ok(Self {
+ asn: asn.parse()?,
+ value: value.parse()?,
+ });
+ }
+
+ anyhow::bail!("can not parse route target: {value}")
+ }
+}
+
+impl Display for ExtCommunityRouteTarget {
+ fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+ write!(f, "{}:{}", self.asn, self.value)
+ }
+}
+
+proxmox_serde::forward_serialize_to_display!(ExtCommunityRouteTarget);
+proxmox_serde::forward_deserialize_to_from_str!(ExtCommunityRouteTarget);
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
+#[serde(tag = "type", content = "value")]
+pub enum StandardExtCommunityListMatch {
+ #[serde(rename = "rt")]
+ RouteTarget(ExtCommunityRouteTarget),
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
+#[serde(tag = "type", content = "value")]
+pub enum ExtendedExtCommunityListMatch {
+ #[serde(rename = "rt")]
+ RouteTarget(String),
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
+pub struct ExtendedExtCommunityListEntry {
+ pub action: AccessAction,
+ pub match_entry: ExtendedExtCommunityListMatch,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
+pub struct StandardExtCommunityListEntry {
+ pub action: AccessAction,
+ pub match_entry: StandardExtCommunityListMatch,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
+#[serde(tag = "type", content = "entries", rename_all = "kebab-case")]
+pub enum ExtCommunityList {
+ Standard(Vec<StandardExtCommunityListEntry>),
+ Extended(Vec<ExtendedExtCommunityListEntry>),
+}
diff --git a/proxmox-frr/src/ser/mod.rs b/proxmox-frr/src/ser/mod.rs
index 7bb4836..2ff2011 100644
--- a/proxmox-frr/src/ser/mod.rs
+++ b/proxmox-frr/src/ser/mod.rs
@@ -9,8 +9,11 @@ use std::collections::BTreeMap;
use std::net::IpAddr;
use std::str::FromStr;
-use crate::ser::route_map::{
- AccessListName, AccessListRule, PrefixListName, PrefixListRule, RouteMapEntry, RouteMapName,
+use crate::ser::{
+ bgp::{CommunityListName, ExtCommunityList},
+ route_map::{
+ AccessListName, AccessListRule, PrefixListName, PrefixListRule, RouteMapEntry, RouteMapName,
+ },
};
use proxmox_network_types::{
@@ -21,6 +24,19 @@ use proxmox_serde::forward_deserialize_to_from_str;
use serde::{Deserialize, Serialize};
use thiserror::Error;
+/// The action for a [`AccessListRule`] or [`ExtCommunityList`].
+///
+/// The default is Permit. Deny can be used to create a NOT match (e.g. match all routes that are
+/// NOT in 10.10.10.0/24 using `ip access-list TEST deny 10.10.10.0/24`).
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
+#[serde(rename_all = "kebab-case")]
+pub enum AccessAction {
+ Permit,
+ Deny,
+}
+
+proxmox_serde::forward_display_to_serialize!(AccessAction);
+
#[derive(Error, Debug)]
pub enum FrrWordError {
#[error("word is empty")]
@@ -255,4 +271,7 @@ pub struct BgpFrrConfig {
#[serde(default)]
pub vrfs: BTreeMap<InterfaceName, bgp::Vrf>,
+
+ #[serde(default)]
+ pub ext_community_lists: BTreeMap<CommunityListName, ExtCommunityList>,
}
diff --git a/proxmox-frr/src/ser/route_map.rs b/proxmox-frr/src/ser/route_map.rs
index 54d88e7..958a1c8 100644
--- a/proxmox-frr/src/ser/route_map.rs
+++ b/proxmox-frr/src/ser/route_map.rs
@@ -7,16 +7,8 @@ use proxmox_sdn_types::{
};
use serde::{Deserialize, Serialize};
-/// The action for a [`AccessListRule`].
-///
-/// The default is Permit. Deny can be used to create a NOT match (e.g. match all routes that are
-/// NOT in 10.10.10.0/24 using `ip access-list TEST deny 10.10.10.0/24`).
-#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
-#[serde(rename_all = "kebab-case")]
-pub enum AccessAction {
- Permit,
- Deny,
-}
+use crate::ser::bgp::CommunityListName;
+pub use crate::ser::AccessAction;
/// A single [`AccessList`] rule.
///
@@ -66,6 +58,35 @@ pub struct PrefixListRule {
pub is_ipv6: bool,
}
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
+#[serde(rename_all = "kebab-case")]
+pub enum CommunityMatchMode {
+ ExactMatch,
+ Any,
+}
+
+proxmox_serde::forward_display_to_serialize!(CommunityMatchMode);
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize)]
+pub struct ExtendedCommunityMatch {
+ pub name: CommunityListName,
+ pub mode: Option<CommunityMatchMode>,
+}
+
+impl std::fmt::Display for ExtendedCommunityMatch {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let mode = self
+ .mode
+ .as_ref()
+ .map(|mode| format!(" {mode}"))
+ .unwrap_or_else(|| String::new());
+
+ write!(f, "{}{mode}", self.name)
+ }
+}
+
+proxmox_serde::forward_serialize_to_display!(ExtendedCommunityMatch);
+
/// A match statement inside a route-map.
///
/// A route-map has one or more match statements which decide on which routes the route-map will
@@ -102,6 +123,8 @@ pub enum RouteMapMatch {
Peer(String),
#[serde(rename = "tag")]
Tag(SetTagValue),
+ #[serde(rename = "extcommunity")]
+ ExtendedCommunity(ExtendedCommunityMatch),
}
/// Defines the Action a route-map takes when it matches on a route.
--
2.47.3
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH proxmox-ve-rs 03/16] frr-templates: render local-as setting
2026-04-14 16:32 [RFC docs/manager/network/proxmox-ve-rs 00/16] Extend EVPN controller functionality Stefan Hanreich
2026-04-14 16:32 ` [PATCH proxmox-ve-rs 01/16] frr: add local-as setting Stefan Hanreich
2026-04-14 16:32 ` [PATCH proxmox-ve-rs 02/16] frr: add support for extcommunity lists Stefan Hanreich
@ 2026-04-14 16:33 ` Stefan Hanreich
2026-04-14 16:33 ` [PATCH proxmox-ve-rs 04/16] frr-templates: render community lists in templates Stefan Hanreich
` (12 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Stefan Hanreich @ 2026-04-14 16:33 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
proxmox-frr-templates/templates/bgp_router.jinja | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/proxmox-frr-templates/templates/bgp_router.jinja b/proxmox-frr-templates/templates/bgp_router.jinja
index 35d3f89..7b4a85e 100644
--- a/proxmox-frr-templates/templates/bgp_router.jinja
+++ b/proxmox-frr-templates/templates/bgp_router.jinja
@@ -38,6 +38,10 @@
{% 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.local_as %}
+ neighbor {{ neighbor_group.name }} local-as {{ neighbor_group.local_as.asn }}{% if neighbor_group.local_as.mode %}{{ " " ~ neighbor_group.local_as.mode }}{% endif %}
+
+{% endif %}
{% if neighbor_group.bfd %}
neighbor {{ neighbor_group.name }} bfd
{% endif %}
--
2.47.3
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH proxmox-ve-rs 04/16] frr-templates: render community lists in templates
2026-04-14 16:32 [RFC docs/manager/network/proxmox-ve-rs 00/16] Extend EVPN controller functionality Stefan Hanreich
` (2 preceding siblings ...)
2026-04-14 16:33 ` [PATCH proxmox-ve-rs 03/16] frr-templates: render local-as setting Stefan Hanreich
@ 2026-04-14 16:33 ` Stefan Hanreich
2026-04-14 16:33 ` [PATCH pve-network 05/16] evpn controller: make nodes configurable Stefan Hanreich
` (11 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Stefan Hanreich @ 2026-04-14 16:33 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
proxmox-frr-templates/templates/bgpd.jinja | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/proxmox-frr-templates/templates/bgpd.jinja b/proxmox-frr-templates/templates/bgpd.jinja
index e530995..4c9dce1 100644
--- a/proxmox-frr-templates/templates/bgpd.jinja
+++ b/proxmox-frr-templates/templates/bgpd.jinja
@@ -33,3 +33,9 @@ router bgp {{ router_config.asn }} view {{ view_id }}
{{ bgp_router(router_config) -}}
exit
{% endfor %}
+{% for community_list_name, community_list in bgp.ext_community_lists|items %}
+!
+{% for entry in community_list.entries %}
+bgp extcommunity-list {{ community_list.type }} {{ community_list_name }} {{ entry.action }} {{ entry.match_entry.type }} {{ entry.match_entry.value }}
+{% endfor %}
+{% endfor %}
--
2.47.3
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH pve-network 05/16] evpn controller: make nodes configurable
2026-04-14 16:32 [RFC docs/manager/network/proxmox-ve-rs 00/16] Extend EVPN controller functionality Stefan Hanreich
` (3 preceding siblings ...)
2026-04-14 16:33 ` [PATCH proxmox-ve-rs 04/16] frr-templates: render community lists in templates Stefan Hanreich
@ 2026-04-14 16:33 ` Stefan Hanreich
2026-04-14 16:33 ` [PATCH pve-network 06/16] evpn controller: allow multiple evpn controllers in a cluster Stefan Hanreich
` (10 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Stefan Hanreich @ 2026-04-14 16:33 UTC (permalink / raw)
To: pve-devel
This patch allows restricting an EVPN controller only to specific
nodes in the cluster - similar to the BGP controller. This feature on
its own doesn't add a lot, but is very handy together with other
improvements to the EVPN controller that are implemented in subsequent
patches. Subsequent patches add the ability to define multiple EVPN
controllers, apply route maps to EVPN controllers as well as defining
the type of BGP sessions (all added in separate patches). Together
with this patch, this allows for setting up more complex EVPN setups,
e.g.:
Having an additional EVPN controller on the exit nodes that
communicates with an external EVPN BGP speaker. This allows for
utilizing iBGP internally, but eBGP externally - and only on the exit
nodes. With the addition of route maps, this could be used to
advertise type 2 routes internally, but only type-5 routes externally.
In setups with multiple racks, where the top-of-rack switches act as
route reflectors for the racks, this can be used to define EVPN
peering sessions for each rack independently.
It also allows for the specfic nodes to act as route servers for the
entire cluster that aggregate, modify and re-advertise routes to the
uplink without sitting in the datapath itself. Previously this
required an external route server that handles this outside of the
Promox VE SDN stack. In the future it is planned to implement
configuring EVPN controllers to act as route reflectors, which would
make this setup even better, by adding the ability to scale better for
large clusters.
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
src/PVE/API2/Network/SDN/Controllers.pm | 1 +
src/PVE/Network/SDN/Controllers/EvpnPlugin.pm | 7 +++++++
2 files changed, 8 insertions(+)
diff --git a/src/PVE/API2/Network/SDN/Controllers.pm b/src/PVE/API2/Network/SDN/Controllers.pm
index bc3ec09..8633e45 100644
--- a/src/PVE/API2/Network/SDN/Controllers.pm
+++ b/src/PVE/API2/Network/SDN/Controllers.pm
@@ -93,6 +93,7 @@ my $CONTROLLER_PROPERTIES = {
type => 'string',
format => 'pve-sdn-isis-net',
},
+ nodes => get_standard_option('pve-node-list', { optional => 1 }),
};
__PACKAGE__->register_method({
diff --git a/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm b/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm
index 055a75f..c13d08b 100644
--- a/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm
+++ b/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm
@@ -37,6 +37,7 @@ sub properties {
type => 'string',
format => 'ip-list',
},
+ nodes => get_standard_option('pve-node-list', { optional => 1 }),
};
}
@@ -47,6 +48,7 @@ sub options {
'fabric' => { optional => 1 },
'route-map-in' => { optional => 1 },
'route-map-out' => { optional => 1 },
+ 'nodes' => { optional => 1 },
};
}
@@ -56,6 +58,11 @@ sub generate_frr_config {
my $local_node = PVE::INotify::nodename();
+ if (defined($plugin_config->{nodes})) {
+ my @nodes = PVE::Tools::split_list($plugin_config->{nodes});
+ return if !grep { $_ eq $local_node } @nodes;
+ }
+
my @peers;
my $asn = int($plugin_config->{asn});
my $ebgp = undef;
--
2.47.3
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH pve-network 06/16] evpn controller: allow multiple evpn controllers in a cluster
2026-04-14 16:32 [RFC docs/manager/network/proxmox-ve-rs 00/16] Extend EVPN controller functionality Stefan Hanreich
` (4 preceding siblings ...)
2026-04-14 16:33 ` [PATCH pve-network 05/16] evpn controller: make nodes configurable Stefan Hanreich
@ 2026-04-14 16:33 ` Stefan Hanreich
2026-04-14 16:33 ` [PATCH pve-network 07/16] evpn controller: add bgp-mode setting Stefan Hanreich
` (9 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Stefan Hanreich @ 2026-04-14 16:33 UTC (permalink / raw)
To: pve-devel
Previously it was only possible to define one global EVPN controller
in a cluster, which represented a single peer-group - VTEP. This patch
allows defining multiple EVPN controllers in a cluster. One can think
of one EVPN controller as mapping to a single peer-group. This patch
series adds the possibility of defining multiple peer-groups.
In order to enable this change, introduce a new setting in the EVPN
controller 'peer-group-name'. Since it was only possible to create a
single EVPN controller in the entire cluster, the FRR config
generation generated a single peer group with a hard-coded name. To
allow defining multiple peer-groups and preserve
backwards-compatibility, a custom peer group name needs to be defined
explicitly for each additional controller. The setting is optional and
the peer group name defaults to 'VTEP' if unset, in order to avoid
breaking backwards-compatibility with custom FRR configurations.
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
src/PVE/API2/Network/SDN/Controllers.pm | 5 ++
src/PVE/Network/SDN/Controllers/EvpnPlugin.pm | 68 +++++++++++++------
2 files changed, 52 insertions(+), 21 deletions(-)
diff --git a/src/PVE/API2/Network/SDN/Controllers.pm b/src/PVE/API2/Network/SDN/Controllers.pm
index 8633e45..49951a3 100644
--- a/src/PVE/API2/Network/SDN/Controllers.pm
+++ b/src/PVE/API2/Network/SDN/Controllers.pm
@@ -94,6 +94,11 @@ my $CONTROLLER_PROPERTIES = {
format => 'pve-sdn-isis-net',
},
nodes => get_standard_option('pve-node-list', { optional => 1 }),
+ 'peer-group-name' => {
+ description => "Name of the peer group for this EVPN controller",
+ type => 'string',
+ optional => 1,
+ },
};
__PACKAGE__->register_method({
diff --git a/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm b/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm
index c13d08b..a683dde 100644
--- a/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm
+++ b/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm
@@ -38,6 +38,12 @@ sub properties {
format => 'ip-list',
},
nodes => get_standard_option('pve-node-list', { optional => 1 }),
+ 'peer-group-name' => {
+ description => "Name of the peer group for this EVPN controller",
+ type => 'string',
+ optional => 1,
+ default => 'VTEP',
+ },
};
}
@@ -49,6 +55,7 @@ sub options {
'route-map-in' => { optional => 1 },
'route-map-out' => { optional => 1 },
'nodes' => { optional => 1 },
+ 'peer-group-name' => { optional => 1 },
};
}
@@ -147,11 +154,13 @@ sub generate_frr_config {
$bgp_router->{address_families} = {};
}
+ my $peer_group_name = $plugin_config->{'peer-group-name'} // 'VTEP';
+
# Build VTEP neighbor group
my @vtep_ips = grep { $_ ne $ifaceip } @peers;
my $neighbor_group = {
- name => "VTEP",
+ name => $peer_group_name,
bfd => 1,
remote_as => $ebgp ? "external" : $asn,
ips => \@vtep_ips,
@@ -164,28 +173,37 @@ sub generate_frr_config {
# Configure l2vpn evpn address family
$bgp_router->{address_families}->{l2vpn_evpn} //= {
- neighbors => [{
- name => "VTEP",
- route_map_in => 'MAP_VTEP_IN',
- route_map_out => 'MAP_VTEP_OUT',
- }],
+ neighbors => [],
advertise_all_vni => 1,
};
+ my $route_map_in = 'MAP_VTEP_IN';
+ $route_map_in .= "_$peer_group_name" if $plugin_config->{'peer-group-name'};
+
+ my $route_map_out = 'MAP_VTEP_OUT';
+ $route_map_out .= "_$peer_group_name" if $plugin_config->{'peer-group-name'};
+
+ push $bgp_router->{address_families}->{l2vpn_evpn}->{neighbors}->@*,
+ {
+ name => $peer_group_name,
+ route_map_in => $route_map_in,
+ route_map_out => $route_map_out,
+ };
+
$bgp_router->{address_families}->{l2vpn_evpn}->{autort_as} = $autortas if $autortas;
- if (!$config->{frr}->{routemaps}->{'MAP_VTEP_IN'}) {
+ if (!$config->{frr}->{routemaps}->{$route_map_in}) {
my $entry = { seq => 1, action => "permit" };
$entry->{call} = $plugin_config->{'route-map-in'} if $plugin_config->{'route-map-in'};
- push($config->{frr}->{routemaps}->{'MAP_VTEP_IN'}->@*, $entry);
+ push($config->{frr}->{routemaps}->{$route_map_in}->@*, $entry);
}
- if (!$config->{frr}->{routemaps}->{'MAP_VTEP_OUT'}) {
+ if (!$config->{frr}->{routemaps}->{$route_map_out}) {
my $entry = { seq => 1, action => "permit" };
$entry->{call} = $plugin_config->{'route-map-out'} if $plugin_config->{'route-map-out'};
- push($config->{frr}->{routemaps}->{'MAP_VTEP_OUT'}->@*, $entry);
+ push($config->{frr}->{routemaps}->{$route_map_out}->@*, $entry);
}
return $config;
@@ -343,6 +361,12 @@ sub generate_zone_frr_config {
{ seq => 1, action => 'permit', network => '::/0', is_ipv6 => 1 },
) if !defined($config->{frr}->{prefix_lists}->{only_default_v6});
+ my $route_map_in = 'MAP_VTEP_IN';
+ $route_map_in .= "_$controller->{'peer-group-name'}" if $controller->{'peer-group-name'};
+
+ my $route_map_out = 'MAP_VTEP_OUT';
+ $route_map_out .= "_$controller->{'peer-group-name'}" if $controller->{'peer-group-name'};
+
if (!$exitnodes_primary || $exitnodes_primary eq $local_node) {
# Filter default route coming from other exit nodes on primary node
my $routemap_config_v6 = {
@@ -351,7 +375,7 @@ sub generate_zone_frr_config {
};
my $routemap_v6 = { seq => 1, matches => [$routemap_config_v6], action => "deny" };
unshift(
- @{ $config->{frr}->{routemaps}->{'MAP_VTEP_IN'} }, $routemap_v6,
+ @{ $config->{frr}->{routemaps}->{$route_map_in} }, $routemap_v6,
);
my $routemap_config = {
@@ -359,7 +383,7 @@ sub generate_zone_frr_config {
value => 'only_default',
};
my $routemap = { seq => 1, matches => [$routemap_config], action => "deny" };
- unshift(@{ $config->{frr}->{routemaps}->{'MAP_VTEP_IN'} }, $routemap);
+ unshift(@{ $config->{frr}->{routemaps}->{$route_map_in} }, $routemap);
} elsif ($exitnodes_primary ne $local_node) {
my $routemap_config_v6 = {
@@ -373,7 +397,7 @@ sub generate_zone_frr_config {
action => "permit",
};
unshift(
- @{ $config->{frr}->{routemaps}->{'MAP_VTEP_OUT'} }, $routemap_v6,
+ @{ $config->{frr}->{routemaps}->{$route_map_out} }, $routemap_v6,
);
my $routemap_config = {
@@ -386,7 +410,7 @@ sub generate_zone_frr_config {
sets => [{ key => 'metric', value => 200 }],
action => "permit",
};
- unshift(@{ $config->{frr}->{routemaps}->{'MAP_VTEP_OUT'} }, $routemap);
+ unshift(@{ $config->{frr}->{routemaps}->{$route_map_out} }, $routemap);
}
if (!$exitnodes_local_routing) {
@@ -493,18 +517,20 @@ sub on_delete_hook {
sub on_update_hook {
my ($class, $controllerid, $controller_cfg) = @_;
- # we can only have 1 evpn controller / 1 asn by server
+ my $controller = $controller_cfg->{ids}->{$controllerid};
- my $controllernb = 0;
foreach my $id (keys %{ $controller_cfg->{ids} }) {
next if $id eq $controllerid;
- my $controller = $controller_cfg->{ids}->{$id};
- next if $controller->{type} ne "evpn";
- $controllernb++;
- die "only 1 global evpn controller can be defined" if $controllernb >= 1;
+ my $other_controller = $controller_cfg->{ids}->{$id};
+ next if $other_controller->{type} ne "evpn";
+
+ die "cannot have two controllers with no peer-group-name configured"
+ if !$controller->{'peer-group-name'} && !$other_controller->{'peer-group-name'};
+
+ die "cannot have two controllers with same peer-group-name configured"
+ if $controller->{'peer-group-name'} eq $other_controller->{'peer-group-name'};
}
- my $controller = $controller_cfg->{ids}->{$controllerid};
if ($controller->{type} eq 'evpn') {
die "must have exactly one of peers / fabric defined"
if ($controller->{peers} && $controller->{fabric})
--
2.47.3
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH pve-network 07/16] evpn controller: add bgp-mode setting
2026-04-14 16:32 [RFC docs/manager/network/proxmox-ve-rs 00/16] Extend EVPN controller functionality Stefan Hanreich
` (5 preceding siblings ...)
2026-04-14 16:33 ` [PATCH pve-network 06/16] evpn controller: allow multiple evpn controllers in a cluster Stefan Hanreich
@ 2026-04-14 16:33 ` Stefan Hanreich
2026-04-14 16:33 ` [PATCH pve-network 08/16] evpn zone: add secondary-controllers and rt filtering Stefan Hanreich
` (8 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Stefan Hanreich @ 2026-04-14 16:33 UTC (permalink / raw)
To: pve-devel
This setting allows to define the type of BGP session (iBGP or eBGP)
in the EVPN controller, analogous to the BGP controller.
Previously, the EVPN controller always inherited the type of BGP
session of the BGP controller, if one was defined. This made it
impossible to have EVPN over iBGP and the underlay via eBGP. By
decoupling the session type of the EVPN controller from the session
type of the BGP controller, a great deal of flexibility is added for
users that want to configure certain setups that were not possible
before.
Additionally, with the previous patches that introduce defining
multiple EVPN controllers, it is now possible to have multiple EVPN
sessions, each with their own BGP semantics. This allows defining EVPN
controllers that handle announcing EVPN routes inside the cluster via
iBGP and then have a different EVPN BGP session for interconnects /
uplinks that utilize eBGP.
To achieve this, introduce a new helper function that determines the
ASN of a node. The new behavior is explained in the documentation of
the function.
In order to preserve backwards-compatibility, the bgp-mode setting
defaults to 'legacy' which keeps the old behavior. The new behavior is
completely opt-in and needs to be explicitly set in the EVPN
controller. If any EVPN controller on a node is defined with legacy
behavior, then the old logic of inheriting the BGP session always has
precendence.
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
src/PVE/API2/Network/SDN/Controllers.pm | 7 ++
src/PVE/Network/SDN/Controllers/BgpPlugin.pm | 17 +++-
src/PVE/Network/SDN/Controllers/EvpnPlugin.pm | 54 +++++++++++--
src/PVE/Network/SDN/Controllers/Plugin.pm | 77 +++++++++++++++++++
4 files changed, 149 insertions(+), 6 deletions(-)
diff --git a/src/PVE/API2/Network/SDN/Controllers.pm b/src/PVE/API2/Network/SDN/Controllers.pm
index 49951a3..e26e0a5 100644
--- a/src/PVE/API2/Network/SDN/Controllers.pm
+++ b/src/PVE/API2/Network/SDN/Controllers.pm
@@ -99,6 +99,13 @@ my $CONTROLLER_PROPERTIES = {
type => 'string',
optional => 1,
},
+ 'bgp-mode' => {
+ description =>
+ "Whether to use eBGP or iBGP. Legacy mode chooses depending on BGP controller or falls back to iBGP.",
+ type => 'string',
+ enum => ['legacy', 'external', 'internal'],
+ optional => 1,
+ },
};
__PACKAGE__->register_method({
diff --git a/src/PVE/Network/SDN/Controllers/BgpPlugin.pm b/src/PVE/Network/SDN/Controllers/BgpPlugin.pm
index 7cbd436..5edbae6 100644
--- a/src/PVE/Network/SDN/Controllers/BgpPlugin.pm
+++ b/src/PVE/Network/SDN/Controllers/BgpPlugin.pm
@@ -83,7 +83,9 @@ sub generate_frr_config {
# Initialize router if not already configured
if (!keys %{$bgp_router}) {
- $bgp_router->{asn} = $asn;
+ $bgp_router->{asn} =
+ PVE::Network::SDN::Controllers::Plugin::get_default_router_asn($local_node,
+ $controller);
$bgp_router->{router_id} = $routerid;
$bgp_router->{default_ipv4_unicast} = 0;
$bgp_router->{coalesce_time} = 1000;
@@ -104,8 +106,21 @@ sub generate_frr_config {
ips => \@peers,
interfaces => [],
};
+
$neighbor_group->{ebgp_multihop} = int($ebgp_multihop) if $ebgp && $ebgp_multihop;
+ if ($asn != int($bgp_router->{asn})) {
+ # should never trigger due to validation, but asserting it here nonetheless
+ die
+ "cannot set local_as to $asn - since this is the default router ASN and therefore an iBGP session"
+ if !$ebgp;
+
+ $neighbor_group->{local_as} = {
+ asn => $asn,
+ mode => 'no-prepend replace-as',
+ };
+ }
+
push @{ $bgp_router->{neighbor_groups} }, $neighbor_group;
# Configure address-family unicast
diff --git a/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm b/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm
index a683dde..db844ff 100644
--- a/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm
+++ b/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm
@@ -44,6 +44,14 @@ sub properties {
optional => 1,
default => 'VTEP',
},
+ 'bgp-mode' => {
+ description =>
+ "Whether to use eBGP or iBGP. Legacy mode chooses depending on BGP controller or falls back to iBGP.",
+ type => 'string',
+ enum => ['legacy', 'external', 'internal'],
+ optional => 1,
+ default => 'legacy',
+ },
};
}
@@ -56,6 +64,7 @@ sub options {
'route-map-out' => { optional => 1 },
'nodes' => { optional => 1 },
'peer-group-name' => { optional => 1 },
+ 'bgp-mode' => { optional => 1 },
};
}
@@ -77,6 +86,7 @@ sub generate_frr_config {
my $autortas = undef;
my $ifaceip = undef;
my $routerid = undef;
+ my $bgp_mode = $plugin_config->{'bgp-mode'} // 'legacy';
my $bgp_controller = find_bgp_controller($local_node, $controller_cfg);
my $isis_controller = find_isis_controller($local_node, $controller_cfg);
@@ -132,10 +142,12 @@ sub generate_frr_config {
return;
}
- if ($bgp_controller) {
+ if ($bgp_controller && $bgp_mode eq 'legacy') {
$ebgp = 1 if $plugin_config->{'asn'} ne $bgp_controller->{asn};
$asn = int($bgp_controller->{asn}) if $bgp_controller->{asn};
$autortas = $plugin_config->{'asn'} if $ebgp;
+ } else {
+ $ebgp = $bgp_mode eq 'external';
}
return if !$asn || !$routerid;
@@ -144,7 +156,9 @@ sub generate_frr_config {
# Initialize router if not already configured
if (!keys %{$bgp_router}) {
- $bgp_router->{asn} = $asn;
+ $bgp_router->{asn} = PVE::Network::SDN::Controllers::Plugin::get_default_router_asn(
+ $local_node, $controller_cfg,
+ );
$bgp_router->{router_id} = $routerid;
$bgp_router->{default_ipv4_unicast} = 0;
$bgp_router->{hard_administrative_reset} = 0;
@@ -166,7 +180,21 @@ sub generate_frr_config {
ips => \@vtep_ips,
interfaces => [],
};
- $neighbor_group->{ebgp_multihop} = 10 if $ebgp && $loopback;
+
+ $neighbor_group->{ebgp_multihop} = 10 if $ebgp && $loopback && $bgp_mode eq 'legacy';
+
+ if ($asn != int($bgp_router->{asn})) {
+ # should never trigger due to validation, but asserting it here nonetheless
+ die
+ "cannot set local_as to $asn - since this is the default router ASN and therefore an iBGP session"
+ if !$ebgp;
+
+ $neighbor_group->{local_as} = {
+ asn => $asn,
+ mode => 'no-prepend replace-as',
+ };
+ }
+
$neighbor_group->{update_source} = $loopback if $loopback;
push @{ $bgp_router->{neighbor_groups} }, $neighbor_group;
@@ -299,7 +327,8 @@ sub generate_zone_frr_config {
# Configure VRF
my $vrf_router = $config->{frr}->{bgp}->{vrf_router}->{$vrf} //= {};
- $vrf_router->{asn} = $asn;
+ $vrf_router->{asn} = PVE::Network::SDN::Controllers::Plugin::get_default_router_asn($local_node,
+ $controller_cfg);
$vrf_router->{router_id} = $routerid;
$vrf_router->{hard_administrative_reset} = 0;
$vrf_router->{graceful_restart_notification} = 0;
@@ -343,7 +372,7 @@ sub generate_zone_frr_config {
$vrf_router->{address_families} = {};
# Configure L2VPN EVPN address family with route targets
- if ($autortas) {
+ if ($autortas && $autortas ne $vrf_router->{asn}) {
$vrf_router->{address_families}->{l2vpn_evpn} //= {};
$vrf_router->{address_families}->{l2vpn_evpn}->{route_targets} = {
import => ["$autortas:$vrfvxlan"],
@@ -519,6 +548,21 @@ sub on_update_hook {
my $controller = $controller_cfg->{ids}->{$controllerid};
+ my @nodes;
+ if (defined($controller->{nodes})) {
+ @nodes = PVE::Tools::split_list($controller->{nodes});
+ } else {
+ @nodes = PVE::Cluster::get_nodelist()->@*;
+ }
+
+ # check if there is a unambiguous default router ASN on every node with the
+ # updated controller - this method dies if there isn't one and we can use
+ # that behavior for validation purposes to avoid re-implementing the same
+ # logic here. For more information see the documentation of the method.
+ for my $node (@nodes) {
+ PVE::Network::SDN::Controllers::Plugin::get_default_router_asn($node, $controller_cfg);
+ }
+
foreach my $id (keys %{ $controller_cfg->{ids} }) {
next if $id eq $controllerid;
my $other_controller = $controller_cfg->{ids}->{$id};
diff --git a/src/PVE/Network/SDN/Controllers/Plugin.pm b/src/PVE/Network/SDN/Controllers/Plugin.pm
index 1068b5d..f9301a3 100644
--- a/src/PVE/Network/SDN/Controllers/Plugin.pm
+++ b/src/PVE/Network/SDN/Controllers/Plugin.pm
@@ -136,4 +136,81 @@ sub get_router_id {
. hex($mac_bytes[5]);
}
+=head3 get_default_router_asn($node_name, \%controller_config)
+
+This function determines the ASN that should be used for the BGP router
+definition in the FRR configuration on node $node_name with the given controller
+configuration \%controller_config.
+
+For backwards-compatibility reasons, this function checks if there is any EVPN
+controller in legacy mode. The initial SDN implementation *always* uses the ASN
+in the BGP controller for its router defintion, if it exists, so return the ASN
+of the BGP controller if one is configured and any EVPN controller uses the
+legacy mode.
+
+The ASN in the router definition defines the ASN of the local node, and is used
+for deriving e.g. Route Targets in EVPN setups. Therefore the configuration
+needs to always use the EVPN ASN in its router definition to ensure correct
+generation for Route Targets (if not using the autort patch).
+
+The FRR config generation logic utilizes the local-as directive for specifying
+alternate ASN numbers. Since local-as is only applicable for eBGP sessions, the
+internal ASN number always needs to be used for the router definition. So if
+there are no EVPN controllers, but iBGP BGP sessions, utilize the ASN configured
+there.
+
+In other cases fallback to the BGP controller ASN, if there is no EVPN
+controller.
+
+Any configuration that has two iBGP sessions with different ASNs is rejected and
+an error thrown, since it is by definition not possible to have two iBGP
+sessions with different ASNs on the same BGP instance, as one instance can only
+have one local ASN.
+
+=cut
+
+sub get_default_router_asn {
+ my ($node_name, $controller_config) = @_;
+
+ my $legacy_asn = undef;
+ my $evpn_asn = undef;
+ my $ibgp_asn = undef;
+
+ my $bgp_controller = PVE::Network::SDN::Controllers::EvpnPlugin::find_bgp_controller(
+ $node_name, $controller_config,
+ );
+
+ if ($bgp_controller && !$bgp_controller->{ebgp}) {
+ $ibgp_asn = $bgp_controller->{asn};
+ }
+
+ for my $controller_id (sort keys $controller_config->{ids}->%*) {
+ my $controller = $controller_config->{ids}->{$controller_id};
+
+ next if $controller->{type} ne 'evpn';
+
+ if (defined($controller->{nodes})) {
+ my @nodes = PVE::Tools::split_list($controller->{nodes});
+ next if !grep { $_ eq $node_name } @nodes;
+ }
+
+ die "all EVPN controllers on a node must have the same ASN configured"
+ if defined($evpn_asn) && $evpn_asn ne $controller->{asn};
+
+ $evpn_asn = $controller->{asn};
+
+ my $bgp_mode = $controller->{'bgp-mode'} // 'legacy';
+ $legacy_asn = $bgp_controller->{asn} if $bgp_mode eq 'legacy' && $bgp_controller;
+
+ next if $bgp_mode eq 'external';
+
+ die "cannot have two different ASNs for iBGP sessions configured"
+ if defined($ibgp_asn) && $ibgp_asn ne $controller->{asn};
+
+ $ibgp_asn = $controller->{asn};
+ }
+
+ return $legacy_asn // $evpn_asn // $ibgp_asn // $bgp_controller->{asn};
+}
+
1;
--
2.47.3
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH pve-network 08/16] evpn zone: add secondary-controllers and rt filtering
2026-04-14 16:32 [RFC docs/manager/network/proxmox-ve-rs 00/16] Extend EVPN controller functionality Stefan Hanreich
` (6 preceding siblings ...)
2026-04-14 16:33 ` [PATCH pve-network 07/16] evpn controller: add bgp-mode setting Stefan Hanreich
@ 2026-04-14 16:33 ` Stefan Hanreich
2026-04-14 16:33 ` [PATCH pve-network 09/16] evpn controller: add ebgp-multihop setting Stefan Hanreich
` (7 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Stefan Hanreich @ 2026-04-14 16:33 UTC (permalink / raw)
To: pve-devel
With the ability to define multiple EVPN controllers, enable the
possibility to assign multiple EVPN controllers to a single zone. This
can be used to announce the L2VPN EVPN routes of a single zone to
multiple peer groups. By having to specify the controllers responsible
for announcing the routes explicitly, one can selectively announce the
routes for a given zone to specific peer groups. Because the EVPN
controller is used to auto-derive VTEP IPs (and the MTU of the
VNets), based on the peer definition in the EVPN controller, it is not
sufficient to extend the existing field and convert it to a list.
Instead, the old controller field now defines the 'primary' controller
for the zone, which is utilized to generate the VTEP IPs and MTU. All
other secondary controllers only announce the L2VPN EVPN routes -
without having any effect on VTEP IP auto-derivation and MTU.
Since the generated FRR configuration uses `advertise-all-vni`, which
advertise all routes from all zones by default (instead of importing
VRFs / route-targets explicitly), it is required to filter outgoing
EVPN routes in order to preserve backwards-compatibility. Otherwise an
EVPN controller configured on a node would announce the routes from
all zones, irregardless of the assigned zones. To prevent this,
outgoing routes for EVPN controllers need to be filtered, based on the
Route Targets of the zones and vnets. This is implemented via route
maps and extcommunity lists. An EVPN controller will only announce
routes with a route target that matches the route target of a zone /
vnet.
To prevent accidental breakage and preserve full
backwards-compatibility, the base case (one EVPN controller in the
cluster) is special cased. Since it was only possible to configure a
single EVPN controller, and EVPN zones need a controller assigned, it
is not required to filter outgoing routes when there is only one EVPN
controller configured. This, in turn, means that only if there are
multiple EVPN controllers configured, RT filtering for outgoing routes
takes place. This makes the new behavior opt-in and the generated FRR
configuration fully backwards-compatible. This is evidenced by the
fact that all existing test cases still work without any changes.
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
src/PVE/API2/Network/SDN/Zones.pm | 11 +++
src/PVE/Network/SDN.pm | 1 +
src/PVE/Network/SDN/Controllers.pm | 71 +++++++++++++++++++
src/PVE/Network/SDN/Controllers/EvpnPlugin.pm | 53 +++++++++++++-
src/PVE/Network/SDN/Zones/EvpnPlugin.pm | 39 ++++++++--
5 files changed, 167 insertions(+), 8 deletions(-)
diff --git a/src/PVE/API2/Network/SDN/Zones.pm b/src/PVE/API2/Network/SDN/Zones.pm
index 8d829a9..cfdc195 100644
--- a/src/PVE/API2/Network/SDN/Zones.pm
+++ b/src/PVE/API2/Network/SDN/Zones.pm
@@ -202,6 +202,17 @@ my $ZONE_PROPERTIES = {
description => "Disable auto mac learning. VLAN zone only.",
optional => 1,
},
+ 'secondary-controllers' => {
+ type => 'array',
+ description => 'Additional controllers.',
+ items => {
+ type => 'string',
+ minLength => 2,
+ maxLength => 64,
+ pattern => '[a-zA-Z][a-zA-Z0-9_-]*[a-zA-Z0-9]',
+ },
+ optional => 1,
+ },
};
__PACKAGE__->register_method({
diff --git a/src/PVE/Network/SDN.pm b/src/PVE/Network/SDN.pm
index 2b3dead..58a21f2 100644
--- a/src/PVE/Network/SDN.pm
+++ b/src/PVE/Network/SDN.pm
@@ -508,6 +508,7 @@ sub encode_value {
|| $key eq 'entries'
|| $key eq 'match'
|| $key eq 'set'
+ || $key eq 'secondary-controllers'
) {
if (ref($value) eq 'HASH') {
return join(',', sort keys(%$value));
diff --git a/src/PVE/Network/SDN/Controllers.pm b/src/PVE/Network/SDN/Controllers.pm
index 3c18552..6167fa2 100644
--- a/src/PVE/Network/SDN/Controllers.pm
+++ b/src/PVE/Network/SDN/Controllers.pm
@@ -103,23 +103,41 @@ sub generate_frr_config {
}
}
+ my $allowed_communities = {};
+
foreach my $id (sort keys %{ $controller_cfg->{ids} }) {
my $plugin_config = $controller_cfg->{ids}->{$id};
my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type});
$plugin->generate_frr_config($plugin_config, $controller_cfg, $id, $uplinks, $frr_config);
+
+ if ($plugin_config->{type} eq 'evpn') {
+ $allowed_communities->{$id} = [];
+ }
}
foreach my $id (sort keys %{ $zone_cfg->{ids} }) {
my $plugin_config = $zone_cfg->{ids}->{$id};
+
my $controllerid = $plugin_config->{controller};
next if !$controllerid;
+
my $controller = $controller_cfg->{ids}->{$controllerid};
+ my $route_target = "$controller->{asn}:$plugin_config->{'vrf-vxlan'}";
+
if ($controller) {
my $controller_plugin =
PVE::Network::SDN::Controllers::Plugin->lookup($controller->{type});
$controller_plugin->generate_zone_frr_config(
$plugin_config, $controller, $controller_cfg, $id, $uplinks, $frr_config,
);
+
+ push $allowed_communities->{$controllerid}->@*, $route_target;
+ }
+
+ if ($plugin_config->{'secondary-controllers'}) {
+ for my $id ($plugin_config->{'secondary-controllers'}->@*) {
+ push $allowed_communities->{$id}->@*, $route_target;
+ }
}
}
@@ -133,12 +151,65 @@ sub generate_frr_config {
next if !$controllerid;
my $controller = $controller_cfg->{ids}->{$controllerid};
+ my $route_target = "$controller->{asn}:$plugin_config->{'tag'}";
+
if ($controller) {
my $controller_plugin =
PVE::Network::SDN::Controllers::Plugin->lookup($controller->{type});
$controller_plugin->generate_vnet_frr_config(
$plugin_config, $controller, $zone, $zoneid, $id, $frr_config,
);
+
+ push $allowed_communities->{$controllerid}->@*, $route_target;
+ }
+
+ if ($zone->{'secondary-controllers'}) {
+ for my $id ($zone->{'secondary-controllers'}->@*) {
+ push $allowed_communities->{$id}->@*, $route_target;
+ }
+ }
+ }
+
+ if (!PVE::Network::SDN::Controllers::EvpnPlugin::skip_route_target_filtering($controller_cfg)) {
+ $frr_config->{frr}->{bgp}->{ext_community_lists} = {};
+
+ for my $controller_id (sort keys $allowed_communities->%*) {
+ my $route_targets = $allowed_communities->{$controller_id};
+ my $community_list_name = "pve_controller_$controller_id";
+
+ if (scalar($route_targets->@*)) {
+ my @entries = map { {
+ action => 'permit',
+ match_entry => {
+ type => 'rt',
+ value => $_,
+ },
+ } } $route_targets->@*;
+
+ $frr_config->{frr}->{bgp}->{ext_community_lists}->{$community_list_name} = {
+ type => 'standard',
+ entries => \@entries,
+ };
+ } else {
+ # Since it's impossible to create empty community lists create a
+ # community list with one deny entry instead. This works,
+ # because the default verdict is to deny any extcommunity that
+ # doesn't match an entry in the community list. So this
+ # extcommunity-list effectively blocks *every* route.
+
+ $frr_config->{frr}->{bgp}->{ext_community_lists}->{$community_list_name} = {
+ type => 'standard',
+ entries => [
+ {
+ action => 'deny',
+ match_entry => {
+ type => 'rt',
+ value => "0:0",
+ },
+ },
+ ],
+ };
+ }
}
}
}
diff --git a/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm b/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm
index db844ff..e9bee33 100644
--- a/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm
+++ b/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm
@@ -68,6 +68,32 @@ sub options {
};
}
+=head3 skip_route_target_filtering(\%controller_config)
+
+When multiple EVPN controllers are configured for multiple zones, outgoing
+routes need to get filtered, since otherwise EVPN controllers would simply
+announce all routes for all zones located on the node. To preserve
+backwards-compatibility and unnecessary filtering when only one EVPN controller
+is defined, this function can be used to check whether route target filtering
+should be performed or not.
+
+=cut
+
+sub skip_route_target_filtering {
+ my ($controller_config) = @_;
+
+ my $evpn_controller = undef;
+
+ for my $controller (values $controller_config->{ids}->%*) {
+ next if $controller->{type} ne 'evpn';
+
+ return 0 if $evpn_controller;
+ $evpn_controller = $controller;
+ }
+
+ return 1;
+}
+
# Plugin implementation
sub generate_frr_config {
my ($class, $plugin_config, $controller_cfg, $id, $uplinks, $config) = @_;
@@ -228,9 +254,26 @@ sub generate_frr_config {
}
if (!$config->{frr}->{routemaps}->{$route_map_out}) {
- my $entry = { seq => 1, action => "permit" };
- $entry->{call} = $plugin_config->{'route-map-out'} if $plugin_config->{'route-map-out'};
+ my $entry = {
+ seq => 1,
+ action => "permit",
+ };
+
+ # only filter outgoing routes if there are multiple EVPN controllers, to
+ # preserve backwards-compatibility
+ if (!skip_route_target_filtering($controller_cfg)) {
+ $entry->{matches} = [
+ {
+ key => 'extcommunity',
+ value => {
+ name => "pve_controller_$id",
+ mode => 'any',
+ },
+ },
+ ];
+ }
+ $entry->{call} = $plugin_config->{'route-map-out'} if $plugin_config->{'route-map-out'};
push($config->{frr}->{routemaps}->{$route_map_out}->@*, $entry);
}
@@ -538,8 +581,14 @@ sub on_delete_hook {
# verify that zone is associated to this controller
foreach my $id (keys %{ $zone_cfg->{ids} }) {
my $zone = $zone_cfg->{ids}->{$id};
+
die "controller $controllerid is used by $id"
if (defined($zone->{controller}) && $zone->{controller} eq $controllerid);
+
+ for my $secondary_controller ($zone->{'secondary-controllers'}->@*) {
+ die "controller $controllerid is used by $id"
+ if $secondary_controller eq $controllerid;
+ }
}
}
diff --git a/src/PVE/Network/SDN/Zones/EvpnPlugin.pm b/src/PVE/Network/SDN/Zones/EvpnPlugin.pm
index 8e7ddfd..5df7e5f 100644
--- a/src/PVE/Network/SDN/Zones/EvpnPlugin.pm
+++ b/src/PVE/Network/SDN/Zones/EvpnPlugin.pm
@@ -53,6 +53,17 @@ sub properties {
type => 'string',
description => 'Controller for this zone.',
},
+ 'secondary-controllers' => {
+ type => 'array',
+ description => 'Additional controllers.',
+ items => {
+ type => 'string',
+ minLength => 2,
+ maxLength => 64,
+ pattern => '[a-zA-Z][a-zA-Z0-9_-]*[a-zA-Z0-9]',
+ },
+ optional => 1,
+ },
'mac' => {
type => 'string',
description => "Anycast logical router mac address.",
@@ -97,6 +108,7 @@ sub options {
nodes => { optional => 1 },
'vrf-vxlan' => { optional => 0 },
controller => { optional => 0 },
+ 'secondary-controllers' => { optional => 1 },
exitnodes => { optional => 1 },
'exitnodes-local-routing' => { optional => 1 },
'exitnodes-primary' => { optional => 1 },
@@ -348,13 +360,28 @@ sub generate_sdn_config {
sub on_update_hook {
my ($class, $zoneid, $zone_cfg, $controller_cfg) = @_;
+ my $controllers = {};
+
# verify that controller exist
- my $controller = $zone_cfg->{ids}->{$zoneid}->{controller};
- if (!defined($controller_cfg->{ids}->{$controller})) {
- die "controller $controller don't exist";
- } else {
- die "$controller is not a evpn controller type"
- if $controller_cfg->{ids}->{$controller}->{type} ne 'evpn';
+ my $zone = $zone_cfg->{ids}->{$zoneid};
+ my $controller = $zone->{controller};
+
+ $controllers->{$controller} = undef;
+
+ for my $secondary_controller ($zone->{'secondary-controllers'}->@*) {
+ die "can not configure the same controller twice"
+ if exists($controllers->{$secondary_controller});
+
+ $controllers->{$secondary_controller} = undef;
+ }
+
+ for my $controller_id (keys $controllers->%*) {
+ if (!defined($controller_cfg->{ids}->{$controller_id})) {
+ die "controller $controller don't exist";
+ } else {
+ die "$controller_id is not a evpn controller type"
+ if $controller_cfg->{ids}->{$controller_id}->{type} ne 'evpn';
+ }
}
#vrf-vxlan need to be defined
--
2.47.3
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH pve-network 09/16] evpn controller: add ebgp-multihop setting
2026-04-14 16:32 [RFC docs/manager/network/proxmox-ve-rs 00/16] Extend EVPN controller functionality Stefan Hanreich
` (7 preceding siblings ...)
2026-04-14 16:33 ` [PATCH pve-network 08/16] evpn zone: add secondary-controllers and rt filtering Stefan Hanreich
@ 2026-04-14 16:33 ` Stefan Hanreich
2026-04-14 16:33 ` [PATCH pve-network 10/16] test: evpn: add test for ibgp + ebgp evpn controller Stefan Hanreich
` (6 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Stefan Hanreich @ 2026-04-14 16:33 UTC (permalink / raw)
To: pve-devel
This setting already exists in the BGP plugin, so it is moved to the
base plugin - so that it can be reused by the EVPN controller as well.
This setting only has an effect if the BGP mode is explicitly set to
eBGP. This setting is required in particular for implementing Inter-AS
Option C, defined in RFC 4364, which requires the use of multihop
eBGP sessions.
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
src/PVE/Network/SDN/Controllers/BgpPlugin.pm | 5 -----
src/PVE/Network/SDN/Controllers/EvpnPlugin.pm | 8 +++++++-
src/PVE/Network/SDN/Controllers/Plugin.pm | 5 +++++
3 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/src/PVE/Network/SDN/Controllers/BgpPlugin.pm b/src/PVE/Network/SDN/Controllers/BgpPlugin.pm
index 5edbae6..b54b566 100644
--- a/src/PVE/Network/SDN/Controllers/BgpPlugin.pm
+++ b/src/PVE/Network/SDN/Controllers/BgpPlugin.pm
@@ -30,11 +30,6 @@ sub properties {
optional => 1,
description => "Enable eBGP (remote-as external).",
},
- 'ebgp-multihop' => {
- type => 'integer',
- optional => 1,
- description => 'Set maximum amount of hops for eBGP peers.',
- },
loopback => {
description => "Name of the loopback/dummy interface that provides the Router-IP.",
type => 'string',
diff --git a/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm b/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm
index e9bee33..ac92ea8 100644
--- a/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm
+++ b/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm
@@ -64,6 +64,7 @@ sub options {
'route-map-out' => { optional => 1 },
'nodes' => { optional => 1 },
'peer-group-name' => { optional => 1 },
+ 'ebgp-multihop' => { optional => 1 },
'bgp-mode' => { optional => 1 },
};
}
@@ -207,7 +208,12 @@ sub generate_frr_config {
interfaces => [],
};
- $neighbor_group->{ebgp_multihop} = 10 if $ebgp && $loopback && $bgp_mode eq 'legacy';
+ if ($bgp_mode eq 'legacy') {
+ $neighbor_group->{ebgp_multihop} = 10 if $ebgp && $loopback;
+ } elsif ($bgp_mode eq 'external') {
+ $neighbor_group->{ebgp_multihop} = int($plugin_config->{'ebgp-multihop'})
+ if $ebgp && $plugin_config->{'ebgp-multihop'};
+ }
if ($asn != int($bgp_router->{asn})) {
# should never trigger due to validation, but asserting it here nonetheless
diff --git a/src/PVE/Network/SDN/Controllers/Plugin.pm b/src/PVE/Network/SDN/Controllers/Plugin.pm
index f9301a3..7cd98ca 100644
--- a/src/PVE/Network/SDN/Controllers/Plugin.pm
+++ b/src/PVE/Network/SDN/Controllers/Plugin.pm
@@ -54,6 +54,11 @@ my $defaultData = {
format => 'pve-sdn-route-map-id',
optional => 1,
},
+ 'ebgp-multihop' => {
+ type => 'integer',
+ optional => 1,
+ description => 'Set maximum amount of hops for eBGP peers.',
+ },
},
};
--
2.47.3
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH pve-network 10/16] test: evpn: add test for ibgp + ebgp evpn controller
2026-04-14 16:32 [RFC docs/manager/network/proxmox-ve-rs 00/16] Extend EVPN controller functionality Stefan Hanreich
` (8 preceding siblings ...)
2026-04-14 16:33 ` [PATCH pve-network 09/16] evpn controller: add ebgp-multihop setting Stefan Hanreich
@ 2026-04-14 16:33 ` Stefan Hanreich
2026-04-14 16:33 ` [PATCH pve-network 11/16] test: evpn: add legacy test Stefan Hanreich
` (5 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Stefan Hanreich @ 2026-04-14 16:33 UTC (permalink / raw)
To: pve-devel
A test for an EVPN-only setup that utilizes iBGP for cluster-local
EVPN BGP, but eBGP for connecting to the outside world via eBGP.
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
.../expected_controller_config | 66 +++++++++++++++++++
.../expected_sdn_interfaces | 41 ++++++++++++
.../evpn_cluster_ibgp_uplink_ebgp/interfaces | 7 ++
.../evpn_cluster_ibgp_uplink_ebgp/sdn_config | 51 ++++++++++++++
4 files changed, 165 insertions(+)
create mode 100644 src/test/zones/evpn/evpn_cluster_ibgp_uplink_ebgp/expected_controller_config
create mode 100644 src/test/zones/evpn/evpn_cluster_ibgp_uplink_ebgp/expected_sdn_interfaces
create mode 100644 src/test/zones/evpn/evpn_cluster_ibgp_uplink_ebgp/interfaces
create mode 100644 src/test/zones/evpn/evpn_cluster_ibgp_uplink_ebgp/sdn_config
diff --git a/src/test/zones/evpn/evpn_cluster_ibgp_uplink_ebgp/expected_controller_config b/src/test/zones/evpn/evpn_cluster_ibgp_uplink_ebgp/expected_controller_config
new file mode 100644
index 0000000..0199425
--- /dev/null
+++ b/src/test/zones/evpn/evpn_cluster_ibgp_uplink_ebgp/expected_controller_config
@@ -0,0 +1,66 @@
+frr version 10.4.1
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp hard-administrative-reset
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ no bgp graceful-restart notification
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ neighbor uplink peer-group
+ neighbor uplink remote-as external
+ neighbor uplink bfd
+ neighbor 198.51.100.1 peer-group uplink
+ neighbor 198.51.100.2 peer-group uplink
+ !
+ address-family l2vpn evpn
+ neighbor VTEP activate
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor uplink activate
+ neighbor uplink route-map MAP_VTEP_IN_uplink in
+ neighbor uplink route-map MAP_VTEP_OUT_uplink out
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+ no bgp hard-administrative-reset
+ no bgp graceful-restart notification
+exit
+!
+bgp extcommunity-list standard pve_controller_cluster permit rt 65000:1000
+bgp extcommunity-list standard pve_controller_cluster permit rt 65000:100
+!
+bgp extcommunity-list standard pve_controller_uplink permit rt 65000:1000
+bgp extcommunity-list standard pve_controller_uplink permit rt 65000:100
+!
+route-map MAP_VTEP_IN permit 1
+exit
+!
+route-map MAP_VTEP_IN_uplink permit 1
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+ match extcommunity pve_controller_cluster any
+exit
+!
+route-map MAP_VTEP_OUT_uplink permit 1
+ match extcommunity pve_controller_uplink any
+exit
+!
+line vty
+!
diff --git a/src/test/zones/evpn/evpn_cluster_ibgp_uplink_ebgp/expected_sdn_interfaces b/src/test/zones/evpn/evpn_cluster_ibgp_uplink_ebgp/expected_sdn_interfaces
new file mode 100644
index 0000000..4cf13e0
--- /dev/null
+++ b/src/test/zones/evpn/evpn_cluster_ibgp_uplink_ebgp/expected_sdn_interfaces
@@ -0,0 +1,41 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
diff --git a/src/test/zones/evpn/evpn_cluster_ibgp_uplink_ebgp/interfaces b/src/test/zones/evpn/evpn_cluster_ibgp_uplink_ebgp/interfaces
new file mode 100644
index 0000000..66bb826
--- /dev/null
+++ b/src/test/zones/evpn/evpn_cluster_ibgp_uplink_ebgp/interfaces
@@ -0,0 +1,7 @@
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/evpn/evpn_cluster_ibgp_uplink_ebgp/sdn_config b/src/test/zones/evpn/evpn_cluster_ibgp_uplink_ebgp/sdn_config
new file mode 100644
index 0000000..6f285ee
--- /dev/null
+++ b/src/test/zones/evpn/evpn_cluster_ibgp_uplink_ebgp/sdn_config
@@ -0,0 +1,51 @@
+{
+ version => 1,
+ zones => {
+ ids => {
+ myzone => {
+ ipam => "pve",
+ type => "evpn",
+ controller => "cluster",
+ 'vrf-vxlan' => 1000,
+ 'secondary-controllers' => ['uplink'],
+ }
+ },
+ },
+ vnets => {
+ ids => {
+ myvnet => {
+ tag => "100",
+ type => "vnet",
+ zone => "myzone"
+ },
+ },
+ },
+ subnets => {
+ ids => {
+ 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ }
+ }
+ },
+ controllers => {
+ ids => {
+ cluster => {
+ type => "evpn",
+ peers => '192.168.0.1,192.168.0.2,192.168.0.3',
+ asn => "65000",
+ 'bgp-mode' => 'internal',
+ },
+ uplink => {
+ type => "evpn",
+ peers => '198.51.100.1,198.51.100.2',
+ asn => "65000",
+ nodes => 'localhost',
+ 'bgp-mode' => 'external',
+ 'peer-group-name' => 'uplink',
+ }
+ },
+ }
+}
+
--
2.47.3
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH pve-network 11/16] test: evpn: add legacy test
2026-04-14 16:32 [RFC docs/manager/network/proxmox-ve-rs 00/16] Extend EVPN controller functionality Stefan Hanreich
` (9 preceding siblings ...)
2026-04-14 16:33 ` [PATCH pve-network 10/16] test: evpn: add test for ibgp + ebgp evpn controller Stefan Hanreich
@ 2026-04-14 16:33 ` Stefan Hanreich
2026-04-14 16:33 ` [PATCH pve-network 12/16] tests: evpn: force ibgp over ebgp bgp controller with ebgp wan session Stefan Hanreich
` (4 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Stefan Hanreich @ 2026-04-14 16:33 UTC (permalink / raw)
To: pve-devel
This is the exact same as the ebgp test - but explicitly sets the
bgp-mode to legacy. The intention of this test is to check if setting
the legacy mode explicitly results in the exact same settings as the
ebgp test, which does not set any bgp-mode so the FRR config
generation automatically falls back to legacy mode.
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
.../legacy_mode/expected_controller_config | 61 +++++++++++++++++++
.../evpn/legacy_mode/expected_sdn_interfaces | 41 +++++++++++++
src/test/zones/evpn/legacy_mode/interfaces | 7 +++
src/test/zones/evpn/legacy_mode/sdn_config | 51 ++++++++++++++++
4 files changed, 160 insertions(+)
create mode 100644 src/test/zones/evpn/legacy_mode/expected_controller_config
create mode 100644 src/test/zones/evpn/legacy_mode/expected_sdn_interfaces
create mode 100644 src/test/zones/evpn/legacy_mode/interfaces
create mode 100644 src/test/zones/evpn/legacy_mode/sdn_config
diff --git a/src/test/zones/evpn/legacy_mode/expected_controller_config b/src/test/zones/evpn/legacy_mode/expected_controller_config
new file mode 100644
index 0000000..f9472b3
--- /dev/null
+++ b/src/test/zones/evpn/legacy_mode/expected_controller_config
@@ -0,0 +1,61 @@
+frr version 10.4.1
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65001
+ bgp router-id 192.168.0.1
+ no bgp hard-administrative-reset
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ no bgp graceful-restart notification
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as external
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ neighbor BGP peer-group
+ neighbor BGP remote-as external
+ neighbor BGP bfd
+ neighbor BGP ebgp-multihop 3
+ neighbor 192.168.0.252 peer-group BGP
+ neighbor 192.168.0.253 peer-group BGP
+ !
+ address-family ipv4 unicast
+ neighbor BGP activate
+ neighbor BGP soft-reconfiguration inbound
+ exit-address-family
+ !
+ address-family l2vpn evpn
+ neighbor VTEP activate
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ advertise-all-vni
+ autort as 65000
+ exit-address-family
+exit
+!
+router bgp 65001 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+ no bgp hard-administrative-reset
+ no bgp graceful-restart notification
+ !
+ address-family l2vpn evpn
+ route-target import 65000:1000
+ route-target export 65000:1000
+ exit-address-family
+exit
+!
+route-map MAP_VTEP_IN permit 1
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+line vty
+!
diff --git a/src/test/zones/evpn/legacy_mode/expected_sdn_interfaces b/src/test/zones/evpn/legacy_mode/expected_sdn_interfaces
new file mode 100644
index 0000000..4cf13e0
--- /dev/null
+++ b/src/test/zones/evpn/legacy_mode/expected_sdn_interfaces
@@ -0,0 +1,41 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
diff --git a/src/test/zones/evpn/legacy_mode/interfaces b/src/test/zones/evpn/legacy_mode/interfaces
new file mode 100644
index 0000000..66bb826
--- /dev/null
+++ b/src/test/zones/evpn/legacy_mode/interfaces
@@ -0,0 +1,7 @@
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/evpn/legacy_mode/sdn_config b/src/test/zones/evpn/legacy_mode/sdn_config
new file mode 100644
index 0000000..975882e
--- /dev/null
+++ b/src/test/zones/evpn/legacy_mode/sdn_config
@@ -0,0 +1,51 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => {
+ tag => "100",
+ type => "vnet",
+ zone => "myzone",
+ },
+ },
+ },
+
+ zones => {
+ ids => {
+ myzone => {
+ ipam => "pve",
+ type => "evpn",
+ controller => "evpnctl",
+ 'vrf-vxlan' => 1000,
+ },
+ },
+ },
+ controllers => {
+ ids => {
+ evpnctl => {
+ type => "evpn",
+ 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3',
+ asn => "65000",
+ 'bgp-mode' => 'legacy',
+ },
+ localhost => {
+ type => "bgp",
+ 'peers' => '192.168.0.252,192.168.0.253',
+ ebgp => "1",
+ 'ebgp-multihop' => '3',
+ asn => "65001",
+ node => "localhost",
+ },
+ },
+ },
+
+ subnets => {
+ ids => {
+ 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ },
+ },
+ },
+}
--
2.47.3
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH pve-network 12/16] tests: evpn: force ibgp over ebgp bgp controller with ebgp wan session
2026-04-14 16:32 [RFC docs/manager/network/proxmox-ve-rs 00/16] Extend EVPN controller functionality Stefan Hanreich
` (10 preceding siblings ...)
2026-04-14 16:33 ` [PATCH pve-network 11/16] test: evpn: add legacy test Stefan Hanreich
@ 2026-04-14 16:33 ` Stefan Hanreich
2026-04-14 16:33 ` [PATCH pve-network 13/16] tests: test route filtering mechanism with multiple zones/controllers Stefan Hanreich
` (3 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Stefan Hanreich @ 2026-04-14 16:33 UTC (permalink / raw)
To: pve-devel
The new bgp-mode setting allows explicitly overriding the previous
behavior of re-using the existing BGP session from the BGP controller.
Test this by explicitly running an iBGP session over the eBGP mode BGP
controller. Add another EVPN controller that utilizes eBGP for
communicating with an external AS to test the combination of all 3
controllers on a single node.
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
.../expected_controller_config | 87 +++++++++++++++++++
.../expected_sdn_interfaces | 41 +++++++++
.../ebgp_evpn_ibgp_wan_evpn_ebgp/interfaces | 11 +++
.../ebgp_evpn_ibgp_wan_evpn_ebgp/sdn_config | 59 +++++++++++++
4 files changed, 198 insertions(+)
create mode 100644 src/test/zones/evpn/ebgp_evpn_ibgp_wan_evpn_ebgp/expected_controller_config
create mode 100644 src/test/zones/evpn/ebgp_evpn_ibgp_wan_evpn_ebgp/expected_sdn_interfaces
create mode 100644 src/test/zones/evpn/ebgp_evpn_ibgp_wan_evpn_ebgp/interfaces
create mode 100644 src/test/zones/evpn/ebgp_evpn_ibgp_wan_evpn_ebgp/sdn_config
diff --git a/src/test/zones/evpn/ebgp_evpn_ibgp_wan_evpn_ebgp/expected_controller_config b/src/test/zones/evpn/ebgp_evpn_ibgp_wan_evpn_ebgp/expected_controller_config
new file mode 100644
index 0000000..4328515
--- /dev/null
+++ b/src/test/zones/evpn/ebgp_evpn_ibgp_wan_evpn_ebgp/expected_controller_config
@@ -0,0 +1,87 @@
+frr version 10.4.1
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 203.0.113.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ bgp disable-ebgp-connected-route-check
+ neighbor BGP peer-group
+ neighbor BGP remote-as external
+ neighbor BGP local-as 65001 no-prepend replace-as
+ neighbor BGP bfd
+ neighbor 198.51.100.10 peer-group BGP
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor VTEP update-source dummy1
+ neighbor 203.0.113.2 peer-group VTEP
+ neighbor 203.0.113.4 peer-group VTEP
+ neighbor wan peer-group
+ neighbor wan remote-as external
+ neighbor wan bfd
+ neighbor wan update-source dummy1
+ neighbor 203.0.113.100 peer-group wan
+ neighbor 203.0.113.101 peer-group wan
+ !
+ address-family ipv4 unicast
+ network 203.0.113.1/32
+ neighbor BGP activate
+ neighbor BGP soft-reconfiguration inbound
+ exit-address-family
+ !
+ address-family l2vpn evpn
+ neighbor VTEP activate
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor wan activate
+ neighbor wan route-map MAP_VTEP_IN_wan in
+ neighbor wan route-map MAP_VTEP_OUT_wan out
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 203.0.113.1
+ no bgp hard-administrative-reset
+ no bgp graceful-restart notification
+exit
+!
+bgp extcommunity-list standard pve_controller_cluster permit rt 65000:1000
+bgp extcommunity-list standard pve_controller_cluster permit rt 65000:100
+!
+bgp extcommunity-list standard pve_controller_wan permit rt 65000:1000
+bgp extcommunity-list standard pve_controller_wan permit rt 65000:100
+!
+ip prefix-list loopbacks_ips seq 10 permit 0.0.0.0/0 le 32
+!
+route-map MAP_VTEP_IN permit 1
+exit
+!
+route-map MAP_VTEP_IN_wan permit 1
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+ match extcommunity pve_controller_cluster any
+exit
+!
+route-map MAP_VTEP_OUT_wan permit 1
+ match extcommunity pve_controller_wan any
+exit
+!
+route-map correct_src permit 1
+ match ip address prefix-list loopbacks_ips
+ set src 203.0.113.1
+exit
+!
+ip protocol bgp route-map correct_src
+!
+line vty
+!
diff --git a/src/test/zones/evpn/ebgp_evpn_ibgp_wan_evpn_ebgp/expected_sdn_interfaces b/src/test/zones/evpn/ebgp_evpn_ibgp_wan_evpn_ebgp/expected_sdn_interfaces
new file mode 100644
index 0000000..7481d36
--- /dev/null
+++ b/src/test/zones/evpn/ebgp_evpn_ibgp_wan_evpn_ebgp/expected_sdn_interfaces
@@ -0,0 +1,41 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 203.0.113.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 203.0.113.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
diff --git a/src/test/zones/evpn/ebgp_evpn_ibgp_wan_evpn_ebgp/interfaces b/src/test/zones/evpn/ebgp_evpn_ibgp_wan_evpn_ebgp/interfaces
new file mode 100644
index 0000000..1736ffb
--- /dev/null
+++ b/src/test/zones/evpn/ebgp_evpn_ibgp_wan_evpn_ebgp/interfaces
@@ -0,0 +1,11 @@
+auto vmbr0
+iface vmbr0 inet static
+ address 198.51.100.1/24
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
+
+auto dummy1
+iface dummy1 inet static
+ address 203.0.113.1/32
+ link-type dummy
diff --git a/src/test/zones/evpn/ebgp_evpn_ibgp_wan_evpn_ebgp/sdn_config b/src/test/zones/evpn/ebgp_evpn_ibgp_wan_evpn_ebgp/sdn_config
new file mode 100644
index 0000000..c78a434
--- /dev/null
+++ b/src/test/zones/evpn/ebgp_evpn_ibgp_wan_evpn_ebgp/sdn_config
@@ -0,0 +1,59 @@
+{
+ version => 1,
+ zones => {
+ ids => {
+ myzone => {
+ ipam => "pve",
+ type => "evpn",
+ controller => "cluster",
+ 'vrf-vxlan' => 1000,
+ 'secondary-controllers' => ['wan'],
+ }
+ },
+ },
+ vnets => {
+ ids => {
+ myvnet => {
+ tag => "100",
+ type => "vnet",
+ zone => "myzone"
+ },
+ },
+ },
+ subnets => {
+ ids => {
+ 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ }
+ }
+ },
+ controllers => {
+ ids => {
+ cluster => {
+ type => "evpn",
+ peers => '203.0.113.1,203.0.113.2,203.0.113.4',
+ asn => "65000",
+ 'bgp-mode' => 'internal',
+ },
+ bgp => {
+ type => "bgp",
+ 'peers' => '198.51.100.10',
+ ebgp => "1",
+ asn => "65001",
+ loopback => 'dummy1',
+ node => "localhost",
+ },
+ wan => {
+ type => "evpn",
+ peers => '203.0.113.100,203.0.113.101',
+ asn => "65000",
+ nodes => 'localhost',
+ 'bgp-mode' => 'external',
+ 'peer-group-name' => 'wan',
+ }
+ },
+ }
+}
+
--
2.47.3
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH pve-network 13/16] tests: test route filtering mechanism with multiple zones/controllers
2026-04-14 16:32 [RFC docs/manager/network/proxmox-ve-rs 00/16] Extend EVPN controller functionality Stefan Hanreich
` (11 preceding siblings ...)
2026-04-14 16:33 ` [PATCH pve-network 12/16] tests: evpn: force ibgp over ebgp bgp controller with ebgp wan session Stefan Hanreich
@ 2026-04-14 16:33 ` Stefan Hanreich
2026-04-14 16:33 ` [PATCH pve-manager 14/16] sdn: evpn: zone: controller: add new advanced fields Stefan Hanreich
` (2 subsequent siblings)
15 siblings, 0 replies; 17+ messages in thread
From: Stefan Hanreich @ 2026-04-14 16:33 UTC (permalink / raw)
To: pve-devel
When utilizing multiple controllers and assigning them to EVPN zones
selectively, the outgoing routes get filtered - so EVPN controllers
only announce routes of zones/vnets they are explicitly assigned to.
This test case creates two zones and assigns one controller to both,
another controller to only one zone to test the outgoing route
filtering mechanism.
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
.../expected_controller_config | 78 ++++++++++++++++++
.../expected_sdn_interfaces | 81 +++++++++++++++++++
.../interfaces | 7 ++
.../sdn_config | 67 +++++++++++++++
4 files changed, 233 insertions(+)
create mode 100644 src/test/zones/evpn/evpn_multiple_zones_cluster_ibgp_uplink_ebgp/expected_controller_config
create mode 100644 src/test/zones/evpn/evpn_multiple_zones_cluster_ibgp_uplink_ebgp/expected_sdn_interfaces
create mode 100644 src/test/zones/evpn/evpn_multiple_zones_cluster_ibgp_uplink_ebgp/interfaces
create mode 100644 src/test/zones/evpn/evpn_multiple_zones_cluster_ibgp_uplink_ebgp/sdn_config
diff --git a/src/test/zones/evpn/evpn_multiple_zones_cluster_ibgp_uplink_ebgp/expected_controller_config b/src/test/zones/evpn/evpn_multiple_zones_cluster_ibgp_uplink_ebgp/expected_controller_config
new file mode 100644
index 0000000..3d5ec9d
--- /dev/null
+++ b/src/test/zones/evpn/evpn_multiple_zones_cluster_ibgp_uplink_ebgp/expected_controller_config
@@ -0,0 +1,78 @@
+frr version 10.4.1
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+vrf vrf_myzone2
+ vni 2000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp hard-administrative-reset
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ no bgp graceful-restart notification
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ neighbor uplink peer-group
+ neighbor uplink remote-as external
+ neighbor uplink bfd
+ neighbor 198.51.100.1 peer-group uplink
+ neighbor 198.51.100.2 peer-group uplink
+ !
+ address-family l2vpn evpn
+ neighbor VTEP activate
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor uplink activate
+ neighbor uplink route-map MAP_VTEP_IN_uplink in
+ neighbor uplink route-map MAP_VTEP_OUT_uplink out
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+ no bgp hard-administrative-reset
+ no bgp graceful-restart notification
+exit
+!
+router bgp 65000 vrf vrf_myzone2
+ bgp router-id 192.168.0.1
+ no bgp hard-administrative-reset
+ no bgp graceful-restart notification
+exit
+!
+bgp extcommunity-list standard pve_controller_cluster permit rt 65000:1000
+bgp extcommunity-list standard pve_controller_cluster permit rt 65000:2000
+bgp extcommunity-list standard pve_controller_cluster permit rt 65000:100
+bgp extcommunity-list standard pve_controller_cluster permit rt 65000:200
+!
+bgp extcommunity-list standard pve_controller_uplink permit rt 65000:1000
+bgp extcommunity-list standard pve_controller_uplink permit rt 65000:100
+!
+route-map MAP_VTEP_IN permit 1
+exit
+!
+route-map MAP_VTEP_IN_uplink permit 1
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+ match extcommunity pve_controller_cluster any
+exit
+!
+route-map MAP_VTEP_OUT_uplink permit 1
+ match extcommunity pve_controller_uplink any
+exit
+!
+line vty
+!
diff --git a/src/test/zones/evpn/evpn_multiple_zones_cluster_ibgp_uplink_ebgp/expected_sdn_interfaces b/src/test/zones/evpn/evpn_multiple_zones_cluster_ibgp_uplink_ebgp/expected_sdn_interfaces
new file mode 100644
index 0000000..88706b7
--- /dev/null
+++ b/src/test/zones/evpn/evpn_multiple_zones_cluster_ibgp_uplink_ebgp/expected_sdn_interfaces
@@ -0,0 +1,81 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto myvnet2
+iface myvnet2
+ address 10.0.0.1/24
+ bridge_ports vxlan_myvnet2
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone2
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrf_myzone2
+iface vrf_myzone2
+ vrf-table auto
+ post-up ip route add vrf vrf_myzone2 unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfbr_myzone2
+iface vrfbr_myzone2
+ bridge-ports vrfvx_myzone2
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone2
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vrfvx_myzone2
+iface vrfvx_myzone2
+ vxlan-id 2000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet2
+iface vxlan_myvnet2
+ vxlan-id 200
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
diff --git a/src/test/zones/evpn/evpn_multiple_zones_cluster_ibgp_uplink_ebgp/interfaces b/src/test/zones/evpn/evpn_multiple_zones_cluster_ibgp_uplink_ebgp/interfaces
new file mode 100644
index 0000000..66bb826
--- /dev/null
+++ b/src/test/zones/evpn/evpn_multiple_zones_cluster_ibgp_uplink_ebgp/interfaces
@@ -0,0 +1,7 @@
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/evpn/evpn_multiple_zones_cluster_ibgp_uplink_ebgp/sdn_config b/src/test/zones/evpn/evpn_multiple_zones_cluster_ibgp_uplink_ebgp/sdn_config
new file mode 100644
index 0000000..e26fd42
--- /dev/null
+++ b/src/test/zones/evpn/evpn_multiple_zones_cluster_ibgp_uplink_ebgp/sdn_config
@@ -0,0 +1,67 @@
+{
+ version => 1,
+ zones => {
+ ids => {
+ myzone => {
+ ipam => "pve",
+ type => "evpn",
+ controller => "cluster",
+ 'vrf-vxlan' => 1000,
+ 'secondary-controllers' => ['uplink'],
+ },
+ myzone2 => {
+ ipam => "pve",
+ type => "evpn",
+ controller => "cluster",
+ 'vrf-vxlan' => 2000,
+ }
+ },
+ },
+ vnets => {
+ ids => {
+ myvnet => {
+ tag => "100",
+ type => "vnet",
+ zone => "myzone"
+ },
+ myvnet2 => {
+ tag => "200",
+ type => "vnet",
+ zone => "myzone2"
+ },
+ },
+ },
+ subnets => {
+ ids => {
+ 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ },
+ 'myzone2-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet2',
+ 'gateway' => '10.0.0.1',
+ },
+ }
+ },
+ controllers => {
+ ids => {
+ cluster => {
+ type => "evpn",
+ peers => '192.168.0.1,192.168.0.2,192.168.0.3',
+ asn => "65000",
+ 'bgp-mode' => 'internal',
+ },
+ uplink => {
+ type => "evpn",
+ peers => '198.51.100.1,198.51.100.2',
+ asn => "65000",
+ nodes => 'localhost',
+ 'bgp-mode' => 'external',
+ 'peer-group-name' => 'uplink',
+ }
+ },
+ }
+}
+
--
2.47.3
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH pve-manager 14/16] sdn: evpn: zone: controller: add new advanced fields
2026-04-14 16:32 [RFC docs/manager/network/proxmox-ve-rs 00/16] Extend EVPN controller functionality Stefan Hanreich
` (12 preceding siblings ...)
2026-04-14 16:33 ` [PATCH pve-network 13/16] tests: test route filtering mechanism with multiple zones/controllers Stefan Hanreich
@ 2026-04-14 16:33 ` Stefan Hanreich
2026-04-14 16:33 ` [PATCH pve-docs 15/16] sdn: evpn: document new zone / controller options Stefan Hanreich
2026-04-14 16:33 ` [PATCH pve-docs 16/16] sdn: fix typo in bgp controller Stefan Hanreich
15 siblings, 0 replies; 17+ messages in thread
From: Stefan Hanreich @ 2026-04-14 16:33 UTC (permalink / raw)
To: pve-devel
Several new fields for configuring additional aspects of the EVPN
controller/zone have been added to the backend. Expose them here
additionally as advanced fields. For more information on the
functionality of the fields, see the respective commits in
pve-network.
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
www/manager6/sdn/controllers/EvpnEdit.js | 39 ++++++++++++++++++++++++
www/manager6/sdn/zones/EvpnEdit.js | 14 ++++++++-
2 files changed, 52 insertions(+), 1 deletion(-)
diff --git a/www/manager6/sdn/controllers/EvpnEdit.js b/www/manager6/sdn/controllers/EvpnEdit.js
index 67c41f476..75e544540 100644
--- a/www/manager6/sdn/controllers/EvpnEdit.js
+++ b/www/manager6/sdn/controllers/EvpnEdit.js
@@ -63,6 +63,45 @@ Ext.define('PVE.sdn.controllers.EvpnInputPanel', {
},
];
+ me.advancedItems = [
+ {
+ xtype: 'textfield',
+ name: 'peer-group-name',
+ fieldLabel: gettext('Peer Group Name'),
+ allowBlank: true,
+ deleteEmpty: !me.isCreate,
+ },
+ {
+ xtype: 'pveNodeSelector',
+ name: 'nodes',
+ fieldLabel: gettext('Nodes'),
+ multiSelect: true,
+ autoSelect: false,
+ allowBlank: true,
+ deleteEmpty: !me.isCreate,
+ skipEmptyText: true,
+ },
+ {
+ xtype: 'proxmoxKVComboBox',
+ name: 'bgp-mode',
+ value: '',
+ emptyText: 'Legacy',
+ comboItems: [['legacy', gettext('Legacy')], ['external', gettext('eBGP')], ['internal', gettext('iBGP')]],
+ fieldLabel: gettext('BGP mode'),
+ allowBlank: true,
+ deleteEmpty: !me.isCreate,
+ },
+ {
+ xtype: 'proxmoxintegerfield',
+ name: 'ebgp-multihop',
+ minValue: 1,
+ maxValue: 100,
+ fieldLabel: 'ebgp-multihop',
+ allowBlank: true,
+ deleteEmpty: !me.isCreate,
+ },
+ ];
+
me.callParent();
},
});
diff --git a/www/manager6/sdn/zones/EvpnEdit.js b/www/manager6/sdn/zones/EvpnEdit.js
index 63acb8811..511eda329 100644
--- a/www/manager6/sdn/zones/EvpnEdit.js
+++ b/www/manager6/sdn/zones/EvpnEdit.js
@@ -19,7 +19,7 @@ Ext.define('PVE.sdn.zones.EvpnInputPanel', {
me.items = [
{
xtype: 'pveSDNControllerSelector',
- fieldLabel: gettext('Controller'),
+ fieldLabel: gettext('Primary Controller'),
name: 'controller',
value: '',
allowBlank: false,
@@ -90,6 +90,18 @@ Ext.define('PVE.sdn.zones.EvpnInputPanel', {
},
];
+ me.advancedItems = [
+ {
+ xtype: 'pveSDNControllerSelector',
+ fieldLabel: gettext('additional Controllers'),
+ name: 'secondary-controllers',
+ allowBlank: true,
+ multiSelect: true,
+ skipEmptyText: true,
+ deleteEmpty: !me.isCreate,
+ },
+ ];
+
me.callParent();
},
});
--
2.47.3
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH pve-docs 15/16] sdn: evpn: document new zone / controller options
2026-04-14 16:32 [RFC docs/manager/network/proxmox-ve-rs 00/16] Extend EVPN controller functionality Stefan Hanreich
` (13 preceding siblings ...)
2026-04-14 16:33 ` [PATCH pve-manager 14/16] sdn: evpn: zone: controller: add new advanced fields Stefan Hanreich
@ 2026-04-14 16:33 ` Stefan Hanreich
2026-04-14 16:33 ` [PATCH pve-docs 16/16] sdn: fix typo in bgp controller Stefan Hanreich
15 siblings, 0 replies; 17+ messages in thread
From: Stefan Hanreich @ 2026-04-14 16:33 UTC (permalink / raw)
To: pve-devel
Several new options have been added to the EVPN controller / zones.
Additionally, document the existing behavior of the EVPN stack more
thoroughly, particularly the ASN generation - since it is required for
understanding the implications of setting the new 'bgp-mode' setting
for EVPN controllers.
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
pvesdn.adoc | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 69 insertions(+), 3 deletions(-)
diff --git a/pvesdn.adoc b/pvesdn.adoc
index d20a0eb..5eccbea 100644
--- a/pvesdn.adoc
+++ b/pvesdn.adoc
@@ -331,8 +331,13 @@ EVPN zone configuration options:
VRF VXLAN ID:: A VXLAN-ID used for dedicated routing interconnect between VNets.
It must be different than the VXLAN-ID of the VNets.
-Controller:: The EVPN-controller to use for this zone. (See controller plugins
- section).
+Primay Controller:: The primary EVPN-controller to use for this zone.
+ The primary controller is used for auto-deriving the VTEP IPs, based on the
+ peer definition. For more information see the
+ xref:pvesdn_controller_plugin_evpn[controller section].
+
+Addtional Controllers:: Additional EVPN controllers to use for this zone.
+ Controllers listed here will announce the L2VPN EVPN routes for this zone.
VNet MAC Address:: Anycast MAC address that gets assigned to all VNets in this
zone. Will be auto-generated if not defined.
@@ -462,7 +467,8 @@ EVPN controller configuration options:
ASN #:: A unique BGP ASN number. It's highly recommended to use a private ASN
number (64512 – 65534, 4200000000 – 4294967294), as otherwise you could end up
- breaking global routing by mistake.
+ breaking global routing by mistake. Every EVPN controller configured on a node
+ must have the same ASN configured.
SDN Fabric:: A xref:pvesdn_config_fabrics[Fabric] that contains all the nodes
part of the EVPN zone. Will be used as the underlay network.
@@ -470,6 +476,66 @@ SDN Fabric:: A xref:pvesdn_config_fabrics[Fabric] that contains all the nodes
Peers:: An IP list of all nodes that are part of the EVPN zone. (could also be
external nodes or route reflector servers)
+Nodes:: A list of nodes where this controller should be active. This can be used
+ to configure EVPN peering sessions on specific nodes only.
+
+Peer Group Name:: EVPN controllers use the peer group name 'VTEP' by default.
+ When defining multiple EVPN controllers, each additional controller needs to
+ specify a custom peer group name to be used in the FRR configuration.
+
+BGP Mode:: Allows configuring the type of BGP session manually. The 'legacy'
+ mode preserves the old behavior of the SDN stack: EVPN controllers were iBGP,
+ unless there was a BGP controller configured. In that case the EVPN controller
+ re-used the BGP session from the BGP controller. With this option, the type of
+ BGP session can be overridden manually - allowing for explicitly configuring
+ the type of BGP session that should be established with the EVPN controller.
+
+ebgp-multihop:: Increase the number of hops to reach peers, in case they are
+ not directly connected or they use loopback. (only for BGP Mode 'external').
+
+ASN
+^^^
+Every router can be member of exactly one Autonomous System, so any iBGP
+sessions on a given node (irregardless of address family) need to have the same
+ASN configured. Any configuration that has two iBGP sessions with different ASNs
+is rejected and an error thrown, since it is by definition not possible to have
+two iBGP sessions with different ASNs on the same BGP instance, as one instance
+can only have one local ASN.
+
+The local ASN of the FRR instance is auto-derived based on the
+settings in the SDN controllers:
+
+If only a BGP or EVPN controller exists (but not both), then the respective ASN
+from the controller is used.
+
+For backwards-compatibility reasons, the SDN stack checks if there are any EVPN
+controllers in legacy mode. If a BGP controller exists, then the ASN from the
+BGP controller will be used in any case.
+
+Otherwise, if an EVPN controller is configured, then the ASN from the EVPN
+controller is used - otherwise the ASN from the BGP controller.
+
+The SDN stack utilizes the local-as directive together with the `no-prepend
+replace-as` option in the FRR configuration for handling multiple ASN numbers in
+the configuration. This is the case when the BGP and EVPN controller have
+different ASNs configured and legacy mode is disabled.
+
+
+VTEP IP auto-generation
+^^^^^^^^^^^^^^^^^^^^^^^
+The VTEP IPs for the VXLAN devices of a zone will be auto-derived based on the
+(primary) controller defined in the EVPN zone:
+
+If a BGP or IS-IS controller with a loopback interface is used, then the IP from
+the loopback interface will be used as VTEP IP.
+
+If the peer list of the controller contains an IP that is locally configured on
+the node, then this IP will be used as VTEP IP.
+
+Otherwise, the EVPN controller will check the route in the default routing table
+for the first peer IP and use the source IP address specified in the routing
+table as VTEP IP.
+
[[pvesdn_controller_plugin_BGP]]
BGP Controller
--
2.47.3
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH pve-docs 16/16] sdn: fix typo in bgp controller
2026-04-14 16:32 [RFC docs/manager/network/proxmox-ve-rs 00/16] Extend EVPN controller functionality Stefan Hanreich
` (14 preceding siblings ...)
2026-04-14 16:33 ` [PATCH pve-docs 15/16] sdn: evpn: document new zone / controller options Stefan Hanreich
@ 2026-04-14 16:33 ` Stefan Hanreich
15 siblings, 0 replies; 17+ messages in thread
From: Stefan Hanreich @ 2026-04-14 16:33 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
pvesdn.adoc | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pvesdn.adoc b/pvesdn.adoc
index 5eccbea..8f955e8 100644
--- a/pvesdn.adoc
+++ b/pvesdn.adoc
@@ -566,7 +566,7 @@ EBGP:: If your peer's remote-AS is different, this enables EBGP.
Loopback Interface:: Use a loopback or dummy interface as the source of the EVPN network
(for multipath).
-ebgp-mutltihop:: Increase the number of hops to reach peers, in case they are
+ebgp-multihop:: Increase the number of hops to reach peers, in case they are
not directly connected or they use loopback.
bgp-multipath-as-path-relax:: Allow ECMP if your peers have different ASN.
--
2.47.3
^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2026-04-14 16:35 UTC | newest]
Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-04-14 16:32 [RFC docs/manager/network/proxmox-ve-rs 00/16] Extend EVPN controller functionality Stefan Hanreich
2026-04-14 16:32 ` [PATCH proxmox-ve-rs 01/16] frr: add local-as setting Stefan Hanreich
2026-04-14 16:32 ` [PATCH proxmox-ve-rs 02/16] frr: add support for extcommunity lists Stefan Hanreich
2026-04-14 16:33 ` [PATCH proxmox-ve-rs 03/16] frr-templates: render local-as setting Stefan Hanreich
2026-04-14 16:33 ` [PATCH proxmox-ve-rs 04/16] frr-templates: render community lists in templates Stefan Hanreich
2026-04-14 16:33 ` [PATCH pve-network 05/16] evpn controller: make nodes configurable Stefan Hanreich
2026-04-14 16:33 ` [PATCH pve-network 06/16] evpn controller: allow multiple evpn controllers in a cluster Stefan Hanreich
2026-04-14 16:33 ` [PATCH pve-network 07/16] evpn controller: add bgp-mode setting Stefan Hanreich
2026-04-14 16:33 ` [PATCH pve-network 08/16] evpn zone: add secondary-controllers and rt filtering Stefan Hanreich
2026-04-14 16:33 ` [PATCH pve-network 09/16] evpn controller: add ebgp-multihop setting Stefan Hanreich
2026-04-14 16:33 ` [PATCH pve-network 10/16] test: evpn: add test for ibgp + ebgp evpn controller Stefan Hanreich
2026-04-14 16:33 ` [PATCH pve-network 11/16] test: evpn: add legacy test Stefan Hanreich
2026-04-14 16:33 ` [PATCH pve-network 12/16] tests: evpn: force ibgp over ebgp bgp controller with ebgp wan session Stefan Hanreich
2026-04-14 16:33 ` [PATCH pve-network 13/16] tests: test route filtering mechanism with multiple zones/controllers Stefan Hanreich
2026-04-14 16:33 ` [PATCH pve-manager 14/16] sdn: evpn: zone: controller: add new advanced fields Stefan Hanreich
2026-04-14 16:33 ` [PATCH pve-docs 15/16] sdn: evpn: document new zone / controller options Stefan Hanreich
2026-04-14 16:33 ` [PATCH pve-docs 16/16] sdn: fix typo in bgp controller Stefan Hanreich
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.