all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Hannes Laimer <h.laimer@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH pve-network 1/3] sdn: evpn: add ipv6-nd support for subnets
Date: Wed, 18 Feb 2026 11:23:45 +0100	[thread overview]
Message-ID: <20260218102350.211294-2-h.laimer@proxmox.com> (raw)
In-Reply-To: <20260218102350.211294-1-h.laimer@proxmox.com>

With this we allow enabling and configuring router-advertisements for
subnets in EVPN zones that have a ipv6 prefix configured.

Signed-off-by: Hannes Laimer <h.laimer@proxmox.com>
---
 src/PVE/API2/Network/SDN/Subnets.pm           |  8 +++
 src/PVE/Network/SDN/Controllers/EvpnPlugin.pm | 42 ++++++++++-
 src/PVE/Network/SDN/SubnetPlugin.pm           | 70 +++++++++++++++++++
 3 files changed, 119 insertions(+), 1 deletion(-)

diff --git a/src/PVE/API2/Network/SDN/Subnets.pm b/src/PVE/API2/Network/SDN/Subnets.pm
index fc56532..f4a4521 100644
--- a/src/PVE/API2/Network/SDN/Subnets.pm
+++ b/src/PVE/API2/Network/SDN/Subnets.pm
@@ -225,6 +225,10 @@ __PACKAGE__->register_method({
                 $id = "$zoneid-$id";
 
                 my $opts = PVE::Network::SDN::SubnetPlugin->check_config($id, $param, 1, 1);
+                if ($opts->{'nd-ra-flag-auto'} && !$opts->{'nd-ra-enable'}) {
+                    raise_param_exc(
+                        { 'nd-ra-flag-auto' => "SLAAC requires IPv6 RA to be enabled" });
+                }
 
                 my $scfg = undef;
                 if ($scfg = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id, 1)) {
@@ -300,6 +304,10 @@ __PACKAGE__->register_method({
                     PVE::SectionConfig::delete_from_config($data, $options, $opts, $delete);
                 }
                 $data->{$_} = $opts->{$_} for keys $opts->%*;
+                if ($data->{'nd-ra-flag-auto'} && !$data->{'nd-ra-enable'}) {
+                    raise_param_exc(
+                        { 'nd-ra-flag-auto' => "SLAAC requires IPv6 RA to be enabled" });
+                }
 
                 my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id);
                 PVE::Network::SDN::SubnetPlugin->on_update_hook($zone, $id, $subnet, $scfg);
diff --git a/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm b/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm
index cc21712..b89c3bf 100644
--- a/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm
+++ b/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm
@@ -447,6 +447,47 @@ sub generate_zone_frr_config {
 sub generate_vnet_frr_config {
     my ($class, $plugin_config, $controller, $zone, $zoneid, $vnetid, $config) = @_;
 
+    my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid, 1);
+    my $has_v6 = 0;
+    my $managed = 0;
+    my $other = 0;
+    my $rdnss = undef;
+    my @prefix_opts = ();
+
+    foreach my $subnetid (sort keys %{$subnets}) {
+        my $subnet = $subnets->{$subnetid};
+        my $cidr = $subnet->{cidr};
+        my ($ip, $mask) = split(/\//, $cidr);
+        my $is_v6 = Net::IP::ip_is_ipv6($ip);
+        next if !$is_v6;
+
+        # only enable RA if explicitly enabled on the subnet
+        next if !$subnet->{'nd-ra-enable'};
+
+        $has_v6 = 1;
+
+        $managed = 1 if $subnet->{'nd-ra-flag-managed'};
+        $other = 1 if $subnet->{'nd-ra-flag-other'};
+        $rdnss = $subnet->{'nd-ra-rdnss'} if $subnet->{'nd-ra-rdnss'};
+
+        if (!$subnet->{'nd-ra-flag-auto'}) {
+            push @prefix_opts, "ipv6 nd prefix $cidr no-autoconfig";
+        } else {
+            my $valid = $subnet->{'nd-prefix-valid-lifetime'} // 2592000;
+            my $preferred = $subnet->{'nd-prefix-preferred-lifetime'} // 604800;
+            push @prefix_opts, "ipv6 nd prefix $cidr $valid $preferred";
+        }
+    }
+
+    if ($has_v6) {
+        my $iface_rules = ($config->{frr_interfaces}->{$vnetid} //= []);
+        push @$iface_rules, "no ipv6 nd suppress-ra";
+        push @$iface_rules, "ipv6 nd managed-config-flag" if $managed;
+        push @$iface_rules, "ipv6 nd other-config-flag" if $other;
+        push @$iface_rules, "ipv6 nd rdnss $rdnss" if $rdnss;
+        push @$iface_rules, @prefix_opts;
+    }
+
     my $exitnodes = $zone->{'exitnodes'};
     my $exitnodes_local_routing = $zone->{'exitnodes-local-routing'};
 
@@ -457,7 +498,6 @@ sub generate_vnet_frr_config {
 
     return if !$is_gateway;
 
-    my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid, 1);
     my @controller_config = ();
     foreach my $subnetid (sort keys %{$subnets}) {
         my $subnet = $subnets->{$subnetid};
diff --git a/src/PVE/Network/SDN/SubnetPlugin.pm b/src/PVE/Network/SDN/SubnetPlugin.pm
index e2a0e50..0732640 100644
--- a/src/PVE/Network/SDN/SubnetPlugin.pm
+++ b/src/PVE/Network/SDN/SubnetPlugin.pm
@@ -177,6 +177,43 @@ sub properties {
             description => 'IP address for the DNS server',
             optional => 1,
         },
+        'nd-ra-flag-auto' => {
+            type => 'boolean',
+            description => "enable SLAAC for this subnet",
+        },
+        'nd-ra-flag-managed' => {
+            type => 'boolean',
+            description => "enable DHCP Managed (M) flag for this subnet",
+        },
+        'nd-ra-flag-other' => {
+            type => 'boolean',
+            description => "enable DHCP Other (O) flag for this subnet",
+        },
+        'nd-ra-rdnss' => {
+            type => 'string',
+            format => 'ipv6',
+            description => "RDNSS address for this subnet",
+            optional => 1,
+        },
+        'nd-ra-enable' => {
+            type => 'boolean',
+            description =>
+                "enable IPv6 Router Advertisement (RA) for this subnet, only possible if gateway is specified",
+        },
+        'nd-prefix-valid-lifetime' => {
+            type => 'integer',
+            description => "Valid lifetime for the prefix (seconds)",
+            minimum => 0,
+            default => 2592000,
+            optional => 1,
+        },
+        'nd-prefix-preferred-lifetime' => {
+            type => 'integer',
+            description => "Preferred lifetime for the prefix (seconds)",
+            minimum => 0,
+            default => 604800,
+            optional => 1,
+        },
     };
 }
 
@@ -189,6 +226,13 @@ sub options {
         dnszoneprefix => { optional => 1 },
         'dhcp-range' => { optional => 1 },
         'dhcp-dns-server' => { optional => 1 },
+        'nd-ra-flag-auto' => { optional => 1 },
+        'nd-ra-flag-managed' => { optional => 1 },
+        'nd-ra-flag-other' => { optional => 1 },
+        'nd-ra-rdnss' => { optional => 1 },
+        'nd-ra-enable' => { optional => 1 },
+        'nd-prefix-valid-lifetime' => { optional => 1 },
+        'nd-prefix-preferred-lifetime' => { optional => 1 },
     };
 }
 
@@ -206,6 +250,32 @@ sub on_update_hook {
     my $dns = $zone->{dns};
     my $dnszone = $zone->{dnszone};
     my $reversedns = $zone->{reversedns};
+    my $ra_enable = $subnet->{'nd-ra-enable'};
+    my $slaac = $subnet->{'nd-ra-flag-auto'};
+    my $ra_flag_managed = $subnet->{'nd-ra-flag-managed'};
+    my $ra_flag_other = $subnet->{'nd-ra-flag-other'};
+
+    if ($slaac) {
+        raise_param_exc({ slaac => "SLAAC is only supported on IPv6 subnets" })
+            if !PVE::JSONSchema::pve_verify_cidrv6($cidr, 1);
+
+        raise_param_exc({ slaac => "SLAAC requires IPv6 RA to be enabled" }) if !$ra_enable;
+
+        my $v6_mask = $mask;
+        if (!defined($v6_mask) && $cidr =~ /\/(\d+)$/) {
+            $v6_mask = $1;
+        }
+        raise_param_exc({ slaac => "SLAAC is only supported on IPv6 subnets with a /64 mask" })
+            if $v6_mask != 64;
+    }
+
+    if ($ra_enable) {
+        raise_param_exc({ 'ipv6-ra' => "IPv6 RA can only be enabled on IPv6 subnets" })
+            if !PVE::JSONSchema::pve_verify_cidrv6($cidr, 1);
+
+        raise_param_exc({ 'ipv6-ra' => "IPv6 RA requires a gateway to be defined" })
+            if !$gateway;
+    }
 
     my $mac = undef;
 
-- 
2.47.3





  reply	other threads:[~2026-02-18 10:23 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-02-18 10:23 [PATCH docs/manager/network 0/6] add SLAAC support for subnets in EVPN zones Hannes Laimer
2026-02-18 10:23 ` Hannes Laimer [this message]
2026-02-18 10:23 ` [PATCH pve-network 2/3] sdn: evpn: accept untracked IPv6 NA on EVPN vnet bridges Hannes Laimer
2026-02-18 10:23 ` [PATCH pve-network 3/3] api: vnet: include zone-type in vnet list Hannes Laimer
2026-02-18 10:23 ` [PATCH pve-manager 1/1] ui: sdn: add ipv6 options for subnets in evpns zones Hannes Laimer
2026-02-18 10:23 ` [PATCH pve-docs 1/2] sdn: add subnet ipv6 options section Hannes Laimer
2026-02-18 10:23 ` [PATCH pve-docs 2/2] sdn: add exmaple for ipv6 in an evpn zone Hannes Laimer

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260218102350.211294-2-h.laimer@proxmox.com \
    --to=h.laimer@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal