all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Hannes Laimer <h.laimer@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH pve-network v4 01/13] sdn: ipam: stop caching IPAM misses
Date: Tue,  3 Nov 2026 19:55:47 +0100	[thread overview]
Message-ID: <20261103185559.420277-2-h.laimer@proxmox.com> (raw)
In-Reply-To: <20261103185559.420277-1-h.laimer@proxmox.com>

A lookup that missed the cache stored the IPAM's empty answer, and
any entry counted as a hit. So a record created later directly in the
IPAM stayed hidden, and a guest start allocated another address.
Entries without an address, which older versions leave in the cache,
no longer count as a hit.

Signed-off-by: Hannes Laimer <h.laimer@proxmox.com>
---
 src/PVE/Network/SDN/Ipams.pm        | 16 ++++++--
 src/test/run_test_vnets_blackbox.pl | 58 ++++++++++++++++++++++++++++-
 2 files changed, 69 insertions(+), 5 deletions(-)

diff --git a/src/PVE/Network/SDN/Ipams.pm b/src/PVE/Network/SDN/Ipams.pm
index 179bdf76defc..7e7bec27b5ed 100644
--- a/src/PVE/Network/SDN/Ipams.pm
+++ b/src/PVE/Network/SDN/Ipams.pm
@@ -136,16 +136,24 @@ sub get_ips_from_mac {
     my ($mac, $zoneid, $zone) = @_;
 
     my $macdb = read_macdb();
-    return ($macdb->{macs}->{$mac}->{ip4}, $macdb->{macs}->{$mac}->{ip6}) if $macdb->{macs}->{$mac};
+    my $cached = $macdb->{macs}->{$mac};
+    # an entry without an address was left behind by a lookup that missed, and
+    # says nothing about its MAC
+    return ($cached->{ip4}, $cached->{ip6})
+        if $cached && (defined($cached->{ip4}) || defined($cached->{ip6}));
 
     my $plugin_config = get_plugin_config($zone);
     my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
-    ($macdb->{macs}->{$mac}->{ip4}, $macdb->{macs}->{$mac}->{ip6}) =
-        $plugin->get_ips_from_mac($plugin_config, $mac, $zoneid);
+    my ($ip4, $ip6) = $plugin->get_ips_from_mac($plugin_config, $mac, $zoneid);
 
+    # an empty answer is not cached, since the record may simply not exist yet
+    return if !defined($ip4) && !defined($ip6);
+
+    $macdb->{macs}->{$mac}->{ip4} = $ip4 if defined($ip4);
+    $macdb->{macs}->{$mac}->{ip6} = $ip6 if defined($ip6);
     write_macdb($macdb);
 
-    return ($macdb->{macs}->{$mac}->{ip4}, $macdb->{macs}->{$mac}->{ip6});
+    return ($ip4, $ip6);
 }
 
 1;
diff --git a/src/test/run_test_vnets_blackbox.pl b/src/test/run_test_vnets_blackbox.pl
index d5a78c5b9a16..37eb5567eae6 100755
--- a/src/test/run_test_vnets_blackbox.pl
+++ b/src/test/run_test_vnets_blackbox.pl
@@ -5,6 +5,7 @@ use warnings;
 
 use lib qw(..);
 use File::Slurp;
+use JSON;
 use List::Util qw(first all);
 use NetAddr::IP qw(:lower);
 
@@ -194,7 +195,7 @@ $mocked_sdn_ipams->mock(
         $test_state->{ipam_config} = $cfg;
     },
     read_macdb => sub {
-        return $test_state->{macdb};
+        return PVE::Network::SDN::Ipams::json_reader(undef, encode_json($test_state->{macdb}));
     },
     write_macdb => sub {
         my ($cfg) = @_;
@@ -335,6 +336,34 @@ sub create_subnet {
     PVE::API2::Network::SDN::Subnets->create($params);
 }
 
+# a DHCP zone with one vnet, each subnet with its first address as the
+# gateway and a range over its hosts 100 to 200
+sub create_dhcp_zone {
+    my ($zoneid, $vnetid, @cidrs) = @_;
+    create_zone({ type => "simple", dhcp => "dnsmasq", ipam => "pve", zone => $zoneid });
+    create_dhcp_vnet($zoneid, $vnetid, @cidrs);
+}
+
+sub create_dhcp_vnet {
+    my ($zoneid, $vnetid, @cidrs) = @_;
+    create_vnet({ type => "vnet", zone => $zoneid, vnet => $vnetid });
+    create_dhcp_subnet($vnetid, $_) for @cidrs;
+}
+
+sub create_dhcp_subnet {
+    my ($vnetid, $cidr) = @_;
+    my $net = NetAddr::IP->new($cidr);
+    my $addr = sub { my ($ip) = @_; return $ip->version() == 4 ? $ip->addr() : $ip->short(); };
+    create_subnet({
+        type => "subnet",
+        vnet => $vnetid,
+        subnet => $cidr,
+        gateway => $addr->($net->first()),
+        'dhcp-range' =>
+            ["start-address=" . $addr->($net + 100) . ",end-address=" . $addr->($net + 200)],
+    });
+}
+
 sub get_ipam_entries {
     return PVE::API2::Network::SDN::Ipams->ipamindex({ ipam => "pve" });
 }
@@ -435,6 +464,33 @@ sub test_without_subnet {
 }
 run_test(\&test_without_subnet);
 
+sub test_ipam_cache_lookup {
+    my $test_name = (split(/::/, (caller(0))[3]))[-1];
+    my $zoneid = "TESTZONE";
+    my $vnetid = "testvnet";
+    my $mac = "da:65:8f:18:9b:6f";
+
+    create_dhcp_zone($zoneid, $vnetid, "10.0.0.0/24");
+
+    my $cache = decode_json(encode_json($test_state->{macdb}));
+    PVE::Network::SDN::Vnets::get_ips_from_mac($vnetid, $mac);
+    is_deeply($test_state->{macdb}, $cache, "$test_name: the miss is not cached");
+
+    # an entry holding no address, left behind by an earlier miss, does not
+    # hide a record created since
+    create_ip({ zone => $zoneid, vnet => $vnetid, mac => $mac, ip => "10.0.0.50" });
+    $test_state->{macdb}->{macs}->{$mac} = { ip4 => undef, ip6 => undef };
+    my @ips = PVE::Network::SDN::Vnets::get_ips_from_mac($vnetid, $mac);
+    is($ips[0], "10.0.0.50", "$test_name: an address-less entry counts as a miss");
+    is(
+        $test_state->{macdb}->{macs}->{$mac}->{ip4},
+        "10.0.0.50",
+        "$test_name: the answer is cached",
+    );
+}
+
+run_test(\&test_ipam_cache_lookup);
+
 sub test_nic_join {
     my ($test_name, $subnets) = @_;
 
-- 
2.47.3





  reply	other threads:[~2026-11-03 18:56 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-03 18:55 [PATCH docs/manager/network/qemu-server v4 00/13] sdn: improve MAC cache, DHCP reservations and scope both to the vnet Hannes Laimer
2026-11-03 18:55 ` Hannes Laimer [this message]
2026-11-03 18:55 ` [PATCH pve-network v4 02/13] sdn: ipam: cache a MAC lookup's answer under the cluster lock Hannes Laimer
2026-11-03 18:55 ` [PATCH pve-network v4 03/13] sdn: ipam: read the MAC cache case-insensitively Hannes Laimer
2026-11-03 18:55 ` [PATCH pve-network v4 04/13] sdn: dhcp: replace a stale dnsmasq reservation instead of dropping it Hannes Laimer
2026-11-03 18:55 ` [PATCH pve-network v4 05/13] sdn: ipam: release an address the built-in IPAM no longer holds Hannes Laimer
2026-11-03 18:55 ` [PATCH pve-network v4 06/13] sdn: dhcp: update reservations when IP mappings change Hannes Laimer
2026-11-03 18:55 ` [PATCH pve-network v4 07/13] sdn: ipam: look a MAC up within the vnet's subnets Hannes Laimer
2026-11-03 18:55 ` [PATCH pve-network v4 08/13] sdn: ipam: keep the MAC cache per vnet Hannes Laimer
2026-11-03 18:55 ` [PATCH pve-network v4 09/13] sdn: ipam: give each zone a VRF in NetBox and a section in phpIPAM Hannes Laimer
2026-11-03 18:55 ` [PATCH qemu-server v4 10/13] network: sdn: push the DHCP mappings of a guest when it resumes Hannes Laimer
2026-11-03 18:55 ` [PATCH pve-manager v4 11/13] ui: sdn: dhcp tree: do not merge the same subnet in different zones Hannes Laimer
2026-11-03 18:55 ` [PATCH pve-manager v4 12/13] ui: sdn: ipam: add the zone namespace options Hannes Laimer
2026-11-03 18:55 ` [PATCH pve-docs v4 13/13] sdn: ipam: document zone namespaces in NetBox and phpIPAM Hannes Laimer
2026-11-04  9:56 ` superseded: [PATCH docs/manager/network/qemu-server v4 00/13] sdn: improve MAC cache, DHCP reservations and scope both to the vnet Hannes Laimer

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261103185559.420277-2-h.laimer@proxmox.com \
    --to=h.laimer@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal