From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id A439C1FF165 for ; Thu, 22 May 2025 18:26:42 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 8D430BCDA; Thu, 22 May 2025 18:25:55 +0200 (CEST) From: Stefan Hanreich To: pve-devel@lists.proxmox.com Date: Thu, 22 May 2025 18:17:07 +0200 Message-Id: <20250522161731.537011-52-s.hanreich@proxmox.com> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250522161731.537011-1-s.hanreich@proxmox.com> References: <20250522161731.537011-1-s.hanreich@proxmox.com> MIME-Version: 1.0 X-SPAM-LEVEL: Spam detection results: 0 AWL -1.214 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 KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods KAM_MAILER 2 Automated Mailer Tag Left in Email RDNS_NONE 0.793 Delivered to internal network by a host with no rDNS SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_NONE 0.001 SPF: sender does not publish an SPF Record Subject: [pve-devel] [PATCH pve-network v3 18/21] controller: evpn: add fabrics integration X-BeenThere: pve-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox VE development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: Proxmox VE development discussion Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: pve-devel-bounces@lists.proxmox.com Sender: "pve-devel" Provide a new option to the EVPN controller, fabric, that can be used to define a fabric as the underlay network for the EVPN controller. When applying the configuration, the EVPN controller then automatically generates the peer list and from the fabric configuration, rather than users having to specify all IP addresses manually. This also means that the peer list automatically updates when changing the fabric. An EVPN controller can only either define a peer list or a fabric, but not both. This requires the 'peers' property to now be optional, but the existence of either fabric / peers is now validated in the on_update_hook now instead. MTU is set automatically to 1450 (because of VXLAN overhead) when fabrics are used, unless otherwise specified in the EVPN zone configuration, since there is currently now way of reliably accessing the MTU of the interfaces of the fabric. This means users have to manually specify the MTU for the EVPN controller when using fabrics. This could be particularly relevant in the future, when Wireguard is introduced as a fabric, which incurs an overhead of 80 bytes, requiring users to manually set the MTU to 1370. Signed-off-by: Stefan Hanreich --- src/PVE/API2/Network/SDN/Fabrics/Fabric.pm | 10 ++ src/PVE/Network/SDN/Controllers/EvpnPlugin.pm | 143 +++++++++++++++--- src/PVE/Network/SDN/Zones/EvpnPlugin.pm | 56 +++++-- 3 files changed, 177 insertions(+), 32 deletions(-) diff --git a/src/PVE/API2/Network/SDN/Fabrics/Fabric.pm b/src/PVE/API2/Network/SDN/Fabrics/Fabric.pm index 028b352..8b2e286 100644 --- a/src/PVE/API2/Network/SDN/Fabrics/Fabric.pm +++ b/src/PVE/API2/Network/SDN/Fabrics/Fabric.pm @@ -217,6 +217,16 @@ __PACKAGE__->register_method({ } } + # check if this fabric is used in the evpn controller + my $controller_cfg = PVE::Network::SDN::Controllers::config(); + for my $key (keys %{$controller_cfg->{ids}}) { + my $controller = $controller_cfg->{ids}->{$key}; + if ($controller->{type} eq "evpn" && + $controller->{fabric} eq $id) { + die "this fabric is still used in the EVPN controller \"$key\""; + } + } + my $digest = extract_param($param, 'digest'); PVE::Tools::assert_if_modified($config->digest(), $digest) if $digest; diff --git a/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm b/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm index bde331f..8e00c94 100644 --- a/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm +++ b/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm @@ -10,6 +10,7 @@ use PVE::RESTEnvironment qw(log_warn); use PVE::Network::SDN::Controllers::Plugin; use PVE::Network::SDN::Zones::Plugin; +use PVE::Network::SDN::Fabrics; use Net::IP; use base('PVE::Network::SDN::Controllers::Plugin'); @@ -26,6 +27,11 @@ sub properties { minimum => 0, maximum => 4294967296 }, + fabric => { + description => "SDN fabric to use as underlay for this EVPN controller.", + type => 'string', + format => 'pve-sdn-fabric-id', + }, peers => { description => "peers address list.", type => 'string', format => 'ip-list' @@ -36,7 +42,8 @@ sub properties { sub options { return { 'asn' => { optional => 0 }, - 'peers' => { optional => 0 }, + 'peers' => { optional => 1 }, + 'fabric' => { optional => 1 }, }; } @@ -44,34 +51,76 @@ sub options { sub generate_frr_config { my ($class, $plugin_config, $controller_cfg, $id, $uplinks, $config) = @_; - my @peers; - @peers = PVE::Tools::split_list($plugin_config->{'peers'}) if $plugin_config->{'peers'}; - my $local_node = PVE::INotify::nodename(); + my @peers; my $asn = $plugin_config->{asn}; my $ebgp = undef; my $loopback = undef; my $autortas = undef; + my $ifaceip = undef; + my $routerid = undef; + my $bgprouter = find_bgp_controller($local_node, $controller_cfg); my $isisrouter = find_isis_controller($local_node, $controller_cfg); + if ($plugin_config->{'fabric'}) { + my $config = PVE::Network::SDN::Fabrics::config(1); + + my $fabric = eval { $config->get_fabric($plugin_config->{fabric}) }; + if ($@) { + log_warn("could not configure EVPN controller $plugin_config->{id}: $@"); + return; + } + + my $nodes = $config->list_nodes_fabric($plugin_config->{fabric}); + + my $current_node = eval { $config->get_node($plugin_config->{fabric}, $local_node) }; + if ($@) { + log_warn("could not configure EVPN controller $plugin_config->{id}: $@"); + return; + } + + if (!$current_node->{ip}) { + log_warn("Node $local_node requires an IP in the fabric $fabric->{id} to configure the EVPN controller"); + return; + } + + for my $node_id (sort keys %$nodes) { + my $node = $nodes->{$node_id}; + push @peers, $node->{ip} if $node->{ip}; + } + + $loopback = "dummy_$fabric->{id}"; + + $ifaceip = $current_node->{ip}; + $routerid = $current_node->{ip}; + + } elsif ($plugin_config->{'peers'}) { + @peers = PVE::Tools::split_list($plugin_config->{'peers'}); + + if ($bgprouter) { + $loopback = $bgprouter->{loopback} if $bgprouter->{loopback}; + } elsif ($isisrouter) { + $loopback = $isisrouter->{loopback} if $isisrouter->{loopback}; + } + + ($ifaceip, my $interface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback); + $routerid = PVE::Network::SDN::Controllers::Plugin::get_router_id($ifaceip, $interface); + } else { + log_warn("neither fabric nor peers configured for EVPN controller $plugin_config->{id}"); + return; + } + if ($bgprouter) { $ebgp = 1 if $plugin_config->{'asn'} ne $bgprouter->{asn}; - $loopback = $bgprouter->{loopback} if $bgprouter->{loopback}; $asn = $bgprouter->{asn} if $bgprouter->{asn}; $autortas = $plugin_config->{'asn'} if $ebgp; - } elsif ($isisrouter) { - $loopback = $isisrouter->{loopback} if $isisrouter->{loopback}; } - return if !$asn; - + return if !$asn || !$routerid; my $bgp = $config->{frr}->{router}->{"bgp $asn"} //= {}; - my ($ifaceip, $interface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback); - my $routerid = PVE::Network::SDN::Controllers::Plugin::get_router_id($ifaceip, $interface); - my $remoteas = $ebgp ? "external" : $asn; #global options @@ -134,28 +183,74 @@ sub generate_zone_frr_config { $rt_import = [PVE::Tools::split_list($plugin_config->{'rt-import'})] if $plugin_config->{'rt-import'}; my $asn = $controller->{asn}; + my @peers; - @peers = PVE::Tools::split_list($controller->{'peers'}) if $controller->{'peers'}; my $ebgp = undef; my $loopback = undef; + my $ifaceip = undef; my $autortas = undef; + my $routerid = undef; + my $bgprouter = find_bgp_controller($local_node, $controller_cfg); my $isisrouter = find_isis_controller($local_node, $controller_cfg); - if($bgprouter) { - $ebgp = 1 if $controller->{'asn'} ne $bgprouter->{asn}; - $loopback = $bgprouter->{loopback} if $bgprouter->{loopback}; + if ($controller->{fabric}) { + my $config = PVE::Network::SDN::Fabrics::config(1); + + my $fabric = eval { $config->get_fabric($controller->{fabric}) }; + if ($@) { + log_warn("could not configure EVPN controller $controller->{id}: $@"); + return; + } + + my $nodes = $config->list_nodes_fabric($controller->{fabric}); + + my $current_node = eval { $config->get_node($controller->{fabric}, $local_node) }; + if ($@) { + log_warn("could not configure EVPN controller $controller->{id}: $@"); + return; + } + + if (!$current_node->{ip}) { + log_warn("Node $local_node requires an IP in the fabric $fabric->{id} to configure the EVPN controller"); + return; + } + + for my $node (values %$nodes) { + push @peers, $node->{ip} if $node->{ip}; + } + + $loopback = "dummy_$fabric->{id}"; + + $ifaceip = $current_node->{ip}; + $routerid = $current_node->{ip}; + + } elsif ($controller->{peers}) { + @peers = PVE::Tools::split_list($controller->{'peers'}) if $controller->{'peers'}; + + + if($bgprouter) { + $loopback = $bgprouter->{loopback} if $bgprouter->{loopback}; + } elsif ($isisrouter) { + $loopback = $isisrouter->{loopback} if $isisrouter->{loopback}; + } + + ($ifaceip, my $interface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback); + $routerid = PVE::Network::SDN::Controllers::Plugin::get_router_id($ifaceip, $interface); + + } else { + log_warn("neither fabric nor peers configured for EVPN controller $controller->{id}"); + return; + } + + if ($bgprouter) { + $ebgp = 1 if $controller->{'asn'} ne $bgprouter->{asn}; $asn = $bgprouter->{asn} if $bgprouter->{asn}; $autortas = $controller->{'asn'} if $ebgp; - } elsif ($isisrouter) { - $loopback = $isisrouter->{loopback} if $isisrouter->{loopback}; } return if !$vrf || !$vrfvxlan || !$asn; - my ($ifaceip, $interface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback); - my $routerid = PVE::Network::SDN::Controllers::Plugin::get_router_id($ifaceip, $interface); - my $is_gateway = $exitnodes->{$local_node}; # vrf @@ -326,6 +421,12 @@ sub on_update_hook { $controllernb++; die "only 1 global evpn controller can be defined" if $controllernb >= 1; } + + 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}) || !($controller->{peers} || $controller->{fabric}); + } } sub find_bgp_controller { diff --git a/src/PVE/Network/SDN/Zones/EvpnPlugin.pm b/src/PVE/Network/SDN/Zones/EvpnPlugin.pm index 4843756..94cb582 100644 --- a/src/PVE/Network/SDN/Zones/EvpnPlugin.pm +++ b/src/PVE/Network/SDN/Zones/EvpnPlugin.pm @@ -119,24 +119,58 @@ sub generate_sdn_config { die "missing vxlan tag" if !$tag; die "missing controller" if !$controller; - my @peers = PVE::Tools::split_list($controller->{'peers'}); - + my @peers; my $loopback = undef; - my $bgprouter = PVE::Network::SDN::Controllers::EvpnPlugin::find_bgp_controller($local_node, $controller_cfg); - my $isisrouter = PVE::Network::SDN::Controllers::EvpnPlugin::find_isis_controller($local_node, $controller_cfg); - if ($bgprouter->{loopback}) { - $loopback = $bgprouter->{loopback}; - } elsif ($isisrouter->{loopback}) { - $loopback = $isisrouter->{loopback}; + my $ifaceip = undef; + my $iface = undef; + my $routerid = undef; + + if ($controller->{peers}) { + @peers = PVE::Tools::split_list($controller->{'peers'}); + + my $bgprouter = PVE::Network::SDN::Controllers::EvpnPlugin::find_bgp_controller($local_node, $controller_cfg); + my $isisrouter = PVE::Network::SDN::Controllers::EvpnPlugin::find_isis_controller($local_node, $controller_cfg); + + if ($bgprouter->{loopback}) { + $loopback = $bgprouter->{loopback}; + } elsif ($isisrouter->{loopback}) { + $loopback = $isisrouter->{loopback}; + } + + ($ifaceip, $iface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback); + } elsif ($controller->{fabric}) { + my $config = PVE::Network::SDN::Fabrics::config(1); + + my $fabric = eval { $config->get_fabric($controller->{fabric}) }; + die "could not configure EVPN zone $plugin_config->{id}: $@" if $@; + + my $nodes = $config->list_nodes_fabric($controller->{fabric}); + + my $current_node = eval { $config->get_node($controller->{fabric}, $local_node) }; + die "could not configure EVPN zone $plugin_config->{id}: $@" if $@; + + die "Node $local_node requires an IP in the fabric $fabric->{id} to configure the EVPN zone" + if !$current_node->{ip}; + + for my $node (values %$nodes) { + push @peers, $node->{ip} if $node->{ip}; + } + + $loopback = "dummy_$fabric->{id}"; + + $ifaceip = $current_node->{ip}; + $routerid = $current_node->{ip}; + } else { + die "neither fabric nor peers configured for EVPN controller $controller->{id}"; } - my ($ifaceip, $iface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback); my $is_evpn_gateway = $plugin_config->{'exitnodes'}->{$local_node}; my $exitnodes_local_routing = $plugin_config->{'exitnodes-local-routing'}; - my $mtu = 1450; - $mtu = $interfaces_config->{$iface}->{mtu} - 50 if $interfaces_config->{$iface}->{mtu}; + if ($iface) { + $mtu = $interfaces_config->{$iface}->{mtu} - 50 if $interfaces_config->{$iface}->{mtu}; + } $mtu = $plugin_config->{mtu} if $plugin_config->{mtu}; #vxlan interface -- 2.39.5 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel