From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 4B57F1FF136 for ; Mon, 04 May 2026 18:08:57 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 2866B41F3; Mon, 4 May 2026 18:05:34 +0200 (CEST) From: Stefan Hanreich To: pve-devel@lists.proxmox.com Subject: [PATCH pve-network v4 30/47] evpn controller: add route_map_{in,out} parameter Date: Mon, 4 May 2026 18:03:27 +0200 Message-ID: <20260504160350.395470-31-s.hanreich@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260504160350.395470-1-s.hanreich@proxmox.com> References: <20260504160350.395470-1-s.hanreich@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1777910534707 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.671 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: GKBHU6YUWUCEME4IU5F7UV5OIV3ZFIXX X-Message-ID-Hash: GKBHU6YUWUCEME4IU5F7UV5OIV3ZFIXX X-MailFrom: s.hanreich@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: This parameter allows extending the default MAP_VTEP_{IN,OUT} route maps by specifying a custom route map configured in route-maps.cfg. This can be used for filtering incoming and outgoing routes, e.g. for only advertising type-5 routes to external peers or only allow importing routes with specific route targets. The old default route maps are kept around in order to support the exit nodes directive of the EVPN zone. They're still used for filtering the default routes from other exit nodes and for setting the metric of non-primary default routes. If a route map override is configured, an additional call action gets inserted into the auto-generated route map that jumps into the user-supplied route map, after the entries handling the default routes are created. Signed-off-by: Stefan Hanreich --- .../Network/SDN/RouteMaps/RouteMapEntry.pm | 6 ++++ src/PVE/Network/SDN/Controllers/EvpnPlugin.pm | 30 ++++++++++++++++--- src/PVE/Network/SDN/Controllers/Plugin.pm | 14 +++++++++ src/PVE/Network/SDN/RouteMaps.pm | 20 +++++++++++++ 4 files changed, 66 insertions(+), 4 deletions(-) diff --git a/src/PVE/API2/Network/SDN/RouteMaps/RouteMapEntry.pm b/src/PVE/API2/Network/SDN/RouteMaps/RouteMapEntry.pm index 6f9ca08..1e5373b 100644 --- a/src/PVE/API2/Network/SDN/RouteMaps/RouteMapEntry.pm +++ b/src/PVE/API2/Network/SDN/RouteMaps/RouteMapEntry.pm @@ -5,6 +5,7 @@ use warnings; use PVE::Exception qw(raise_param_exc); use PVE::JSONSchema qw(get_standard_option); +use PVE::Network::SDN::RouteMaps; use PVE::Tools qw(extract_param); use PVE::RESTHandler; @@ -127,6 +128,11 @@ __PACKAGE__->register_method({ my $order = extract_param($param, 'order'); $config->delete($route_map_id, $order); + + my $remaining_entries = $config->list_route_map($route_map_id); + PVE::Network::SDN::RouteMaps::check_references($route_map_id) + if !$remaining_entries->%*; + PVE::Network::SDN::RouteMaps::write_config($config); }, "deleting route map entry failed", diff --git a/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm b/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm index 54a2227..f5c0bbb 100644 --- a/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm +++ b/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm @@ -45,6 +45,8 @@ sub options { 'asn' => { optional => 0 }, 'peers' => { optional => 1 }, 'fabric' => { optional => 1 }, + 'route-map-in' => { optional => 1 }, + 'route-map-out' => { optional => 1 }, }; } @@ -165,11 +167,19 @@ sub generate_frr_config { $bgp_router->{address_families}->{l2vpn_evpn}->{autort_as} = $autortas if $autortas; - my $routemap_in = { seq => 1, action => "permit" }; - my $routemap_out = { seq => 1, action => "permit" }; + if (!$config->{frr}->{routemaps}->{'MAP_VTEP_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'}->@*, $routemap_in); - push($config->{frr}->{routemaps}->{'MAP_VTEP_OUT'}->@*, $routemap_out); + push($config->{frr}->{routemaps}->{'MAP_VTEP_IN'}->@*, $entry); + } + + if (!$config->{frr}->{routemaps}->{'MAP_VTEP_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); + } return $config; } @@ -488,6 +498,18 @@ sub on_update_hook { } my $controller = $controller_cfg->{ids}->{$controllerid}; + my $route_map_config = PVE::Network::SDN::RouteMaps::config(0); + + if ($controller->{'route-map-in'}) { + my $entries = $route_map_config->list_route_map($controller->{'route-map-in'}); + die "route map $controller->{'route-map-in'} does not exist!" if !$entries->%*; + } + + if ($controller->{'route-map-out'}) { + my $entries = $route_map_config->list_route_map($controller->{'route-map-out'}); + die "route map $controller->{'route-map-out'} does not exist!" if !$entries->%*; + } + if ($controller->{type} eq 'evpn') { die "must have exactly one of peers / fabric defined" if ($controller->{peers} && $controller->{fabric}) diff --git a/src/PVE/Network/SDN/Controllers/Plugin.pm b/src/PVE/Network/SDN/Controllers/Plugin.pm index 77d8f42..1068b5d 100644 --- a/src/PVE/Network/SDN/Controllers/Plugin.pm +++ b/src/PVE/Network/SDN/Controllers/Plugin.pm @@ -7,6 +7,8 @@ use PVE::Tools; use PVE::JSONSchema; use PVE::Cluster; +use PVE::Network::SDN::RouteMaps; + use PVE::JSONSchema qw(get_standard_option); use base qw(PVE::SectionConfig); @@ -40,6 +42,18 @@ my $defaultData = { 'pve-sdn-controller-id', { completion => \&PVE::Network::SDN::complete_sdn_controller }, ), + 'route-map-in' => { + description => "Route Map that should be applied for incoming routes", + type => 'string', + format => 'pve-sdn-route-map-id', + optional => 1, + }, + 'route-map-out' => { + description => "Route Map that should be applied for outgoing routes", + type => 'string', + format => 'pve-sdn-route-map-id', + optional => 1, + }, }, }; diff --git a/src/PVE/Network/SDN/RouteMaps.pm b/src/PVE/Network/SDN/RouteMaps.pm index dd8dc67..2f7c1b7 100644 --- a/src/PVE/Network/SDN/RouteMaps.pm +++ b/src/PVE/Network/SDN/RouteMaps.pm @@ -110,6 +110,26 @@ sub write_config { cfs_write_file("sdn/route-maps.cfg", $config->to_raw(), 1); } +sub check_references { + my ($route_map_id) = @_; + + my $controller_config = PVE::Network::SDN::Controllers::config(); + + for my $controller_id (keys $controller_config->{ids}->%*) { + my $controller = $controller_config->{ids}->{$controller_id}; + + if ($controller->{'route-map-in'}) { + die "route map $route_map_id still referenced by controller $controller_id" + if $controller->{'route-map-in'} eq $route_map_id; + } + + if ($controller->{'route-map-out'}) { + die "route map $route_map_id still referenced by controller $controller_id" + if $controller->{'route-map-out'} eq $route_map_id; + } + } +} + sub route_map_properties { my ($update) = @_; -- 2.47.3