From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id DF5FE1FF0E3 for ; Tue, 27 Oct 2026 10:02:44 +0100 (CET) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 405F72170F; Tue, 27 Oct 2026 09:57:17 +0100 (CET) From: Hannes Laimer To: pve-devel@lists.proxmox.com Subject: [PATCH qemu-server v3 42/45] network: update the microseg enforcement on a tag or name change Date: Tue, 27 Oct 2026 09:55:24 +0100 Message-ID: <20261027085527.702738-43-h.laimer@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20261027085527.702738-1-h.laimer@proxmox.com> References: <20261027085527.702738-1-h.laimer@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1793091379293 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.355 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust 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: XGEQRODMRWDHSCDT25CHHKQOJGDBSOMO X-Message-ID-Hash: XGEQRODMRWDHSCDT25CHHKQOJGDBSOMO X-MailFrom: h.laimer@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: The groups of a VM's NICs depend on its tags and name, but a change of those took effect at the next start of the VM or the next network reload of the node. Update the NICs of a running VM once the new config is written, so the change applies right away. The update reads the config under the same lock as a network reload, so a reload running at the same time cannot leave the old groups in place. A failed update only warns, and the next reload or start catches up. Moving a VM between groups is an SDN change like putting a NIC on a vnet, which needs SDN.Use on the vnet. So a tag or name change that can move the VM also needs SDN.Use on /sdn/microseg, and so does creating a VM with such a tag or name. Signed-off-by: Hannes Laimer --- src/PVE/API2/Qemu.pm | 26 ++++++++++++++++++++++++++ src/PVE/QemuServer.pm | 12 +++++++++++- src/PVE/QemuServer/Network.pm | 14 ++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/src/PVE/API2/Qemu.pm b/src/PVE/API2/Qemu.pm index f4dbd2a13396..bd1046784d66 100644 --- a/src/PVE/API2/Qemu.pm +++ b/src/PVE/API2/Qemu.pm @@ -26,6 +26,7 @@ use PVE::JSONSchema qw(get_standard_option); use PVE::RESTHandler; use PVE::ReplicationConfig; use PVE::GuestHelpers qw(assert_tag_permissions); +use PVE::Network::SDN::Microseg; use PVE::GuestImport; use PVE::QemuConfig; use PVE::QemuServer; @@ -1544,6 +1545,10 @@ __PACKAGE__->register_method({ my $vga = PVE::QemuServer::parse_vga($conf->{vga}); PVE::QemuServer::assert_clipboard_config($vga); + PVE::Network::SDN::Microseg::check_name_access( + $rpcenv, $authuser, undef, $conf->{name}, + ) if defined($conf->{name}); + PVE::QemuServer::Drive::warn_about_virtio_win_issues_in_config($conf); # auto generate uuid if user did not specify smbios1 option @@ -1571,6 +1576,9 @@ __PACKAGE__->register_method({ $vmid, {}, $conf->{tags}, $rpcenv, $authuser, ); $conf->{tags} = PVE::GuestHelpers::get_unique_tags($conf->{tags}); + PVE::Network::SDN::Microseg::check_tag_access( + $rpcenv, $authuser, '', $conf->{tags}, + ); } PVE::QemuConfig->write_config($vmid, $conf); @@ -2409,8 +2417,11 @@ my $update_vm_api = sub { PVE::QemuConfig->write_config($vmid, $conf); } elsif ($opt eq 'tags') { assert_tag_permissions($vmid, $val, '', $rpcenv, $authuser); + PVE::Network::SDN::Microseg::check_tag_access($rpcenv, $authuser, $val, ''); delete $conf->{$opt}; PVE::QemuConfig->write_config($vmid, $conf); + PVE::QemuServer::Network::update_nets_enforcement($conf, $vmid) + if PVE::QemuServer::check_running($vmid); } elsif ($opt =~ m/^net\d+$/) { if ($conf->{$opt}) { PVE::QemuServer::check_bridge_access( @@ -2421,6 +2432,10 @@ my $update_vm_api = sub { } PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force); PVE::QemuConfig->write_config($vmid, $conf); + } elsif ($opt eq 'name') { + PVE::Network::SDN::Microseg::check_name_access($rpcenv, $authuser, $val, undef); + PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force); + PVE::QemuConfig->write_config($vmid, $conf); } else { PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force); PVE::QemuConfig->write_config($vmid, $conf); @@ -2538,6 +2553,12 @@ my $update_vm_api = sub { ); $conf->{pending}->{$opt} = PVE::GuestHelpers::get_unique_tags($param->{$opt}); + PVE::Network::SDN::Microseg::check_tag_access( + $rpcenv, + $authuser, + $conf->{$opt}, + $conf->{pending}->{$opt}, + ); } elsif ($opt =~ m/^net\d+$/) { if ($conf->{$opt}) { PVE::QemuServer::check_bridge_access( @@ -2578,6 +2599,11 @@ my $update_vm_api = sub { if $param->{cipassword} !~ /^\$(?:[156]|2[ay])(\$.+){2}/; } $conf->{cipassword} = $param->{cipassword}; + } elsif ($opt eq 'name') { + PVE::Network::SDN::Microseg::check_name_access( + $rpcenv, $authuser, $conf->{$opt}, $param->{$opt}, + ); + $conf->{pending}->{$opt} = $param->{$opt}; } else { $conf->{pending}->{$opt} = $param->{$opt}; diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm index 1efeeb66e0e3..8595131e9e6b 100644 --- a/src/PVE/QemuServer.pm +++ b/src/PVE/QemuServer.pm @@ -4412,6 +4412,11 @@ sub foreach_volid { } } +my $microseg_option = { + 'name' => 1, + 'tags' => 1, +}; + my $fast_plug_option = { 'description' => 1, 'hookscript' => 1, @@ -4524,12 +4529,14 @@ sub vmconfig_hotplug_pending { }; my $changes = 0; + my $microseg_relevant = 0; foreach my $opt (keys %{ $conf->{pending} }) { # add/change if ($fast_plug_option->{$opt}) { my $new = delete $conf->{pending}->{$opt}; $cloudinit_record_changed->($conf, $opt, $conf->{$opt}, $new); $conf->{$opt} = $new; $changes = 1; + $microseg_relevant = 1 if $microseg_option->{$opt}; } } @@ -4589,7 +4596,7 @@ sub vmconfig_hotplug_pending { my $balloon = get_current_memory($conf->{memory}); mon_cmd($vmid, "balloon", value => $balloon * 1024 * 1024); } elsif ($fast_plug_option->{$opt}) { - # do nothing + $microseg_relevant = 1 if $microseg_option->{$opt}; } elsif ($opt =~ m/^net(\d+)$/) { die "skip\n" if !$hotplug_features->{network}; vm_deviceunplug($vmid, $conf, $opt); @@ -4765,6 +4772,9 @@ sub vmconfig_hotplug_pending { PVE::QemuConfig->write_config($vmid, $conf); + # reads the tags and name from pmxcfs, so after the write + PVE::QemuServer::Network::update_nets_enforcement($conf, $vmid) if $microseg_relevant; + if ($hotplug_features->{cloudinit} && PVE::QemuServer::Cloudinit::has_changes($conf)) { PVE::QemuServer::vmconfig_update_cloudinit_drive($storecfg, $conf, $vmid); } diff --git a/src/PVE/QemuServer/Network.pm b/src/PVE/QemuServer/Network.pm index b4893c015646..ebde9621fbf8 100644 --- a/src/PVE/QemuServer/Network.pm +++ b/src/PVE/QemuServer/Network.pm @@ -355,6 +355,20 @@ sub tap_plug { PVE::Network::SDN::Zones::tap_plug($iface, $bridge, $tag, $firewall, $trunks, $rate); } +# the groups of a NIC depend on the VM's tags and name +sub update_nets_enforcement { + my ($conf, $vmid) = @_; + + for my $opt (keys %$conf) { + next if $opt !~ m/^net(\d+)$/; + my $iface = "tap${vmid}i$1"; + next if !-d "/sys/class/net/$iface"; + my $net = parse_net($conf->{$opt}) or next; + my $firewall = $net->{firewall} && PVE::Firewall::Helpers::needs_fwbr($net->{bridge}); + PVE::Network::SDN::Zones::tap_update($iface, $firewall); + } +} + sub get_nets_host_mtu { my ($vmid, $conf) = @_; -- 2.47.3