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 CFD331FF0A8 for ; Sat, 05 Sep 2026 11:22:07 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 3E1772159E; Sat, 05 Sep 2026 11:22:05 +0200 (CEST) Message-ID: Date: Sat, 5 Sep 2026 11:22:01 +0200 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: superseded: [PATCH pve-network] sdn: push mapping changes from the ipam API to the dhcp backend To: pve-devel@lists.proxmox.com References: <20260902125357.757029-1-h.laimer@proxmox.com> From: Hannes Laimer Content-Language: en-US In-Reply-To: <20260902125357.757029-1-h.laimer@proxmox.com> Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 7bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1788600116311 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.592 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: ADKYOCOSDLN2CWLHEBJX7VO2MPMSAVVX X-Message-ID-Hash: ADKYOCOSDLN2CWLHEBJX7VO2MPMSAVVX 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: superseded-by: https://lore.proxmox.com/pve-devel/20260905092106.10567-1-h.laimer@proxmox.com/T/#u On 2026-09-02 14:54, Hannes Laimer wrote: > Mapping edits through the API only wrote the IPAM record and the > cache, the dhcp backend was never told, so dnsmasq kept serving > stale reservations until a later guest start in the zone happened > to sweep them. > > Push the MAC's current answer after each create, update and delete, > removing the old reservation first, since a re-add alone leaves a > stale address behind when only one of the MAC's two addresses was > deleted. The push is best effort, the record write stays > authoritative. Mapping removal was dispatched to the plugins but > implemented nowhere and never called, add the dnsmasq > implementation for it. > > Signed-off-by: Hannes Laimer > --- > src/PVE/API2/Network/SDN/Ips.pm | 18 ++++++++++++++++ > src/PVE/Network/SDN/Dhcp/Dnsmasq.pm | 32 ++++++++++++++++++++++++++++- > src/PVE/Network/SDN/Dhcp/Plugin.pm | 5 +++++ > 3 files changed, 54 insertions(+), 1 deletion(-) > > diff --git a/src/PVE/API2/Network/SDN/Ips.pm b/src/PVE/API2/Network/SDN/Ips.pm > index 5ff05e7..fc76c90 100644 > --- a/src/PVE/API2/Network/SDN/Ips.pm > +++ b/src/PVE/API2/Network/SDN/Ips.pm > @@ -13,6 +13,17 @@ use PVE::RESTHandler; > > use base qw(PVE::RESTHandler); > > +my sub update_dhcp_mapping { > + my ($vnet, $mac) = @_; > + > + eval { > + my ($ip4, $ip6) = PVE::Network::SDN::Vnets::get_ips_from_mac($vnet, $mac); > + PVE::Network::SDN::Dhcp::remove_mapping($vnet, $mac); > + PVE::Network::SDN::Dhcp::add_mapping($vnet, $mac, $ip4, $ip6) if $ip4 || $ip6; > + }; > + warn "could not update dhcp mapping for $mac: $@" if $@; > +} > + > __PACKAGE__->register_method({ > name => 'ipdelete', > path => '', > @@ -46,6 +57,8 @@ __PACKAGE__->register_method({ > eval { PVE::Network::SDN::Vnets::del_ip($vnet, $ip, '', $mac); }; > die "$@\n" if $@; > > + update_dhcp_mapping($vnet, $mac); > + > return undef; > }, > }); > @@ -82,6 +95,8 @@ __PACKAGE__->register_method({ > > PVE::Network::SDN::Vnets::add_ip($vnet, $ip, '', $mac, undef); > > + update_dhcp_mapping($vnet, $mac); > + > return undef; > }, > }); > @@ -132,6 +147,9 @@ __PACKAGE__->register_method({ > } > > die "$error\n" if $error; > + > + update_dhcp_mapping($vnet, $mac); > + > return undef; > }, > }); > diff --git a/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm b/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm > index 477b700..4677330 100644 > --- a/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm > +++ b/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm > @@ -6,7 +6,7 @@ use warnings; > use base qw(PVE::Network::SDN::Dhcp::Plugin); > > use Net::IP qw(:PROC); > -use PVE::Tools qw(file_set_contents run_command lock_file); > +use PVE::Tools qw(file_get_contents file_set_contents run_command lock_file); > > use File::Copy; > use Net::DBus; > @@ -136,6 +136,36 @@ sub add_ip_mapping { > update_lease($dhcpid, $ip4, $mac); > } > > +sub del_ip_mapping { > + my ($class, $dhcpid, $mac) = @_; > + > + my $ethers_file = ethers_file($dhcpid); > + > + my $reload = undef; > + > + my $removeFn = sub { > + my @lines = split(/\n/, file_get_contents($ethers_file)); > + my @remaining = grep { > + my ($parsed_mac) = split(/,/, $_); > + !defined($parsed_mac) || $parsed_mac ne $mac; > + } @lines; > + > + return if scalar(@remaining) == scalar(@lines); > + > + file_set_contents($ethers_file, join("\n", @remaining) . "\n", 0644); > + $reload = 1; > + }; > + > + PVE::Tools::lock_file($ethers_file, 10, $removeFn); > + > + if ($@) { > + warn "Unable to remove $mac from the dnsmasq configuration: $@\n"; > + return; > + } > + > + systemctl_service('reload', "dnsmasq\@$dhcpid") if $reload; > +} > + > sub configure_subnet { > my ($class, $config, $dhcpid, $vnetid, $subnet_config) = @_; > > diff --git a/src/PVE/Network/SDN/Dhcp/Plugin.pm b/src/PVE/Network/SDN/Dhcp/Plugin.pm > index b5d32fa..6cbb8a2 100644 > --- a/src/PVE/Network/SDN/Dhcp/Plugin.pm > +++ b/src/PVE/Network/SDN/Dhcp/Plugin.pm > @@ -27,6 +27,11 @@ sub add_ip_mapping { > die 'implement in sub class'; > } > > +sub del_ip_mapping { > + my ($class, $dhcpid, $mac) = @_; > + die 'implement in sub class'; > +} > + > sub configure_range { > my ($class, $config, $dhcpid, $vnetid, $subnet_config, $range_config) = @_; > die 'implement in sub class';