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 BCF081FF0AA for ; Tue, 20 Oct 2026 15:38:25 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 60298216DC; Tue, 20 Oct 2026 15:38:24 +0200 (CEST) Message-ID: <7f297f97-077c-41a9-ab42-329166819804@proxmox.com> Date: Tue, 20 Oct 2026 15:38:19 +0200 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: [PATCH qemu-server v3 13/17] network: sdn: keep the DHCP backends current on guest NICs To: pve-devel@lists.proxmox.com References: <20261020080005.470757-1-h.laimer@proxmox.com> <20261020080005.470757-14-h.laimer@proxmox.com> Content-Language: en-US From: Stefan Hanreich In-Reply-To: <20261020080005.470757-14-h.laimer@proxmox.com> Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 7bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.342 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: ZEF77K7OISVFZOUVV4TEBXCQJLWUWQC5 X-Message-ID-Hash: ZEF77K7OISVFZOUVV4TEBXCQJLWUWQC5 X-MailFrom: s.hanreich@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: Tested-by: Stefan Hanreich On 10/20/26 10:02 AM, Hannes Laimer wrote: > 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 > --- > 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) = @_; same here w.r.t. $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;