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 09/12] sdn: dhcp: apply mapping edits on the node serving the guest
Date: Wed,  2 Sep 2026 14:47:36 +0200	[thread overview]
Message-ID: <20260902124739.750853-10-h.laimer@proxmox.com> (raw)
In-Reply-To: <20260902124739.750853-1-h.laimer@proxmox.com>

Mapping edits change cluster-wide records, but the ebpf backend
answers from a per-node map, so an edit made through another node's
API left the map of the node running the guest stale until the next
apply or guest start there. Resolve the MAC to its guest and re-apply
the mapping on that node through a node-scoped call, the same
remove-and-add the editing node runs locally. No other node needs the
record, each picks it up at that guest's next start there, so the
cost of an edit stays one call however large the cluster is.

The push runs detached from the edit request, an unreachable node
cannot hold the edit up, it just catches up on its next apply or the
guest's next start.

Signed-off-by: Hannes Laimer <h.laimer@proxmox.com>
---
 src/PVE/API2/Network/SDN/Ips.pm          |  5 +-
 src/PVE/API2/Network/SDN/Nodes/Status.pm | 37 +++++++++++-
 src/PVE/Network/SDN/Dhcp.pm              | 74 ++++++++++++++++++++++++
 3 files changed, 112 insertions(+), 4 deletions(-)

diff --git a/src/PVE/API2/Network/SDN/Ips.pm b/src/PVE/API2/Network/SDN/Ips.pm
index fc76c90..63bfce6 100644
--- a/src/PVE/API2/Network/SDN/Ips.pm
+++ b/src/PVE/API2/Network/SDN/Ips.pm
@@ -17,9 +17,8 @@ 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;
+        PVE::Network::SDN::Dhcp::update_mapping($vnet, $mac);
+        PVE::Network::SDN::Dhcp::notify_guest_node($vnet, $mac);
     };
     warn "could not update dhcp mapping for $mac: $@" if $@;
 }
diff --git a/src/PVE/API2/Network/SDN/Nodes/Status.pm b/src/PVE/API2/Network/SDN/Nodes/Status.pm
index 7977e0c..f11fd48 100644
--- a/src/PVE/API2/Network/SDN/Nodes/Status.pm
+++ b/src/PVE/API2/Network/SDN/Nodes/Status.pm
@@ -9,6 +9,8 @@ use PVE::API2::Network::SDN::Nodes::Vnets;
 
 use PVE::JSONSchema qw(get_standard_option);
 
+use PVE::Network::SDN::Dhcp;
+
 use PVE::RESTHandler;
 use base qw(PVE::RESTHandler);
 
@@ -27,6 +29,36 @@ __PACKAGE__->register_method({
     path => 'vnets',
 });
 
+__PACKAGE__->register_method({
+    name => 'dhcp_mapping',
+    path => 'dhcp-mapping',
+    method => 'POST',
+    description =>
+        'Re-apply the DHCP mapping of a MAC address on this node from the current records.',
+    permissions => {
+        check => ['perm', '/sdn/zones/{zone}/{vnet}', ['SDN.Allocate']],
+    },
+    protected => 1,
+    proxyto => 'node',
+    parameters => {
+        additionalProperties => 0,
+        properties => {
+            node => get_standard_option('pve-node'),
+            zone => get_standard_option('pve-sdn-zone-id'),
+            vnet => get_standard_option('pve-sdn-vnet-id'),
+            mac => get_standard_option('mac-addr'),
+        },
+    },
+    returns => { type => 'null' },
+    code => sub {
+        my ($param) = @_;
+
+        PVE::Network::SDN::Dhcp::update_mapping($param->{vnet}, $param->{mac});
+
+        return undef;
+    },
+});
+
 __PACKAGE__->register_method({
     name => 'sdnindex',
     path => '',
@@ -52,7 +84,10 @@ __PACKAGE__->register_method({
         my ($param) = @_;
 
         my $result = [
-            { name => 'fabrics' }, { name => 'vnets' }, { name => 'zones' },
+            { name => 'dhcp-mapping' },
+            { name => 'fabrics' },
+            { name => 'vnets' },
+            { name => 'zones' },
         ];
         return $result;
     },
diff --git a/src/PVE/Network/SDN/Dhcp.pm b/src/PVE/Network/SDN/Dhcp.pm
index f046dfd..6bc9bad 100644
--- a/src/PVE/Network/SDN/Dhcp.pm
+++ b/src/PVE/Network/SDN/Dhcp.pm
@@ -3,6 +3,8 @@ package PVE::Network::SDN::Dhcp;
 use strict;
 use warnings;
 
+use POSIX qw();
+
 use PVE::Cluster;
 
 use PVE::Network::SDN;
@@ -59,6 +61,78 @@ sub remove_mapping {
     $dhcp_plugin->del_ip_mapping($zoneid, $mac);
 }
 
+# re-apply a MAC's mapping on this node from the current records
+sub update_mapping {
+    my ($vnetid, $mac) = @_;
+
+    my ($ip4, $ip6) = PVE::Network::SDN::Vnets::get_ips_from_mac($vnetid, $mac);
+    remove_mapping($vnetid, $mac);
+    add_mapping($vnetid, $mac, $ip4, $ip6) if $ip4 || $ip6;
+}
+
+# the guest config lines come from pmxcfs in one go, no guest config
+# gets parsed for this
+my sub guest_node_by_mac {
+    my ($mac) = @_;
+
+    my $vmlist = PVE::Cluster::get_vmlist();
+    my $nets = PVE::Cluster::get_guest_config_properties([map { "net$_" } 0 .. 31]);
+    for my $vmid (keys %$nets) {
+        for my $net (values %{ $nets->{$vmid} }) {
+            return $vmlist->{ids}->{$vmid}->{node} if $net =~ m/=\Q$mac\E(?:,|$)/i;
+        }
+    }
+
+    return undef;
+}
+
+# The records are cluster-wide, but a node answers from its own map,
+# so an edit has to reach the node running the guest behind the MAC.
+# Every other node picks the record up at that guest's next start
+# there. The push runs detached from the request, an unreachable node
+# must not hold the edit up.
+sub notify_guest_node {
+    my ($vnetid, $mac) = @_;
+
+    my $vnet = PVE::Network::SDN::Vnets::get_vnet($vnetid, 1);
+    return if !$vnet;
+
+    my $zone = PVE::Network::SDN::Zones::get_zone($vnet->{zone}, 1);
+    return if !$zone || !$zone->{dhcp} || $zone->{dhcp} ne 'ebpf';
+
+    my $node = guest_node_by_mac($mac);
+    return if !$node || $node eq PVE::INotify::nodename();
+
+    # double fork, the grandchild is reparented to init so nothing on
+    # the request path ever waits on it
+    my $pid = fork();
+    if (!defined($pid)) {
+        warn "could not fork for the dhcp mapping push to $node: $!\n";
+        return;
+    }
+    if ($pid) {
+        waitpid($pid, 0);
+        return;
+    }
+
+    POSIX::setsid();
+    my $child = fork();
+    POSIX::_exit(1) if !defined($child);
+    POSIX::_exit(0) if $child;
+
+    exec(
+        'pvesh',
+        'create',
+        "/nodes/$node/sdn/dhcp-mapping",
+        '--zone',
+        $vnet->{zone},
+        '--vnet',
+        $vnetid,
+        '--mac',
+        $mac,
+    ) or POSIX::_exit(1);
+}
+
 sub regenerate_config {
     my ($reload) = @_;
 
-- 
2.47.3





  parent reply	other threads:[~2026-09-02 12:49 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-02 12:47 [RFC manager/network/proxmox{-ebpf,-perl-rs} 00/12] sdn: implement DHCP for all zones using eBPF Hannes Laimer
2026-09-02 12:47 ` [PATCH proxmox-ebpf 01/12] dhcp: add per-tap responder BPF program Hannes Laimer
2026-09-02 12:47 ` [PATCH proxmox-ebpf 02/12] dhcp: add responder subsystem Hannes Laimer
2026-09-02 12:47 ` [PATCH proxmox-perl-rs 03/12] pve-rs: sdn: add dhcp responder bindings Hannes Laimer
2026-09-02 12:47 ` [PATCH pve-network 04/12] sdn: ipam: do not cache negative per-MAC answers, lock the write Hannes Laimer
2026-09-02 12:47 ` [PATCH pve-network 05/12] sdn: subnets: add dhcp-lease-time property Hannes Laimer
2026-09-02 12:47 ` [PATCH pve-network 06/12] sdn: dhcp: only assert a backend's availability for zones using it Hannes Laimer
2026-09-02 12:47 ` [PATCH pve-network 07/12] sdn: dhcp: add ebpf plugin Hannes Laimer
2026-09-02 12:47 ` [PATCH pve-network 08/12] sdn: zones: attach the dhcp responder on tap plug Hannes Laimer
2026-09-02 12:47 ` Hannes Laimer [this message]
2026-09-02 12:47 ` [PATCH pve-network 10/12] sdn: zones: offer dhcp on all zone types, keep dnsmasq simple-only Hannes Laimer
2026-09-02 12:47 ` [PATCH pve-network 11/12] tests: cover the ebpf dhcp backend and ipam API mapping pushes Hannes Laimer
2026-09-02 12:47 ` [PATCH pve-manager 12/12] ui: sdn: dhcp backend selector on all zones, expose dhcp options Hannes Laimer
2026-09-02 12:54 ` [RFC manager/network/proxmox{-ebpf,-perl-rs} 00/12] sdn: implement DHCP for all zones using eBPF Hannes Laimer
2026-09-03  4:26 ` 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=20260902124739.750853-10-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