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 645D91FF0C0 for ; Wed, 21 Oct 2026 10:47:36 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 41D2E217BC; Wed, 21 Oct 2026 10:45:53 +0200 (CEST) From: Hannes Laimer To: pve-devel@lists.proxmox.com Subject: [PATCH qemu-server v4 15/20] network: sdn: keep the DHCP backends current on guest NICs Date: Wed, 21 Oct 2026 10:44:40 +0200 Message-ID: <20261021084445.462195-16-h.laimer@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20261021084445.462195-1-h.laimer@proxmox.com> References: <20261021084445.462195-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: 1792572317452 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.358 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: KH7PHK3LVYI2OZCM5J4XDLBGL67JF65P X-Message-ID-Hash: KH7PHK3LVYI2OZCM5J4XDLBGL67JF65P 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: A DHCP backend that serves the guest's tap itself has to hear of every plug and removal of that tap. The plug now carries the NIC's MAC, so the backend can look up the NIC's record. Removing a tap now goes through the SDN layer as well, so the backend drops its entry for that tap. A bridge change allocated the NIC's address in the new vnet, but unlike a start or a hotplug it never pushed the mapping to the backend of that vnet's zone. It now does. During a live migration the cluster lists the guest on the source until its config moves. A mapping edited in that window is sent to the source, while the target keeps the record it plugged the NIC with. When the target resumes the guest after the migration, it first pushes the current mapping of every NIC. Signed-off-by: Hannes Laimer Tested-by: Stefan Hanreich --- src/PVE/CLI/qm.pm | 4 ++-- src/PVE/QemuServer.pm | 4 ++++ src/PVE/QemuServer/Network.pm | 19 +++++++++++++++++-- src/PVE/QemuServer/RunState.pm | 2 ++ src/usr/pve-bridge | 8 +++++++- src/usr/pve-bridgedown | 4 ++-- 6 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/PVE/CLI/qm.pm b/src/PVE/CLI/qm.pm index b903c1f1..6c53581a 100755 --- a/src/PVE/CLI/qm.pm +++ b/src/PVE/CLI/qm.pm @@ -22,7 +22,7 @@ use PVE::GuestHelpers; use PVE::GuestImport::OVF; use PVE::INotify; use PVE::JSONSchema qw(get_standard_option); -use PVE::Network; +use PVE::Network::SDN::Zones; use PVE::RPCEnvironment; use PVE::SafeSyslog; use PVE::Tools qw(extract_param file_get_contents); @@ -1143,7 +1143,7 @@ __PACKAGE__->register_method({ foreach my $opt (keys %$conf) { next if $opt !~ m/^net(\d+)$/; my $interface = $1; - PVE::Network::tap_unplug("tap${vmid}i${interface}"); + PVE::Network::SDN::Zones::tap_unplug("tap${vmid}i${interface}"); } } diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm index 135c993f..39bdfc8b 100644 --- a/src/PVE/QemuServer.pm +++ b/src/PVE/QemuServer.pm @@ -4975,6 +4975,9 @@ sub vmconfig_update_net { PVE::Network::SDN::Vnets::add_next_free_cidr( $newnet->{bridge}, $conf->{name}, $newnet->{macaddr}, $vmid, undef, 1, ); + PVE::Network::SDN::Vnets::add_dhcp_mapping( + $newnet->{bridge}, $newnet->{macaddr}, $vmid, $conf->{name}, + ); } PVE::QemuServer::Network::tap_plug( @@ -4984,6 +4987,7 @@ sub vmconfig_update_net { $newnet->{firewall}, $newnet->{trunks}, $newnet->{rate}, + { mac => $newnet->{macaddr} }, ); } elsif (safe_num_ne($oldnet->{rate}, $newnet->{rate})) { diff --git a/src/PVE/QemuServer/Network.pm b/src/PVE/QemuServer/Network.pm index b4893c01..e95fa62d 100644 --- a/src/PVE/QemuServer/Network.pm +++ b/src/PVE/QemuServer/Network.pm @@ -8,6 +8,7 @@ use PVE::Firewall::Helpers; use PVE::JSONSchema qw(get_standard_option parse_property_string); use PVE::Network::SDN::Vnets; use PVE::Network::SDN::Zones; +use PVE::Network::SDN::Dhcp; use PVE::RESTEnvironment qw(log_warn); use PVE::Tools qw($IPV6RE file_read_firstline); @@ -272,6 +273,20 @@ sub add_random_macs { } } +# an edit made while the VM migrated went to the source, which the vmlist +# still named. The target pushes every NIC's current mapping once it runs here +# for good +sub add_nets_dhcp_mapping { + my ($conf, $vmid) = @_; + + for my $opt (keys %$conf) { + next if $opt !~ m/^net\d+$/; + my $net = parse_net($conf->{$opt}, 1) or next; + next if !$net->{bridge} || !$net->{macaddr}; + PVE::Network::SDN::Dhcp::update_mapping($net->{bridge}, $net->{macaddr}, $vmid); + } +} + sub add_nets_bridge_fdb { my ($conf, $vmid) = @_; @@ -349,10 +364,10 @@ sub delete_ifaces_ipams_ips { } sub tap_plug { - my ($iface, $bridge, $tag, $firewall, $trunks, $rate) = @_; + my ($iface, $bridge, $tag, $firewall, $trunks, $rate, $opts) = @_; $firewall = $firewall && PVE::Firewall::Helpers::needs_fwbr($bridge); - PVE::Network::SDN::Zones::tap_plug($iface, $bridge, $tag, $firewall, $trunks, $rate); + PVE::Network::SDN::Zones::tap_plug($iface, $bridge, $tag, $firewall, $trunks, $rate, $opts); } sub get_nets_host_mtu { diff --git a/src/PVE/QemuServer/RunState.pm b/src/PVE/QemuServer/RunState.pm index 889ddd28..0fdd8807 100644 --- a/src/PVE/QemuServer/RunState.pm +++ b/src/PVE/QemuServer/RunState.pm @@ -181,6 +181,8 @@ sub vm_resume($vmid, $skiplock = undef, $nocheck = undef) { PVE::QemuServer::Network::add_nets_bridge_fdb($conf, $vmid) if $resume_cmd eq 'cont'; + PVE::QemuServer::Network::add_nets_dhcp_mapping($conf, $vmid) + if $nocheck && $resume_cmd eq 'cont'; mon_cmd($vmid, $resume_cmd); }, diff --git a/src/usr/pve-bridge b/src/usr/pve-bridge index c7260c6b..20724271 100755 --- a/src/usr/pve-bridge +++ b/src/usr/pve-bridge @@ -43,7 +43,13 @@ die "unable to parse network config '$netid'\n" if !$net; PVE::Network::SDN::Vnets::add_dhcp_mapping($net->{bridge}, $net->{macaddr}, $vmid, $conf->{name}); PVE::Network::SDN::Zones::tap_create($iface, $net->{bridge}); PVE::QemuServer::Network::tap_plug( - $iface, $net->{bridge}, $net->{tag}, $net->{firewall}, $net->{trunks}, $net->{rate}, + $iface, + $net->{bridge}, + $net->{tag}, + $net->{firewall}, + $net->{trunks}, + $net->{rate}, + { mac => $net->{macaddr} }, ); exit 0; diff --git a/src/usr/pve-bridgedown b/src/usr/pve-bridgedown index e867b640..79a3722b 100755 --- a/src/usr/pve-bridgedown +++ b/src/usr/pve-bridgedown @@ -2,7 +2,7 @@ use strict; use warnings; -use PVE::Network; +use PVE::Network::SDN::Zones; my $iface = shift; @@ -11,6 +11,6 @@ die "no interface specified\n" if !$iface; die "got strange interface name '$iface'\n" if $iface !~ m/^tap(\d+)i(\d+)$/; -PVE::Network::tap_unplug($iface); +PVE::Network::SDN::Zones::tap_unplug($iface); exit 0; -- 2.47.3