all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: "Rémi PAETA" <remi.paeta@comaite.com>
To: pve-devel@lists.proxmox.com
Subject: [RFC PATCH network 09/16] sdn: ovn: support ssl connections to the databases
Date: Tue, 13 Oct 2026 14:14:25 +0200	[thread overview]
Message-ID: <20261013121432.2642979-10-remi.paeta@comaite.com> (raw)
In-Reply-To: <20261013121432.2642979-1-remi.paeta@comaite.com>

Allow ssl:<ip>:<port> connections to the OVN databases, so that the
northbound and southbound traffic gets authenticated and encrypted.
Without it, any host reaching the southbound database can register as
a chassis and claim the ports of guests.

As for the OVN deployment itself, the certificates are provided by the
administrator, in /etc/pve/priv/sdn/ovn/<controller>/: the CA
certificate ca.pem, and a certificate and key per node, <node>.pem and
<node>.key. Certificates per node allow restricting each chassis to
its own data with the southbound role based access control.

ovn-nbctl gets them as options. ovn-controller only takes them on its
command line, so the node certificate gets copied locally and a runtime
drop-in adds the options to its unit, written along with the chassis
settings on boot and on network reload. ovn-controller only gets
restarted when this setup changes.

Signed-off-by: Rémi PAETA <remi.paeta@comaite.com>
---
 src/PVE/Network/SDN/Controllers/OvnPlugin.pm |  14 +-
 src/PVE/Network/SDN/Ovn.pm                   | 168 ++++++++++++++++++-
 src/PVE/Network/SDN/Zones/OvnPlugin.pm       |   9 +-
 src/test/run_test_ovn.pl                     | 103 +++++++++++-
 4 files changed, 281 insertions(+), 13 deletions(-)

diff --git a/src/PVE/Network/SDN/Controllers/OvnPlugin.pm b/src/PVE/Network/SDN/Controllers/OvnPlugin.pm
index f7a9b96..9e31eca 100644
--- a/src/PVE/Network/SDN/Controllers/OvnPlugin.pm
+++ b/src/PVE/Network/SDN/Controllers/OvnPlugin.pm
@@ -15,11 +15,12 @@ sub pve_verify_sdn_ovn_remotes {
     my ($remotes, $noerr) = @_;
 
     for my $remote (split(/,/, $remotes)) {
-        next if $remote =~ m/^tcp:$PVE::Tools::IPV4RE:\d+$/;
-        next if $remote =~ m/^tcp:\[$PVE::Tools::IPV6RE\]:\d+$/;
+        next if $remote =~ m/^(?:tcp|ssl):$PVE::Tools::IPV4RE:\d+$/;
+        next if $remote =~ m/^(?:tcp|ssl):\[$PVE::Tools::IPV6RE\]:\d+$/;
 
         return undef if $noerr;
-        die "invalid OVN database connection '$remote', expected tcp:<ip>:<port>\n";
+        die "invalid OVN database connection '$remote', expected tcp:<ip>:<port> or"
+            . " ssl:<ip>:<port>\n";
     }
 
     return $remotes;
@@ -33,13 +34,16 @@ sub properties {
     return {
         'nb-connection' => {
             description => "Comma separated list of the OVN northbound database servers,"
-                . " for example tcp:10.0.0.1:6641,tcp:10.0.0.2:6641.",
+                . " for example tcp:10.0.0.1:6641,tcp:10.0.0.2:6641. With ssl:<ip>:<port>,"
+                . " every node needs its certificate in /etc/pve/priv/sdn/ovn/<controller>/"
+                . "<node>.pem and .key, next to the CA certificate ca.pem.",
             type => 'string',
             format => 'pve-sdn-ovn-remotes',
         },
         'sb-connection' => {
             description => "Comma separated list of the OVN southbound database servers,"
-                . " for example tcp:10.0.0.1:6642,tcp:10.0.0.2:6642.",
+                . " for example tcp:10.0.0.1:6642,tcp:10.0.0.2:6642. SSL connections use the"
+                . " same certificates as for the northbound database.",
             type => 'string',
             format => 'pve-sdn-ovn-remotes',
         },
diff --git a/src/PVE/Network/SDN/Ovn.pm b/src/PVE/Network/SDN/Ovn.pm
index cdedc4a..ad9dda3 100644
--- a/src/PVE/Network/SDN/Ovn.pm
+++ b/src/PVE/Network/SDN/Ovn.pm
@@ -2,6 +2,7 @@ package PVE::Network::SDN::Ovn;
 
 use v5.36;
 
+use File::Path qw(make_path);
 use JSON;
 
 use PVE::Cluster;
@@ -21,6 +22,17 @@ our $CONNECT_TIMEOUT = 10;
 
 our $INTEGRATION_BRIDGE = 'br-int';
 
+# certificates provided by the administrator for SSL connections to the OVN
+# databases: <controller>/ca.pem, and <controller>/<node>.pem and .key
+our $SSL_DIR = '/etc/pve/priv/sdn/ovn';
+
+# local copies of the certificates of this node for ovn-controller, which only
+# takes them as command line options, through a runtime drop-in for its unit
+our $CHASSIS_SSL_DIR = '/etc/ovn/pve-sdn';
+our $CONTROLLER_UNIT_FILE = '/usr/lib/systemd/system/ovn-controller.service';
+our $CONTROLLER_DROPIN = '/run/systemd/system/ovn-controller.service.d/pve-sdn-ssl.conf';
+our $SYSTEMCTL = '/usr/bin/systemctl';
+
 # external_ids keys tagging the objects managed by PVE. Northbound objects
 # without them are never touched, so the OVN deployment can be shared with
 # other consumers.
@@ -36,7 +48,7 @@ my @CHASSIS_KEYS = ('ovn-remote', 'ovn-encap-type', 'ovn-encap-ip', $EXT_CONTROL
 =head3 ovn_controllers(\%running_cfg)
 
 Returns a hash of all controllers of type C<ovn> in C<\%running_cfg>, keyed by
-their ID.
+their ID, as returned by C<controller_config>.
 
 =cut
 
@@ -46,11 +58,63 @@ sub ovn_controllers {
     my $controllers = $running_cfg->{controllers}->{ids} // {};
 
     return {
-        map { $_ => $controllers->{$_} }
+        map { $_ => controller_config($running_cfg, $_) }
         grep { $controllers->{$_}->{type} eq 'ovn' } keys %$controllers
     };
 }
 
+=head3 controller_config(\%running_cfg, $controllerid)
+
+Returns a copy of the configuration of the controller C<$controllerid>, with its
+ID under C<id>, which is needed to find its certificates.
+
+=cut
+
+sub controller_config {
+    my ($running_cfg, $controllerid) = @_;
+
+    my $controller = $running_cfg->{controllers}->{ids}->{$controllerid} or return;
+
+    return { $controller->%*, id => $controllerid };
+}
+
+=head3 uses_ssl($connection)
+
+Returns whether the OVN database connection string C<$connection> uses SSL.
+
+=cut
+
+sub uses_ssl {
+    my ($connection) = @_;
+
+    return (grep { m/^ssl:/ } split(/,/, $connection // '')) ? 1 : 0;
+}
+
+=head3 ssl_files($controllerid, $nodename)
+
+Returns the paths of the private key, certificate and CA certificate that node
+C<$nodename> uses for the SSL connections to the databases of the OVN
+controller C<$controllerid>. Dies if one of them is missing.
+
+=cut
+
+sub ssl_files {
+    my ($controllerid, $nodename) = @_;
+
+    my $dir = "$SSL_DIR/$controllerid";
+    my $files = {
+        'private-key' => "$dir/$nodename.key",
+        certificate => "$dir/$nodename.pem",
+        'ca-cert' => "$dir/ca.pem",
+    };
+
+    my @missing = grep { !-f $_ } sort values %$files;
+    die "missing SSL certificates for the OVN controller '$controllerid': @missing\n"
+        if @missing;
+
+    return $files;
+}
+
 =head3 desired_state(\%running_cfg)
 
 Computes the northbound objects that should exist for C<\%running_cfg>, keyed by
@@ -240,10 +304,22 @@ sub guest_nics {
 sub nbctl {
     my ($controller, @args) = @_;
 
+    my @ssl_args = ();
+    if (uses_ssl($controller->{'nb-connection'})) {
+        my $files = ssl_files($controller->{id}, PVE::INotify::nodename());
+        @ssl_args = map { "--$_=$files->{$_}" } sort keys %$files;
+    }
+
     my ($output, $errors) = ('', '');
     eval {
         run_command(
-            [$NBCTL, "--db=$controller->{'nb-connection'}", "--timeout=$NBCTL_TIMEOUT", @args],
+            [
+                $NBCTL,
+                "--db=$controller->{'nb-connection'}",
+                "--timeout=$NBCTL_TIMEOUT",
+                @ssl_args,
+                @args,
+            ],
             outfunc => sub { $output .= "$_[0]\n"; },
             errfunc => sub { $errors .= "$_[0]\n"; },
         );
@@ -752,6 +828,7 @@ sub configure_chassis {
         if ($attached) {
             run_command([$VSCTL, 'remove', 'Open_vSwitch', '.', 'external_ids', @CHASSIS_KEYS]);
         }
+        configure_chassis_ssl(undef, $nodename);
         return;
     }
 
@@ -763,6 +840,10 @@ sub configure_chassis {
     die "unable to find the local IP to reach the OVN southbound database at $remote_ip\n"
         if !$encap_ip;
 
+    configure_chassis_ssl(
+        uses_ssl($controller->{'sb-connection'}) ? $id : undef, $nodename,
+    );
+
     my $settings = chassis_settings($id, $controller, $encap_ip);
     run_command([
         $VSCTL,
@@ -793,6 +874,87 @@ sub wait_controller_connected {
     return controller_connected();
 }
 
+=head3 chassis_ssl_dropin($unit_content)
+
+Returns the systemd drop-in for the ovn-controller unit with the content
+C<$unit_content>, which adds the options for the local copies of the
+certificates to its command line.
+
+=cut
+
+sub chassis_ssl_dropin {
+    my ($unit_content) = @_;
+
+    my @exec_start = $unit_content =~ m/^ExecStart=(.+)$/mg;
+    die "unable to find the command line of ovn-controller in $CONTROLLER_UNIT_FILE\n"
+        if scalar(@exec_start) != 1;
+
+    my $options = join(
+        ' ',
+        "--ovn-controller-ssl-key=$CHASSIS_SSL_DIR/chassis.key",
+        "--ovn-controller-ssl-cert=$CHASSIS_SSL_DIR/chassis.pem",
+        "--ovn-controller-ssl-ca-cert=$CHASSIS_SSL_DIR/ca.pem",
+    );
+
+    return "# generated by the Proxmox VE SDN, do not edit\n"
+        . "[Service]\nExecStart=\nExecStart=$exec_start[0] $options\n";
+}
+
+# writes $content to $path if it changed, returns whether it did
+sub update_file {
+    my ($path, $content, $perm) = @_;
+
+    my $current = -f $path ? PVE::Tools::file_get_contents($path) : undef;
+    return 0 if defined($current) && $current eq $content;
+
+    PVE::Tools::file_set_contents($path, $content, $perm);
+    return 1;
+}
+
+=head3 configure_chassis_ssl($controllerid, $nodename)
+
+Sets up the certificates of node C<$nodename> for the SSL connection of
+ovn-controller to the southbound database of the OVN controller
+C<$controllerid>, or removes that setup if C<$controllerid> is undefined.
+ovn-controller gets restarted if anything changed.
+
+=cut
+
+sub configure_chassis_ssl {
+    my ($controllerid, $nodename) = @_;
+
+    my $changed = 0;
+
+    if (defined($controllerid)) {
+        my $files = ssl_files($controllerid, $nodename);
+
+        make_path($CHASSIS_SSL_DIR, { mode => 0700 });
+        my $copies = {
+            'private-key' => ['chassis.key', 0600],
+            certificate => ['chassis.pem', 0644],
+            'ca-cert' => ['ca.pem', 0644],
+        };
+        for my $type (sort keys %$copies) {
+            my ($name, $perm) = $copies->{$type}->@*;
+            my $content = PVE::Tools::file_get_contents($files->{$type});
+            $changed = 1 if update_file("$CHASSIS_SSL_DIR/$name", $content, $perm);
+        }
+
+        my $dropin = chassis_ssl_dropin(PVE::Tools::file_get_contents($CONTROLLER_UNIT_FILE));
+        make_path($CONTROLLER_DROPIN =~ s|/[^/]+$||r);
+        $changed = 1 if update_file($CONTROLLER_DROPIN, $dropin, 0644);
+    } elsif (-e $CONTROLLER_DROPIN) {
+        unlink($CONTROLLER_DROPIN) or die "unable to remove $CONTROLLER_DROPIN - $!\n";
+        unlink(glob("$CHASSIS_SSL_DIR/*"));
+        $changed = 1;
+    }
+
+    return if !$changed;
+
+    run_command([$SYSTEMCTL, 'daemon-reload']);
+    run_command([$SYSTEMCTL, 'try-restart', 'ovn-controller.service']);
+}
+
 =head3 controller_connected()
 
 Returns whether the local ovn-controller is connected to the southbound
diff --git a/src/PVE/Network/SDN/Zones/OvnPlugin.pm b/src/PVE/Network/SDN/Zones/OvnPlugin.pm
index 83c2c51..a474dce 100644
--- a/src/PVE/Network/SDN/Zones/OvnPlugin.pm
+++ b/src/PVE/Network/SDN/Zones/OvnPlugin.pm
@@ -92,8 +92,10 @@ sub tap_plug {
     my ($class, $plugin_config, $vnet, $tag, $iface, $vnetid, $firewall, $trunks, $rate) = @_;
 
     my $controllerid = $plugin_config->{controller};
-    my $controller =
-        PVE::Network::SDN::running_config()->{controllers}->{ids}->{$controllerid};
+    my $controller = PVE::Network::SDN::Ovn::controller_config(
+        PVE::Network::SDN::running_config(),
+        $controllerid,
+    );
     die "OVN controller '$controllerid' of vnet '$vnetid' is not applied yet\n"
         if !$controller;
 
@@ -119,7 +121,8 @@ sub add_bridge_fdb {
 
     my $running_config = PVE::Network::SDN::running_config();
     my $controllerid = $plugin_config->{controller};
-    my $controller = $running_config->{controllers}->{ids}->{$controllerid} or return;
+    my $controller = PVE::Network::SDN::Ovn::controller_config($running_config, $controllerid)
+        or return;
 
     eval {
         my $vnetid = PVE::Network::SDN::Ovn::port_vnet($controller, $iface)
diff --git a/src/test/run_test_ovn.pl b/src/test/run_test_ovn.pl
index cb36a0f..83bfd80 100755
--- a/src/test/run_test_ovn.pl
+++ b/src/test/run_test_ovn.pl
@@ -5,6 +5,7 @@ use warnings;
 
 use lib qw(..);
 
+use File::Temp qw(tempdir);
 use JSON;
 use Test::More;
 use Test::MockModule;
@@ -15,6 +16,10 @@ use PVE::Network::SDN::Controllers::OvnPlugin;
 use PVE::Network::SDN::SubnetPlugin;
 use PVE::Network::SDN::Zones::OvnPlugin;
 
+# keep the tests away from the systemd configuration of the host
+my $tmpdir = tempdir(CLEANUP => 1);
+$PVE::Network::SDN::Ovn::CONTROLLER_DROPIN = "$tmpdir/dropin/pve-sdn-ssl.conf";
+
 my $controller = {
     type => 'ovn',
     'nb-connection' => 'tcp:192.168.0.1:6641,tcp:192.168.0.2:6641',
@@ -668,15 +673,109 @@ is($connection_checks, 2, 'wait_controller_connected: waits until connected');
 ok(!PVE::Network::SDN::Ovn::wait_controller_connected(1), 'wait_controller_connected: timeout');
 $ovn_module->mock(controller_connected => sub { return 1 });
 
+# SSL
+
+ok(PVE::Network::SDN::Ovn::uses_ssl('ssl:10.0.0.1:6641,ssl:10.0.0.2:6641'), 'uses_ssl: ssl');
+ok(!PVE::Network::SDN::Ovn::uses_ssl('tcp:10.0.0.1:6641'), 'uses_ssl: tcp');
+
+$PVE::Network::SDN::Ovn::SSL_DIR = "$tmpdir/ssl";
+eval { PVE::Network::SDN::Ovn::ssl_files('ovnctl', 'node1') };
+like($@, qr/missing SSL certificates .*ca\.pem .*node1\.key .*node1\.pem/, 'ssl_files: missing');
+
+mkdir("$tmpdir/ssl");
+mkdir("$tmpdir/ssl/ovnctl");
+PVE::Tools::file_set_contents("$tmpdir/ssl/ovnctl/$_", "$_\n") for qw(ca.pem node1.pem node1.key);
+is_deeply(
+    PVE::Network::SDN::Ovn::ssl_files('ovnctl', 'node1'),
+    {
+        'private-key' => "$tmpdir/ssl/ovnctl/node1.key",
+        certificate => "$tmpdir/ssl/ovnctl/node1.pem",
+        'ca-cert' => "$tmpdir/ssl/ovnctl/ca.pem",
+    },
+    'ssl_files: present',
+);
+
+my $ssl_controller = {
+    id => 'ovnctl',
+    'nb-connection' => 'ssl:192.168.0.1:6641',
+    'sb-connection' => 'ssl:192.168.0.1:6642',
+};
+my $nbctl_cmd;
+$ovn_module->mock(run_command => sub { $nbctl_cmd = $_[0] });
+PVE::Network::SDN::Ovn::nbctl($ssl_controller, 'ls-list');
+is_deeply(
+    [$nbctl_cmd->@[1 .. $#$nbctl_cmd]],
+    [
+        '--db=ssl:192.168.0.1:6641',
+        '--timeout=10',
+        "--ca-cert=$tmpdir/ssl/ovnctl/ca.pem",
+        "--certificate=$tmpdir/ssl/ovnctl/node1.pem",
+        "--private-key=$tmpdir/ssl/ovnctl/node1.key",
+        'ls-list',
+    ],
+    'nbctl: certificates of the node for ssl connections',
+);
+
+is_deeply(
+    PVE::Network::SDN::Ovn::controller_config($running_cfg, 'ovnctl'),
+    { $controller->%*, id => 'ovnctl' },
+    'controller_config: with its ID',
+);
+
+my $unit = "[Service]\nExecStart=/usr/share/ovn/scripts/ovn-ctl start_controller \$OVN_CTL_OPTS\n";
+like(
+    PVE::Network::SDN::Ovn::chassis_ssl_dropin($unit),
+    qr{^ExecStart=\nExecStart=/usr/share/ovn/scripts/ovn-ctl start_controller \$OVN_CTL_OPTS --ovn-controller-ssl-key=\S+/chassis\.key --ovn-controller-ssl-cert=\S+/chassis\.pem --ovn-controller-ssl-ca-cert=\S+/ca\.pem$}m,
+    'chassis_ssl_dropin: ssl options appended to the command line of the unit',
+);
+eval { PVE::Network::SDN::Ovn::chassis_ssl_dropin("[Service]\n") };
+like($@, qr/unable to find the command line/, 'chassis_ssl_dropin: unexpected unit');
+
+$PVE::Network::SDN::Ovn::CHASSIS_SSL_DIR = "$tmpdir/chassis";
+$PVE::Network::SDN::Ovn::CONTROLLER_UNIT_FILE = "$tmpdir/ovn-controller.service";
+PVE::Tools::file_set_contents("$tmpdir/ovn-controller.service", $unit);
+my $systemctl_calls = [];
+$ovn_module->mock(run_command => sub { push @$systemctl_calls, [$_[0]->@[1 .. $_[0]->$#*]] });
+my $restart = [['daemon-reload'], ['try-restart', 'ovn-controller.service']];
+
+PVE::Network::SDN::Ovn::configure_chassis_ssl('ovnctl', 'node1');
+is_deeply($systemctl_calls, $restart, 'configure_chassis_ssl: restart on setup');
+is(
+    PVE::Tools::file_get_contents("$tmpdir/chassis/chassis.key"),
+    "node1.key\n",
+    'configure_chassis_ssl: key copied',
+);
+is((stat("$tmpdir/chassis/chassis.key"))[2] & 07777, 0600, 'configure_chassis_ssl: key mode');
+ok(-f $PVE::Network::SDN::Ovn::CONTROLLER_DROPIN, 'configure_chassis_ssl: drop-in written');
+
+$systemctl_calls = [];
+PVE::Network::SDN::Ovn::configure_chassis_ssl('ovnctl', 'node1');
+is_deeply($systemctl_calls, [], 'configure_chassis_ssl: no restart without changes');
+
+PVE::Tools::file_set_contents("$tmpdir/ssl/ovnctl/node1.pem", "renewed\n");
+PVE::Network::SDN::Ovn::configure_chassis_ssl('ovnctl', 'node1');
+is_deeply($systemctl_calls, $restart, 'configure_chassis_ssl: restart on renewed certificate');
+
+$systemctl_calls = [];
+PVE::Network::SDN::Ovn::configure_chassis_ssl(undef, 'node1');
+is_deeply($systemctl_calls, $restart, 'configure_chassis_ssl: restart on removal');
+ok(!-e $PVE::Network::SDN::Ovn::CONTROLLER_DROPIN, 'configure_chassis_ssl: drop-in removed');
+ok(!-e "$tmpdir/chassis/chassis.key", 'configure_chassis_ssl: key removed');
+
+$systemctl_calls = [];
+PVE::Network::SDN::Ovn::configure_chassis_ssl(undef, 'node1');
+is_deeply($systemctl_calls, [], 'configure_chassis_ssl: nothing to remove');
+$ovn_module->unmock('run_command');
+
 # controller schema
 
 ok(
     PVE::Network::SDN::Controllers::OvnPlugin::pve_verify_sdn_ovn_remotes(
-        'tcp:10.0.0.1:6641,tcp:[fd00::1]:6641', 1,
+        'tcp:10.0.0.1:6641,tcp:[fd00::1]:6641,ssl:10.0.0.2:6641,ssl:[fd00::2]:6641', 1,
     ),
     'remotes format: valid',
 );
-for my $invalid ('ssl:10.0.0.1:6641', 'tcp:10.0.0.1', 'tcp:ovn.example.com:6641') {
+for my $invalid ('unix:/run/ovn/ovnnb_db.sock', 'tcp:10.0.0.1', 'ssl:ovn.example.com:6641') {
     ok(
         !PVE::Network::SDN::Controllers::OvnPlugin::pve_verify_sdn_ovn_remotes($invalid, 1),
         "remotes format: invalid '$invalid'",
-- 
2.47.3




  parent reply	other threads:[~2026-10-13 12:34 UTC|newest]

Thread overview: 18+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-09  9:22 [RFC] sdn: OVN zone and controller plugins Rémi PAETA
2026-10-13 12:14 ` [RFC PATCH ve-rs/network/manager/docs 00/16] sdn: add " Rémi PAETA
2026-10-13 12:14   ` [RFC PATCH ve-rs 01/16] ve-config: sdn: add ovn zone type Rémi PAETA
2026-10-13 12:14   ` SPAM: [RFC PATCH network 02/16] sdn: controllers: add ovn plugin Rémi PAETA
2026-10-13 12:14   ` [RFC PATCH network 03/16] sdn: zones: " Rémi PAETA
2026-10-13 12:14   ` [RFC PATCH network 04/16] sdn: ovn: sync the northbound database on reload Rémi PAETA
2026-10-13 12:14   ` [RFC PATCH network 05/16] sdn: ovn: configure the chassis on boot and on network reload Rémi PAETA
2026-10-13 12:14   ` [RFC PATCH network 06/16] sdn: ovn: add port security Rémi PAETA
2026-10-13 12:14   ` [RFC PATCH network 07/16] sdn: ovn: allow overriding the port security per vnet Rémi PAETA
2026-10-13 12:14   ` [RFC PATCH network 08/16] sdn: subnets: refuse gateway, snat and dhcp on ovn zones Rémi PAETA
2026-10-13 12:14   ` Rémi PAETA [this message]
2026-10-13 12:14   ` [RFC PATCH network 10/16] sdn: ovn: bind guest ports to the chassis of their node Rémi PAETA
2026-10-13 12:14   ` [RFC PATCH manager 11/16] ui: sdn: controller selector: allow filtering by controller type Rémi PAETA
2026-10-13 12:14   ` [RFC PATCH manager 12/16] ui: sdn: add ovn controller Rémi PAETA
2026-10-13 12:14   ` [RFC PATCH manager 13/16] ui: sdn: add ovn zone Rémi PAETA
2026-10-13 12:14   ` [RFC PATCH manager 14/16] ui: sdn: vnet: add port security for ovn zones Rémi PAETA
2026-10-13 12:14   ` [RFC PATCH manager 15/16] ui: sdn: vnet: disable isolate ports " Rémi PAETA
2026-10-13 12:14   ` [RFC PATCH docs 16/16] sdn: document ovn zones and controllers Rémi PAETA

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=20261013121432.2642979-10-remi.paeta@comaite.com \
    --to=remi.paeta@comaite.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