From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id E6BAC1FF0AE for ; Tue, 13 Oct 2026 14:22:46 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 73F7F21464; Tue, 13 Oct 2026 14:22:36 +0200 (CEST) DKIM-Filter: OpenDKIM Filter v2.10.3 smtp.infra.comaite.com A734F62C0D DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=comaite.com; s=377A38C6-3A8D-11E5-A0EC-E97757B27CB3; t=1791893673; bh=YdKN2Y2WQLkB9aMXAmrmhOwq89437aud4/9SqLzpiI8=; h=From:To:Date:Message-ID:MIME-Version; b=A/T1x6zGwA7vBIcWdrupkIdDde8PPvvBGcfeURXI115WBj+XeC43nMli/eYY7LXGB ImVredC93Vvx2Mo9R4Jpsrb9yqgp+THGToKjVjG4y4qkJkKJ1r7QfJ0HhzJOH2kEAV Gw0MXvykcSVxrepLBh2j8tsjW+TVMZbd2Q2Ofgv42qASVMDbBPNm9hqxFg1Qt8SMKZ oJtvwKCLcg+Q/vxukF2aTG/9OEJ8Go1P5B0Q0lvgSA0INRytZTs2g8sqzcHq8gLMHR iH1rxsLXny4VexW+H6gU+VCZ4SZGmFRNNbAIC/PYXxnfpyQhGle5kEpCzuEieB/yX/ 1bIn9V8LOFS1w== From: =?UTF-8?q?R=C3=A9mi=20PAETA?= To: pve-devel@lists.proxmox.com Subject: [RFC PATCH network 07/16] sdn: ovn: allow overriding the port security per vnet Date: Tue, 13 Oct 2026 14:14:23 +0200 Message-ID: <20261013121432.2642979-8-remi.paeta@comaite.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20261013121432.2642979-1-remi.paeta@comaite.com> References: <1116090294.1714263.1791537779868.JavaMail.zimbra@comaite.com> <20261013121432.2642979-1-remi.paeta@comaite.com> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 DKIM_SIGNED 0.1 Message has a DKIM or DK signature, not necessarily valid DKIM_VALID -0.1 Message has at least one valid DKIM or DK signature DKIM_VALID_AU -0.1 Message has a valid DKIM or DK signature from author's domain DKIM_VALID_EF -0.1 Message has a valid DKIM or DK signature from envelope-from domain DMARC_PASS -0.1 DMARC pass policy 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: XD67DQQXD36JTKEP2Z6LD3D2W7NDEAJZ X-Message-ID-Hash: XD67DQQXD36JTKEP2Z6LD3D2W7NDEAJZ X-MailFrom: remi.paeta@comaite.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: The port security level needed depends on the network rather than on the zone: a vnet for regular guests can enforce their IPAM addresses, while another one in the same zone carries a Kubernetes cluster or VRRP virtual addresses and needs no filtering. Add a 'port-security' vnet option overriding the default of the zone. It is refused on vnets of other zone types, which do not filter the guest traffic. Signed-off-by: Rémi PAETA --- src/PVE/API2/Network/SDN/Vnets.pm | 8 +++++++ src/PVE/Network/SDN/Ovn.pm | 15 ++++++++++++- src/PVE/Network/SDN/VnetPlugin.pm | 8 +++++++ src/PVE/Network/SDN/Zones/OvnPlugin.pm | 8 +++---- src/test/run_test_ovn.pl | 29 ++++++++++++++++++++++++++ src/test/run_test_vnets_blackbox.pl | 21 +++++++++++++++++++ 6 files changed, 84 insertions(+), 5 deletions(-) diff --git a/src/PVE/API2/Network/SDN/Vnets.pm b/src/PVE/API2/Network/SDN/Vnets.pm index 3f1cb1b..b753463 100644 --- a/src/PVE/API2/Network/SDN/Vnets.pm +++ b/src/PVE/API2/Network/SDN/Vnets.pm @@ -325,6 +325,10 @@ __PACKAGE__->register_method({ my $zone_scfg = PVE::Network::SDN::Zones::sdn_zones_config($zone_cfg, $zoneid); my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($zone_scfg->{type}); $plugin->vnet_update_hook($cfg, $id, $zone_cfg); + my $port_security = $cfg->{ids}->{$id}->{'port-security'}; + if (defined($port_security) && $zone_scfg->{type} ne 'ovn') { + raise_param_exc({ 'port-security' => "only supported on OVN zones" }); + } PVE::Network::SDN::VnetPlugin->on_update_hook($id, $cfg); @@ -401,6 +405,10 @@ __PACKAGE__->register_method({ my $zone_scfg = PVE::Network::SDN::Zones::sdn_zones_config($zone_cfg, $zoneid); my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($zone_scfg->{type}); $plugin->vnet_update_hook($cfg, $id, $zone_cfg); + my $port_security = $cfg->{ids}->{$id}->{'port-security'}; + if (defined($port_security) && $zone_scfg->{type} ne 'ovn') { + raise_param_exc({ 'port-security' => "only supported on OVN zones" }); + } PVE::Network::SDN::VnetPlugin->on_update_hook($id, $cfg); diff --git a/src/PVE/Network/SDN/Ovn.pm b/src/PVE/Network/SDN/Ovn.pm index 54d9c58..cdedc4a 100644 --- a/src/PVE/Network/SDN/Ovn.pm +++ b/src/PVE/Network/SDN/Ovn.pm @@ -77,7 +77,7 @@ sub desired_state { next if !$controllerid || !$state->{$controllerid}; $state->{$controllerid}->{switches}->{$vnetid} = { - 'port-security' => $zone->{'port-security'} // 'off', + 'port-security' => port_security_level($zone, $vnets->{$vnetid}), external_ids => { $EXT_CONTROLLER => $controllerid, $EXT_ZONE => $zoneid, @@ -464,6 +464,19 @@ sub port_commands { return $commands; } +=head3 port_security_level(\%zone, \%vnet) + +Returns the port security level of the guest ports on C<\%vnet>: the one of the +vnet if set, else the one of its zone C<\%zone>. + +=cut + +sub port_security_level { + my ($zone, $vnet) = @_; + + return $vnet->{'port-security'} // $zone->{'port-security'} // 'off'; +} + =head3 port_addresses($level, $mac, \@ips) Returns the addresses and the port security entries of a port for the guest MAC diff --git a/src/PVE/Network/SDN/VnetPlugin.pm b/src/PVE/Network/SDN/VnetPlugin.pm index e041575..bf079bd 100644 --- a/src/PVE/Network/SDN/VnetPlugin.pm +++ b/src/PVE/Network/SDN/VnetPlugin.pm @@ -83,6 +83,13 @@ sub properties { "If true, sets the isolated property for all interfaces on the bridge of this VNet.", optional => 1, }, + 'port-security' => { + type => 'string', + description => "Port security level of the guest interfaces (OVN zones only)," + . " overriding the one of the zone.", + enum => ['off', 'mac', 'mac-ip'], + optional => 1, + }, }; } @@ -93,6 +100,7 @@ sub options { alias => { optional => 1 }, vlanaware => { optional => 1 }, 'isolate-ports' => { optional => 1 }, + 'port-security' => { optional => 1 }, }; } diff --git a/src/PVE/Network/SDN/Zones/OvnPlugin.pm b/src/PVE/Network/SDN/Zones/OvnPlugin.pm index b25e70c..83c2c51 100644 --- a/src/PVE/Network/SDN/Zones/OvnPlugin.pm +++ b/src/PVE/Network/SDN/Zones/OvnPlugin.pm @@ -21,9 +21,9 @@ sub properties { return { 'port-security' => { description => - "Filtering of the traffic of the guests: 'mac' only lets them send with" - . " their own MAC, 'mac-ip' additionally restricts them to their IPs from the" - . " IPAM.", + "Default filtering of the traffic of the guests, which vnets can override:" + . " 'mac' only lets them send with their own MAC, 'mac-ip' additionally" + . " restricts them to their IPs from the IPAM.", type => 'string', enum => ['off', 'mac', 'mac-ip'], default => 'off', @@ -131,7 +131,7 @@ sub add_bridge_fdb { PVE::Network::SDN::Ovn::update_port( $controller, $iface, - $zone->{'port-security'} // 'off', + PVE::Network::SDN::Ovn::port_security_level($zone, $vnet), $running_config, $vnetid, $macaddr, diff --git a/src/test/run_test_ovn.pl b/src/test/run_test_ovn.pl index 3e97a92..9f191b3 100755 --- a/src/test/run_test_ovn.pl +++ b/src/test/run_test_ovn.pl @@ -316,6 +316,35 @@ is_deeply( eval { PVE::Network::SDN::Ovn::port_addresses('strict', $mac, []) }; like($@, qr/unknown port security level 'strict'/, 'port_addresses: invalid level'); +is( + PVE::Network::SDN::Ovn::port_security_level({ 'port-security' => 'mac' }, {}), + 'mac', + 'port_security_level: zone default', +); +is( + PVE::Network::SDN::Ovn::port_security_level( + { 'port-security' => 'mac' }, + { 'port-security' => 'off' }, + ), + 'off', + 'port_security_level: vnet overrides zone', +); +is(PVE::Network::SDN::Ovn::port_security_level({}, {}), 'off', 'port_security_level: default off'); + +my $override_cfg = { + controllers => $running_cfg->{controllers}, + zones => $running_cfg->{zones}, + vnets => { + ids => { + vnet1 => { type => 'vnet', zone => 'zone1', 'port-security' => 'mac' }, + vnet2 => { type => 'vnet', zone => 'zone2' }, + }, + }, +}; +my $override = PVE::Network::SDN::Ovn::desired_state($override_cfg)->{ovnctl}->{switches}; +is($override->{vnet1}->{'port-security'}, 'mac', 'desired_state: vnet level overrides zone'); +is($override->{vnet2}->{'port-security'}, 'mac-ip', 'desired_state: zone level as default'); + my $sec_switches = [ { name => 'vnet1', external_ids => ext('zone1', 'vnet1') }, { name => 'vnet2', external_ids => ext('zone2', 'vnet2') }, diff --git a/src/test/run_test_vnets_blackbox.pl b/src/test/run_test_vnets_blackbox.pl index 9f4c424..af329ea 100755 --- a/src/test/run_test_vnets_blackbox.pl +++ b/src/test/run_test_vnets_blackbox.pl @@ -421,6 +421,27 @@ sub test_without_subnet { } run_test(\&test_without_subnet); +sub test_port_security_needs_ovn_zone { + my $test_name = (split(/::/, (caller(0))[3]))[-1]; + + create_zone({ + type => "simple", + ipam => "pve", + zone => "TESTZONE", + }); + + eval { + create_vnet({ + type => "vnet", + zone => "TESTZONE", + vnet => "testvnet", + 'port-security' => "mac", + }); + }; + like($@, qr/port-security/, "$test_name: refused on a simple zone"); +} +run_test(\&test_port_security_needs_ovn_zone); + sub test_nic_join { my ($test_name, $subnets) = @_; -- 2.47.3