public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pve-devel] [WIP v3 cluster/network/manager/qemu-server 00/22] Add support for DHCP servers to SDN
@ 2023-11-14 18:05 Stefan Hanreich
  2023-11-14 18:05 ` [pve-devel] [WIP v3 pve-cluster 01/22] add priv/macs.db Stefan Hanreich
                   ` (21 more replies)
  0 siblings, 22 replies; 27+ messages in thread
From: Stefan Hanreich @ 2023-11-14 18:05 UTC (permalink / raw)
  To: pve-devel

This patch series adds support for automatically deploying dnsmasq as a DHCP
server to a simple SDN Zone.

This is mostly an update for Alexandre, Stefan and Thomas so we have a
consolidated base for further development of this feature. Code and UI is (very)
rough in some places, but all the planned functionality is now included and
usable via the web UI.

I will be doing some cleanup and refactoring the following days. Additionally,
permissions and validations are still missing and are now top priority on my
TODO list.

Alexandre is still working on the new LXC integration, that should follow
shortly.

You need to install dnsmasq (and disable it afterwards):

  apt install dnsmasq && systemctl disable --now dnsmasq


You can use the following example configuration for deploying a DHCP server in
a SDN subnet, you should also be able to recreate this configuration in the
web UI:


/etc/pve/sdn/zones.cfg:

  simple: DHCPNAT
          ipam pve
          dhcp dnsmasq


/etc/pve/sdn/vnets.cfg:

  vnet: dhcpnat
          zone DHCPNAT


/etc/pve/sdn/subnets.cfg:

  subnet: DHCPNAT-10.1.0.0-16
          vnet dhcpnat
          dhcp-dns-server 10.1.0.1
          dhcp-range start-address=10.1.0.100,end-address=10.1.0.200
          gateway 10.1.0.1
          snat 1

Don't forget to apply the new configuration!

For testing it can be helpful to monitor the following files (e.g. with watch)
to find out what is happening
  * /etc/dnsmasq.d/<zone_id>/ethers (on each node)
  * /etc/pve/priv/ipam.db
  * /etc/pve/priv/macs.db

Changes from v2 -> v3:
  * Removed dhcp.cfg, DHCP server now get configured at the zone
  * added UI
  * added / updated API
  * DHCP acquires IPs at vNIC creation instead of VM start
  * DHCP releases IPs at vNIC removal instead of VM stop
  * improved dnsmasq configuration generation
  * added priv/macs.db for caching mac/IP mappings
  * refactored IPAM plugins
  * updated tests

Changes from v1 -> v2:
  * added hooks for handling DHCP when starting / stopping / .. VMs and CTs
  * Get an IP from IPAM and register that IP in the DHCP server
    (pve only for now)
  * remove lease-time, since it is now infinite and managed by the VM lifecycle
  * add hooks for setting & deleting DHCP mappings to DHCP plugins
  * modified interface of the abstract class to reflect new requirements
  * added helpers in existing SDN classes
  * simplified DHCP configuration settings



pve-cluster:

Alexandre Derumier (1):
  add priv/macs.db

 src/PVE/Cluster.pm  | 1 +
 src/pmxcfs/status.c | 1 +
 2 files changed, 2 insertions(+)


pve-network:

Alexandre Derumier (1):
  sdn: fix tests

Stefan Hanreich (12):
  sdn: preparations for DHCP plugin
  subnet: add dhcp options
  sdn: zone: add dhcp options
  sdn: subnet: vnet: refactor IPAM related methods
  ipam: plugins: preparations for DHCP
  dhcp: add abstract class for DHCP plugins
  sdn: dhcp: add dnsmasq plugin
  sdn: dhcp: add helper for creating DHCP leases
  api: add IPAM endpoints
  api: subnet: add dhcp ranges
  api: zone: add dhcp options
  dhcp: regenerate config for DHCP servers on reload

 debian/control                             |   1 +
 src/PVE/API2/Network/SDN.pm                |   6 +
 src/PVE/API2/Network/SDN/Ipam.pm           | 172 ++++++++++++++++++
 src/PVE/API2/Network/SDN/Makefile          |   2 +-
 src/PVE/API2/Network/SDN/Subnets.pm        |   1 +
 src/PVE/API2/Network/SDN/Zones.pm          |   1 +
 src/PVE/Network/SDN.pm                     |   9 +-
 src/PVE/Network/SDN/Dhcp.pm                | 115 ++++++++++++
 src/PVE/Network/SDN/Dhcp/Dnsmasq.pm        | 198 +++++++++++++++++++++
 src/PVE/Network/SDN/Dhcp/Makefile          |   8 +
 src/PVE/Network/SDN/Dhcp/Plugin.pm         |  65 +++++++
 src/PVE/Network/SDN/Ipams.pm               |  80 ++++++++-
 src/PVE/Network/SDN/Ipams/NetboxPlugin.pm  |  86 ++++++++-
 src/PVE/Network/SDN/Ipams/PVEPlugin.pm     |  85 ++++++++-
 src/PVE/Network/SDN/Ipams/PhpIpamPlugin.pm |  29 +++
 src/PVE/Network/SDN/Ipams/Plugin.pm        |  19 +-
 src/PVE/Network/SDN/Makefile               |   3 +-
 src/PVE/Network/SDN/SubnetPlugin.pm        |  32 +++-
 src/PVE/Network/SDN/Subnets.pm             |  98 +++++++---
 src/PVE/Network/SDN/Vnets.pm               | 122 +++++++------
 src/PVE/Network/SDN/Zones.pm               |  34 +++-
 src/PVE/Network/SDN/Zones/SimplePlugin.pm  |   7 +-
 src/test/run_test_subnets.pl               |   8 +-
 src/test/run_test_vnets.pl                 |   4 +-
 24 files changed, 1069 insertions(+), 116 deletions(-)
 create mode 100644 src/PVE/API2/Network/SDN/Ipam.pm
 create mode 100644 src/PVE/Network/SDN/Dhcp.pm
 create mode 100644 src/PVE/Network/SDN/Dhcp/Dnsmasq.pm
 create mode 100644 src/PVE/Network/SDN/Dhcp/Makefile
 create mode 100644 src/PVE/Network/SDN/Dhcp/Plugin.pm


pve-manager:

Stefan Hanreich (4):
  sdn: regenerate DHCP config on reload
  sdn: add DHCP option to Zone dialogue
  sdn: subnet: add panel for editing DHCP ranges
  sdn: dhcp: add view for DHCP mappings

 PVE/API2/Network.pm                  |   1 +
 www/css/ext6-pve.css                 |  10 +-
 www/manager6/Makefile                |   2 +
 www/manager6/dc/Config.js            |  12 +-
 www/manager6/sdn/MappingEdit.js      |  65 ++++++++
 www/manager6/sdn/SubnetEdit.js       | 161 +++++++++++++++++++-
 www/manager6/sdn/zones/Base.js       |   4 +-
 www/manager6/sdn/zones/SimpleEdit.js |  10 ++
 www/manager6/tree/DhcpTree.js        | 215 +++++++++++++++++++++++++++
 9 files changed, 469 insertions(+), 11 deletions(-)
 create mode 100644 www/manager6/sdn/MappingEdit.js
 create mode 100644 www/manager6/tree/DhcpTree.js


qemu-server:

Alexandre Derumier (4):
  vmnic add|remove : add|del ip in ipam
  vm_start : vm-network-scripts: get ip from ipam and add dhcp
    reservation
  api2: create|restore|clone: add_free_ip
  vm_destroy: delete ip from ipam && dhcp

 PVE/API2/Qemu.pm              |  6 +++
 PVE/QemuServer.pm             | 86 +++++++++++++++++++++++++++++++++++
 vm-network-scripts/pve-bridge |  5 ++
 3 files changed, 97 insertions(+)


Summary over all repositories:
  38 files changed, 1637 insertions(+), 127 deletions(-)

-- 
murpp v0.4.0




^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [WIP v3 pve-cluster 01/22] add priv/macs.db
  2023-11-14 18:05 [pve-devel] [WIP v3 cluster/network/manager/qemu-server 00/22] Add support for DHCP servers to SDN Stefan Hanreich
@ 2023-11-14 18:05 ` Stefan Hanreich
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 02/22] sdn: preparations for DHCP plugin Stefan Hanreich
                   ` (20 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Stefan Hanreich @ 2023-11-14 18:05 UTC (permalink / raw)
  To: pve-devel

From: Alexandre Derumier <aderumier@odiso.com>

use to cache mac-ip list association.

can be use by external ipam, firewall,etc for fast lookup

Signed-off-by: Alexandre Derumier <aderumier@odiso.com>
---
 src/PVE/Cluster.pm  | 1 +
 src/pmxcfs/status.c | 1 +
 2 files changed, 2 insertions(+)

diff --git a/src/PVE/Cluster.pm b/src/PVE/Cluster.pm
index cfa2583..80c4bc0 100644
--- a/src/PVE/Cluster.pm
+++ b/src/PVE/Cluster.pm
@@ -62,6 +62,7 @@ my $observed = {
     'priv/token.cfg' => 1,
     'priv/acme/plugins.cfg' => 1,
     'priv/ipam.db' => 1,
+    'priv/macs.db' => 1,
     '/qemu-server/' => 1,
     '/openvz/' => 1,
     '/lxc/' => 1,
diff --git a/src/pmxcfs/status.c b/src/pmxcfs/status.c
index c8094ac..078602e 100644
--- a/src/pmxcfs/status.c
+++ b/src/pmxcfs/status.c
@@ -89,6 +89,7 @@ static memdb_change_t memdb_change_array[] = {
 	{ .path = "priv/tfa.cfg" },
 	{ .path = "priv/token.cfg" },
 	{ .path = "priv/ipam.db" },
+	{ .path = "priv/macs.db" },
 	{ .path = "datacenter.cfg" },
 	{ .path = "vzdump.cron" },
 	{ .path = "vzdump.conf" },
-- 
2.39.2




^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [WIP v3 pve-network 02/22] sdn: preparations for DHCP plugin
  2023-11-14 18:05 [pve-devel] [WIP v3 cluster/network/manager/qemu-server 00/22] Add support for DHCP servers to SDN Stefan Hanreich
  2023-11-14 18:05 ` [pve-devel] [WIP v3 pve-cluster 01/22] add priv/macs.db Stefan Hanreich
@ 2023-11-14 18:06 ` Stefan Hanreich
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 03/22] subnet: add dhcp options Stefan Hanreich
                   ` (19 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Stefan Hanreich @ 2023-11-14 18:06 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 src/PVE/Network/SDN/Subnets.pm | 25 +++++++++++++------------
 src/PVE/Network/SDN/Vnets.pm   | 27 +++++++++++++--------------
 src/PVE/Network/SDN/Zones.pm   | 34 +++++++++++++++++++++++++---------
 3 files changed, 51 insertions(+), 35 deletions(-)

diff --git a/src/PVE/Network/SDN/Subnets.pm b/src/PVE/Network/SDN/Subnets.pm
index 6bb42e5..f654d3a 100644
--- a/src/PVE/Network/SDN/Subnets.pm
+++ b/src/PVE/Network/SDN/Subnets.pm
@@ -23,7 +23,9 @@ sub sdn_subnets_config {
     my $scfg = $cfg->{ids}->{$id};
     die "sdn subnet '$id' does not exist\n" if (!$noerr && !$scfg);
 
-    if($scfg) {
+    if ($scfg) {
+	$scfg->{id} = $id;
+
 	my ($zone, $network, $mask) = split(/-/, $id);
 	$scfg->{cidr} = "$network/$mask";
 	$scfg->{zone} = $zone;
@@ -35,7 +37,14 @@ sub sdn_subnets_config {
 }
 
 sub config {
-    my $config = cfs_read_file("sdn/subnets.cfg");
+    my ($running) = @_;
+
+    if ($running) {
+	my $cfg = PVE::Network::SDN::running_config();
+	return $cfg->{subnets};
+    }
+
+    return cfs_read_file("sdn/subnets.cfg");
 }
 
 sub write_config {
@@ -61,16 +70,8 @@ sub complete_sdn_subnet {
 sub get_subnet {
     my ($subnetid, $running) = @_;
 
-    my $cfg = {};
-    if($running) {
-	my $cfg = PVE::Network::SDN::running_config();
-	$cfg = $cfg->{subnets};
-    } else {
-	$cfg = PVE::Network::SDN::Subnets::config();
-    }
-
-    my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $subnetid, 1);
-    return $subnet;
+    my $cfg = PVE::Network::SDN::Subnets::config($running);
+    return PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $subnetid, 1);
 }
 
 sub find_ip_subnet {
diff --git a/src/PVE/Network/SDN/Vnets.pm b/src/PVE/Network/SDN/Vnets.pm
index 1106c9f..39bdda0 100644
--- a/src/PVE/Network/SDN/Vnets.pm
+++ b/src/PVE/Network/SDN/Vnets.pm
@@ -26,6 +26,13 @@ sub sdn_vnets_config {
 }
 
 sub config {
+    my ($running) = @_;
+
+    if ($running) {
+	my $cfg = PVE::Network::SDN::running_config();
+	return $cfg->{vnets};
+    }
+
     return cfs_read_file("sdn/vnets.cfg");
 }
 
@@ -54,31 +61,23 @@ sub get_vnet {
 
     return if !$vnetid;
 
-    my $scfg = {};
-    if($running) {
-	my $cfg = PVE::Network::SDN::running_config();
-	$scfg = $cfg->{vnets};
-    } else {
-	$scfg = PVE::Network::SDN::Vnets::config();
-    }
-
-    my $vnet = PVE::Network::SDN::Vnets::sdn_vnets_config($scfg, $vnetid, 1);
-
-    return $vnet;
+    my $cfg = PVE::Network::SDN::Vnets::config($running);
+    return PVE::Network::SDN::Vnets::sdn_vnets_config($cfg, $vnetid, 1);
 }
 
 sub get_subnets {
-    my ($vnetid) = @_;
+    my ($vnetid, $running) = @_;
 
     my $subnets = undef;
-    my $subnets_cfg = PVE::Network::SDN::Subnets::config();
+    my $subnets_cfg = PVE::Network::SDN::Subnets::config($running);
+
     foreach my $subnetid (sort keys %{$subnets_cfg->{ids}}) {
 	my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($subnets_cfg, $subnetid);
 	next if !$subnet->{vnet} || ($vnetid && $subnet->{vnet} ne $vnetid);
 	$subnets->{$subnetid} = $subnet;
     }
-    return $subnets;
 
+    return $subnets;
 }
 
 sub get_subnet_from_vnet_cidr {
diff --git a/src/PVE/Network/SDN/Zones.pm b/src/PVE/Network/SDN/Zones.pm
index 4ad4e4d..5bd3536 100644
--- a/src/PVE/Network/SDN/Zones.pm
+++ b/src/PVE/Network/SDN/Zones.pm
@@ -40,8 +40,14 @@ sub sdn_zones_config {
 }
 
 sub config {
-    my $config = cfs_read_file("sdn/zones.cfg");
-    return $config;
+    my ($running) = @_;
+
+    if ($running) {
+	my $cfg = PVE::Network::SDN::running_config();
+	return $cfg->{zones};
+    }
+
+    return cfs_read_file("sdn/zones.cfg");
 }
 
 sub get_plugin_config {
@@ -74,19 +80,29 @@ sub complete_sdn_zone {
 sub get_zone {
     my ($zoneid, $running) = @_;
 
-    my $cfg = {};
-    if($running) {
-        my $cfg = PVE::Network::SDN::running_config();
-        $cfg = $cfg->{vnets};
-    } else {
-        $cfg = PVE::Network::SDN::Zones::config();
-    }
+    my $cfg = PVE::Network::SDN::Zones::config($running);
 
     my $zone = PVE::Network::SDN::Zones::sdn_zones_config($cfg, $zoneid, 1);
 
     return $zone;
 }
 
+sub get_vnets {
+    my ($zoneid, $running) = @_;
+
+    return if !$zoneid;
+
+    my $vnets_config = PVE::Network::SDN::Vnets::config($running);
+    my $vnets = undef;
+
+    for my $vnetid (keys %{$vnets_config->{ids}}) {
+        my $vnet = PVE::Network::SDN::Vnets::sdn_vnets_config($vnets_config, $vnetid);
+        next if !$vnet->{zone} || $vnet->{zone} ne $zoneid;
+        $vnets->{$vnetid} = $vnet;
+    }
+
+    return $vnets;
+}
 
 sub generate_etc_network_config {
 
-- 
2.39.2




^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [WIP v3 pve-network 03/22] subnet: add dhcp options
  2023-11-14 18:05 [pve-devel] [WIP v3 cluster/network/manager/qemu-server 00/22] Add support for DHCP servers to SDN Stefan Hanreich
  2023-11-14 18:05 ` [pve-devel] [WIP v3 pve-cluster 01/22] add priv/macs.db Stefan Hanreich
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 02/22] sdn: preparations for DHCP plugin Stefan Hanreich
@ 2023-11-14 18:06 ` Stefan Hanreich
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 04/22] sdn: zone: " Stefan Hanreich
                   ` (18 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Stefan Hanreich @ 2023-11-14 18:06 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 src/PVE/Network/SDN/SubnetPlugin.pm | 29 +++++++++++++++++++++++++++++
 src/PVE/Network/SDN/Subnets.pm      | 23 +++++++++++++++++++++++
 2 files changed, 52 insertions(+)

diff --git a/src/PVE/Network/SDN/SubnetPlugin.pm b/src/PVE/Network/SDN/SubnetPlugin.pm
index 15b370f..a4adae8 100644
--- a/src/PVE/Network/SDN/SubnetPlugin.pm
+++ b/src/PVE/Network/SDN/SubnetPlugin.pm
@@ -61,6 +61,19 @@ sub private {
     return $defaultData;
 }
 
+my $dhcp_range_fmt = {
+    'start-address' => {
+	type => 'ip',
+	description => 'Start address for the DHCP IP range',
+    },
+    'end-address' => {
+	type => 'ip',
+	description => 'End address for the DHCP IP range',
+    },
+};
+
+PVE::JSONSchema::register_format('pve-sdn-dhcp-range', $dhcp_range_fmt);
+
 sub properties {
     return {
         vnet => {
@@ -84,6 +97,20 @@ sub properties {
             type => 'string', format => 'dns-name',
             description => "dns domain zone prefix  ex: 'adm' -> <hostname>.adm.mydomain.com",
         },
+	'dhcp-range' => {
+	    type => 'array',
+	    description => 'A list of DHCP ranges for this subnet',
+	    optional => 1,
+	    items => {
+		type => 'string',
+		format => 'pve-sdn-dhcp-range',
+	    }
+	},
+	'dhcp-dns-server' => {
+	    type => 'ip',
+	    description => 'IP address for the DNS server',
+	    optional => 1,
+	},
     };
 }
 
@@ -94,6 +121,8 @@ sub options {
 #	routes => { optional => 1 },
 	snat => { optional => 1 },
 	dnszoneprefix => { optional => 1 },
+	'dhcp-range' => { optional => 1 },
+	'dhcp-dns-server' => { optional => 1 },
     };
 }
 
diff --git a/src/PVE/Network/SDN/Subnets.pm b/src/PVE/Network/SDN/Subnets.pm
index f654d3a..6e74de1 100644
--- a/src/PVE/Network/SDN/Subnets.pm
+++ b/src/PVE/Network/SDN/Subnets.pm
@@ -8,6 +8,7 @@ use Net::IP;
 use NetAddr::IP qw(:lower);
 
 use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
+use PVE::JSONSchema qw(parse_property_string);
 use PVE::Network::SDN::Dns;
 use PVE::Network::SDN::Ipams;
 
@@ -36,6 +37,28 @@ sub sdn_subnets_config {
     return $scfg;
 }
 
+sub get_dhcp_ranges {
+    my ($subnet_config) = @_;
+
+    my @dhcp_ranges = ();
+
+    if ($subnet_config->{'dhcp-range'}) {
+	foreach my $element (@{$subnet_config->{'dhcp-range'}}) {
+	    my $dhcp_range = eval { parse_property_string('pve-sdn-dhcp-range', $element) };
+
+	    if ($@ || !$dhcp_range) {
+		warn "Unable to parse dhcp-range string: $element\n";
+		warn "$@\n" if $@;
+		next;
+	    }
+
+	    push @dhcp_ranges, $dhcp_range;
+	}
+    }
+
+    return \@dhcp_ranges;
+}
+
 sub config {
     my ($running) = @_;
 
-- 
2.39.2




^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [WIP v3 pve-network 04/22] sdn: zone: add dhcp options
  2023-11-14 18:05 [pve-devel] [WIP v3 cluster/network/manager/qemu-server 00/22] Add support for DHCP servers to SDN Stefan Hanreich
                   ` (2 preceding siblings ...)
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 03/22] subnet: add dhcp options Stefan Hanreich
@ 2023-11-14 18:06 ` Stefan Hanreich
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 05/22] sdn: subnet: vnet: refactor IPAM related methods Stefan Hanreich
                   ` (17 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Stefan Hanreich @ 2023-11-14 18:06 UTC (permalink / raw)
  To: pve-devel

Co-Authored-By: Alexandre Derumier <aderumier@odiso.com>
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 src/PVE/Network/SDN/Zones/SimplePlugin.pm | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/src/PVE/Network/SDN/Zones/SimplePlugin.pm b/src/PVE/Network/SDN/Zones/SimplePlugin.pm
index 4922903..f30278c 100644
--- a/src/PVE/Network/SDN/Zones/SimplePlugin.pm
+++ b/src/PVE/Network/SDN/Zones/SimplePlugin.pm
@@ -26,7 +26,11 @@ sub properties {
 	dnszone => {
 	    type => 'string', format => 'dns-name',
 	    description => "dns domain zone  ex: mydomain.com",
-	}
+	},
+	dhcp => {
+	    type => 'pve-configid',
+	    description => 'ID of the DHCP server responsible for managing this range',
+	},
     };
 }
 
@@ -38,6 +42,7 @@ sub options {
 	reversedns => { optional => 1 },
 	dnszone => { optional => 1 },
 	ipam => { optional => 1 },
+	dhcp => { optional => 1 },
     };
 }
 
-- 
2.39.2




^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [WIP v3 pve-network 05/22] sdn: subnet: vnet: refactor IPAM related methods
  2023-11-14 18:05 [pve-devel] [WIP v3 cluster/network/manager/qemu-server 00/22] Add support for DHCP servers to SDN Stefan Hanreich
                   ` (3 preceding siblings ...)
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 04/22] sdn: zone: " Stefan Hanreich
@ 2023-11-14 18:06 ` Stefan Hanreich
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 06/22] ipam: plugins: preparations for DHCP Stefan Hanreich
                   ` (16 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Stefan Hanreich @ 2023-11-14 18:06 UTC (permalink / raw)
  To: pve-devel

Co-Authored-By: Alexandre Derumier <aderumier@odiso.com>
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 src/PVE/Network/SDN/SubnetPlugin.pm |  3 +-
 src/PVE/Network/SDN/Subnets.pm      | 50 ++++++++++-----
 src/PVE/Network/SDN/Vnets.pm        | 95 +++++++++++++++++------------
 3 files changed, 92 insertions(+), 56 deletions(-)

diff --git a/src/PVE/Network/SDN/SubnetPlugin.pm b/src/PVE/Network/SDN/SubnetPlugin.pm
index a4adae8..88933f5 100644
--- a/src/PVE/Network/SDN/SubnetPlugin.pm
+++ b/src/PVE/Network/SDN/SubnetPlugin.pm
@@ -172,8 +172,7 @@ sub on_update_hook {
 	}
         if(!$old_gateway || $gateway && $gateway ne $old_gateway) {
 	    my $hostname = "$vnetid-gw";
-	    my $description = "gateway";
-	    PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $gateway, $hostname, $mac, $description, 1);
+	    PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $gateway, $hostname, $mac, undef, 1);
 	}
 
 	#delete old gateway after update
diff --git a/src/PVE/Network/SDN/Subnets.pm b/src/PVE/Network/SDN/Subnets.pm
index 6e74de1..b05b3d9 100644
--- a/src/PVE/Network/SDN/Subnets.pm
+++ b/src/PVE/Network/SDN/Subnets.pm
@@ -98,14 +98,12 @@ sub get_subnet {
 }
 
 sub find_ip_subnet {
-    my ($ip, $mask, $subnets) = @_;
+    my ($ip, $subnets) = @_;
 
     my $subnet = undef;
     my $subnetid = undef;
 
     foreach my $id (sort keys %{$subnets}) {
-
-	next if $mask ne $subnets->{$id}->{mask};
 	my $cidr = $subnets->{$id}->{cidr};
 	my $subnet_matcher = subnet_matcher($cidr);
 	next if !$subnet_matcher->($ip);
@@ -207,12 +205,11 @@ sub del_subnet {
     $plugin->del_subnet($plugin_config, $subnetid, $subnet);
 }
 
-sub next_free_ip {
-    my ($zone, $subnetid, $subnet, $hostname, $mac, $description, $skipdns) = @_;
+sub add_next_free_ip {
+    my ($zone, $subnetid, $subnet, $hostname, $mac, $vmid, $skipdns, $dhcprange) = @_;
 
     my $cidr = undef;
     my $ip = undef;
-    $description = '' if !$description;
 
     my $ipamid = $zone->{ipam};
     my $dns = $zone->{dns};
@@ -230,10 +227,28 @@ sub next_free_ip {
 	my $plugin_config = $ipam_cfg->{ids}->{$ipamid};
 	my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
 	eval {
-	    $cidr = $plugin->add_next_freeip($plugin_config, $subnetid, $subnet, $hostname, $mac, $description);
-	    ($ip, undef) = split(/\//, $cidr);
+	    if ($dhcprange) {
+		my $data = {
+		    mac => $mac,
+		    hostname => $hostname,
+		    vmid => $vmid,
+		};
+
+		my $dhcp_ranges = PVE::Network::SDN::Subnets::get_dhcp_ranges($subnet);
+
+		foreach my $range (@$dhcp_ranges) {
+		    $ip = $plugin->add_range_next_freeip($plugin_config, $subnet, $range, $data);
+	            next if !$ip;
+		}
+	    } else {
+		$ip = $plugin->add_next_freeip($plugin_config, $subnetid, $subnet, $hostname, $mac, $vmid);
+	    }
 	};
+
 	die $@ if $@;
+
+	eval { PVE::Network::SDN::Ipams::add_cache_mac_ip($mac, $ip); };
+	warn $@ if $@;
     }
 
     eval {
@@ -250,15 +265,15 @@ sub next_free_ip {
 	#rollback
 	my $err = $@;
 	eval {
-	    PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname)
+	    PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac)
 	};
 	die $err;
     }
-    return $cidr;
+    return $ip;
 }
 
 sub add_ip {
-    my ($zone, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $skipdns) = @_;
+    my ($zone, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, $skipdns) = @_;
 
     return if !$subnet || !$ip; 
 
@@ -287,7 +302,7 @@ sub add_ip {
 	my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
 
 	eval {
-	    $plugin->add_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway);
+	    $plugin->add_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway);
 	};
 	die $@ if $@;
     }
@@ -304,14 +319,14 @@ sub add_ip {
 	#rollback
 	my $err = $@;
 	eval {
-	    PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname)
+	    PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac)
 	};
 	die $err;
     }
 }
 
 sub update_ip {
-    my ($zone, $subnetid, $subnet, $ip, $hostname, $oldhostname, $mac, $description, $skipdns) = @_;
+    my ($zone, $subnetid, $subnet, $ip, $hostname, $oldhostname, $mac, $vmid, $skipdns) = @_;
 
     return if !$subnet || !$ip; 
 
@@ -338,7 +353,7 @@ sub update_ip {
 	my $plugin_config = $ipam_cfg->{ids}->{$ipamid};
 	my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
 	eval {
-	    $plugin->update_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description);
+	    $plugin->update_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid);
 	};
 	die $@ if $@;
     }
@@ -358,7 +373,7 @@ sub update_ip {
 }
 
 sub del_ip {
-    my ($zone, $subnetid, $subnet, $ip, $hostname, $skipdns) = @_;
+    my ($zone, $subnetid, $subnet, $ip, $hostname, $mac, $skipdns) = @_;
 
     return if !$subnet || !$ip;
 
@@ -383,6 +398,9 @@ sub del_ip {
 	my $plugin_config = $ipam_cfg->{ids}->{$ipamid};
 	my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
 	$plugin->del_ip($plugin_config, $subnetid, $subnet, $ip);
+
+	eval { PVE::Network::SDN::Ipams::del_cache_mac_ip($mac, $ip); };
+	warn $@ if $@;
     }
 
     eval {
diff --git a/src/PVE/Network/SDN/Vnets.pm b/src/PVE/Network/SDN/Vnets.pm
index 39bdda0..9ec16eb 100644
--- a/src/PVE/Network/SDN/Vnets.pm
+++ b/src/PVE/Network/SDN/Vnets.pm
@@ -80,81 +80,100 @@ sub get_subnets {
     return $subnets;
 }
 
-sub get_subnet_from_vnet_cidr {
-    my ($vnetid, $cidr) = @_;
+sub get_subnet_from_vnet_ip {
+    my ($vnetid, $ip) = @_;
 
     my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid, 1);
     my $vnet = PVE::Network::SDN::Vnets::get_vnet($vnetid);
     my $zoneid = $vnet->{zone};
     my $zone = PVE::Network::SDN::Zones::get_zone($zoneid);
 
-    my ($ip, $mask) = split(/\//, $cidr);
-    die "ip address is not in cidr format" if !$mask;
-
-    my ($subnetid, $subnet) = PVE::Network::SDN::Subnets::find_ip_subnet($ip, $mask, $subnets);
+    my ($subnetid, $subnet) = PVE::Network::SDN::Subnets::find_ip_subnet($ip, $subnets);
 
     return ($zone, $subnetid, $subnet, $ip);
 }
 
-sub get_next_free_cidr {
-    my ($vnetid, $hostname, $mac, $description, $ipversion, $skipdns) = @_;
+sub add_next_free_cidr {
+    my ($vnetid, $hostname, $mac, $vmid, $skipdns, $dhcprange) = @_;
 
     my $vnet = PVE::Network::SDN::Vnets::get_vnet($vnetid);
+    return if !$vnet;
+
     my $zoneid = $vnet->{zone};
     my $zone = PVE::Network::SDN::Zones::get_zone($zoneid);
 
     return if !$zone->{ipam};
 
-    $ipversion = 4 if !$ipversion;
     my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnetid, 1);
-    my $ip = undef;
-    my $subnetcount = 0;
-
-    foreach my $subnetid (sort keys %{$subnets}) {
-        my $subnet = $subnets->{$subnetid};
-	my $network = $subnet->{network};
-
-	next if $ipversion != Net::IP::ip_get_version($network);
-	$subnetcount++;
 
-	eval {
-	    $ip = PVE::Network::SDN::Subnets::next_free_ip($zone, $subnetid, $subnet, $hostname, $mac, $description, $skipdns);
-	};
-	warn $@ if $@;
-	last if $ip;
+    my @ipversions = qw/ 4 6 /;
+    for my $ipversion (@ipversions) {
+	my $ip = undef;
+	my $subnetcount = 0;
+	foreach my $subnetid (sort keys %{$subnets}) {
+	    my $subnet = $subnets->{$subnetid};
+	    my $network = $subnet->{network};
+
+	    next if Net::IP::ip_get_version($network) != $ipversion;
+	    $subnetcount++;
+
+	    eval {
+		$ip = PVE::Network::SDN::Subnets::add_next_free_ip($zone, $subnetid, $subnet, $hostname, $mac, $vmid, $skipdns, $dhcprange);
+	    };
+	    die $@ if $@;
+	    last if $ip;
+	}
+	die "can't find any free ip" if !$ip && $subnetcount > 0;
     }
-    die "can't find any free ip" if !$ip && $subnetcount > 0;
-
-    return $ip;
 }
 
-sub add_cidr {
-    my ($vnetid, $cidr, $hostname, $mac, $description, $skipdns) = @_;
+sub add_ip {
+    my ($vnetid, $ip, $hostname, $mac, $vmid, $skipdns) = @_;
 
     return if !$vnetid;
     
-    my ($zone, $subnetid, $subnet, $ip) = PVE::Network::SDN::Vnets::get_subnet_from_vnet_cidr($vnetid, $cidr);
-    PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $description, undef, $skipdns);
+    my ($zone, $subnetid, $subnet) = PVE::Network::SDN::Vnets::get_subnet_from_vnet_ip($vnetid, $ip);
+    PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, undef, $skipdns);
 }
 
-sub update_cidr {
-    my ($vnetid, $cidr, $hostname, $oldhostname, $mac, $description, $skipdns) = @_;
+sub update_ip {
+    my ($vnetid, $ip, $hostname, $oldhostname, $mac, $vmid, $skipdns) = @_;
 
     return if !$vnetid;
 
-    my ($zone, $subnetid, $subnet, $ip) = PVE::Network::SDN::Vnets::get_subnet_from_vnet_cidr($vnetid, $cidr);
-    PVE::Network::SDN::Subnets::update_ip($zone, $subnetid, $subnet, $ip, $hostname, $oldhostname, $mac, $description, $skipdns);
+    my ($zone, $subnetid, $subnet) = PVE::Network::SDN::Vnets::get_subnet_from_vnet_ip($vnetid, $ip);
+    PVE::Network::SDN::Subnets::update_ip($zone, $subnetid, $subnet, $ip, $hostname, $oldhostname, $mac, $vmid, $skipdns);
 }
 
-sub del_cidr {
-    my ($vnetid, $cidr, $hostname, $skipdns) = @_;
+sub del_ip {
+    my ($vnetid, $ip, $hostname, $mac, $skipdns) = @_;
 
     return if !$vnetid;
 
-    my ($zone, $subnetid, $subnet, $ip) = PVE::Network::SDN::Vnets::get_subnet_from_vnet_cidr($vnetid, $cidr);
-    PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname, $skipdns);
+    my ($zone, $subnetid, $subnet) = PVE::Network::SDN::Vnets::get_subnet_from_vnet_ip($vnetid, $ip);
+    PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $skipdns);
 }
 
+sub get_ips_from_mac {
+    my ($vnetid, $mac) = @_;
+
+    my $vnet = PVE::Network::SDN::Vnets::get_vnet($vnetid);
+    return if !$vnet;
 
+    my $zoneid = $vnet->{zone};
+    my $zone = PVE::Network::SDN::Zones::get_zone($zoneid);
+
+    return if !$zone->{ipam} || !$zone->{dhcp};
+
+    return PVE::Network::SDN::Ipams::get_ips_from_mac($mac, $zoneid, $zone);
+}
+
+sub del_ips_from_mac {
+    my ($vnetid, $mac, $hostname) = @_;
+
+    my ($ip4, $ip6) = PVE::Network::SDN::Vnets::get_ips_from_mac($vnetid, $mac);
+    PVE::Network::SDN::Vnets::del_ip($vnetid, $ip4, $hostname, $mac) if $ip4;
+    PVE::Network::SDN::Vnets::del_ip($vnetid, $ip6, $hostname, $mac) if $ip6;
+}
 
 1;
-- 
2.39.2




^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [WIP v3 pve-network 06/22] ipam: plugins: preparations for DHCP
  2023-11-14 18:05 [pve-devel] [WIP v3 cluster/network/manager/qemu-server 00/22] Add support for DHCP servers to SDN Stefan Hanreich
                   ` (4 preceding siblings ...)
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 05/22] sdn: subnet: vnet: refactor IPAM related methods Stefan Hanreich
@ 2023-11-14 18:06 ` Stefan Hanreich
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 07/22] dhcp: add abstract class for DHCP plugins Stefan Hanreich
                   ` (15 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Stefan Hanreich @ 2023-11-14 18:06 UTC (permalink / raw)
  To: pve-devel

Co-Authored-By: Alexandre Derumier <aderumier@odiso.com>
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 src/PVE/Network/SDN/Ipams.pm               | 80 +++++++++++++++++++-
 src/PVE/Network/SDN/Ipams/NetboxPlugin.pm  | 86 ++++++++++++++++++++--
 src/PVE/Network/SDN/Ipams/PVEPlugin.pm     | 85 +++++++++++++++++++--
 src/PVE/Network/SDN/Ipams/PhpIpamPlugin.pm | 29 ++++++++
 src/PVE/Network/SDN/Ipams/Plugin.pm        | 19 ++++-
 5 files changed, 281 insertions(+), 18 deletions(-)

diff --git a/src/PVE/Network/SDN/Ipams.pm b/src/PVE/Network/SDN/Ipams.pm
index e8a4b0b..926df90 100644
--- a/src/PVE/Network/SDN/Ipams.pm
+++ b/src/PVE/Network/SDN/Ipams.pm
@@ -4,9 +4,10 @@ use strict;
 use warnings;
 
 use JSON;
+use Net::IP;
 
 use PVE::Tools qw(extract_param dir_glob_regex run_command);
-use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
+use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
 use PVE::Network;
 
 use PVE::Network::SDN::Ipams::PVEPlugin;
@@ -19,6 +20,64 @@ PVE::Network::SDN::Ipams::NetboxPlugin->register();
 PVE::Network::SDN::Ipams::PhpIpamPlugin->register();
 PVE::Network::SDN::Ipams::Plugin->init();
 
+my $macdb_filename = 'priv/macs.db';
+
+cfs_register_file($macdb_filename, \&json_reader, \&json_writer);
+
+sub json_reader {
+    my ($filename, $data) = @_;
+
+    return defined($data) && length($data) > 0 ? decode_json($data) : {};
+}
+
+sub json_writer {
+    my ($filename, $data) = @_;
+
+    return encode_json($data);
+}
+
+sub read_macdb {
+    my () = @_;
+
+    return cfs_read_file($macdb_filename);
+}
+
+sub write_macdb {
+    my ($data) = @_;
+
+    cfs_write_file($macdb_filename, $data);
+}
+
+sub add_cache_mac_ip {
+    my ($mac, $ip) = @_;
+
+    cfs_lock_file($macdb_filename, undef, sub {
+	my $db = read_macdb();
+	if (Net::IP::ip_is_ipv4($ip)) {
+	    $db->{macs}->{$mac}->{ip4} = $ip;
+	} else {
+	    $db->{macs}->{$mac}->{ip6} = $ip;
+	}
+	write_macdb($db);
+    });
+    warn "$@" if $@;
+}
+
+sub del_cache_mac_ip {
+    my ($mac, $ip) = @_;
+
+    cfs_lock_file($macdb_filename, undef, sub {
+	my $db = read_macdb();
+	if (Net::IP::ip_is_ipv4($ip)) {
+	    delete $db->{macs}->{$mac}->{ip4};
+	} else {
+	    delete $db->{macs}->{$mac}->{ip6};
+	}
+        delete $db->{macs}->{$mac} if !defined($db->{macs}->{$mac}->{ip4}) && !defined($db->{macs}->{$mac}->{ip6});
+	write_macdb($db);
+    });
+    warn "$@" if $@;
+}
 
 sub sdn_ipams_config {
     my ($cfg, $id, $noerr) = @_;
@@ -39,8 +98,8 @@ sub config {
 }
 
 sub get_plugin_config {
-    my ($vnet) = @_;
-    my $ipamid = $vnet->{ipam};
+    my ($zone) = @_;
+    my $ipamid = $zone->{ipam};
     my $ipam_cfg = PVE::Network::SDN::Ipams::config();
     return $ipam_cfg->{ids}->{$ipamid};
 }
@@ -65,5 +124,20 @@ sub complete_sdn_vnet {
     return  $cmdname eq 'add' ? [] : [ PVE::Network::SDN::Vnets::sdn_ipams_ids($cfg) ];
 }
 
+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 $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);
+
+    write_macdb($macdb);
+
+    return ($macdb->{macs}->{$mac}->{ip4}, $macdb->{macs}->{$mac}->{ip6});
+}
+
 1;
 
diff --git a/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm b/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
index f0e7168..91010bb 100644
--- a/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
+++ b/src/PVE/Network/SDN/Ipams/NetboxPlugin.pm
@@ -77,14 +77,20 @@ sub del_subnet {
 }
 
 sub add_ip {
-    my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_;
+    my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, $noerr) = @_;
 
     my $mask = $subnet->{mask};
     my $url = $plugin_config->{url};
     my $token = $plugin_config->{token};
     my $section = $plugin_config->{section};
     my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
-    $description .= " mac:$mac" if $mac && $description;
+
+    my $description = undef;
+    if ($is_gateway) {
+	$description = 'gateway'
+    } elsif ($mac) {
+	$description = "mac:$mac";
+    }
 
     my $params = { address => "$ip/$mask", dns_name => $hostname, description => $description };
 
@@ -102,14 +108,20 @@ sub add_ip {
 }
 
 sub update_ip {
-    my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_;
+    my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, $noerr) = @_;
 
     my $mask = $subnet->{mask};
     my $url = $plugin_config->{url};
     my $token = $plugin_config->{token};
     my $section = $plugin_config->{section};
     my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
-    $description .= " mac:$mac" if $mac && $description;
+
+    my $description = undef;
+    if ($is_gateway) {
+	$description = 'gateway'
+    } elsif ($mac) {
+	$description = "mac:$mac";
+    }
 
     my $params = { address => "$ip/$mask", dns_name => $hostname, description => $description };
 
@@ -125,7 +137,7 @@ sub update_ip {
 }
 
 sub add_next_freeip {
-    my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $description, $noerr) = @_;
+    my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $vmid, $noerr) = @_;
 
     my $cidr = $subnet->{cidr};
 
@@ -134,7 +146,8 @@ sub add_next_freeip {
     my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
 
     my $internalid = get_prefix_id($url, $cidr, $headers);
-    $description .= " mac:$mac" if $mac && $description;
+
+    my $description = "mac:$mac" if $mac;
 
     my $params = { dns_name => $hostname, description => $description };
 
@@ -151,6 +164,33 @@ sub add_next_freeip {
     return $ip;
 }
 
+sub add_range_next_freeip {
+    my ($class, $plugin_config, $subnet, $range, $data, $noerr) = @_;
+
+    my $url = $plugin_config->{url};
+    my $token = $plugin_config->{token};
+    my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
+
+    my $internalid = get_iprange_id($url, $range, $headers);
+    my $description = "mac:$data->{mac}" if $data->{mac};
+
+    my $params = { dns_name => $data->{hostname}, description => $description };
+
+    my $ip = undef;
+    eval {
+	my $result = PVE::Network::SDN::api_request("POST", "$url/ipam/ip-ranges/$internalid/available-ips/", $headers, $params);
+	$ip = $result->{address};
+	print "found ip free $ip in range $range->{'start-address'}-$range->{'end-address'}\n" if $ip;
+    };
+
+    if ($@) {
+	die "can't find free ip in range $range->{'start-address'}-$range->{'end-address'}: $@" if !$noerr;
+    }
+
+    return $ip;
+
+}
+
 sub del_ip {
     my ($class, $plugin_config, $subnetid, $subnet, $ip, $noerr) = @_;
 
@@ -171,6 +211,31 @@ sub del_ip {
     }
 }
 
+sub get_ips_from_mac {
+    my ($class, $plugin_config, $mac, $zoneid) = @_;
+
+    my $url = $plugin_config->{url};
+    my $token = $plugin_config->{token};
+    my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Authorization' => "token $token"];
+
+    my $ip4 = undef;
+    my $ip6 = undef;
+
+    my $data = PVE::Network::SDN::api_request("GET", "$url/ipam/ip-addresses/?description__ic=$mac", $headers);
+    for my $ip (@{$data->{results}}) {
+	if ($ip->{family}->{value} == 4 && !$ip4) {
+	    ($ip4, undef) = split(/\//, $ip->{address});
+	}
+
+	if ($ip->{family}->{value} == 6 && !$ip6) {
+	    ($ip6, undef) = split(/\//, $ip->{address});
+	}
+    }
+
+    return ($ip4, $ip6);
+}
+
+
 sub verify_api {
     my ($class, $plugin_config) = @_;
 
@@ -204,6 +269,15 @@ sub get_prefix_id {
     return $internalid;
 }
 
+sub get_iprange_id {
+    my ($url, $range, $headers) = @_;
+
+    my $result = PVE::Network::SDN::api_request("GET", "$url/ipam/ip-ranges/?start_address=$range->{'start-address'}&end_address=$range->{'end-address'}", $headers);
+    my $data = @{$result->{results}}[0];
+    my $internalid = $data->{id};
+    return $internalid;
+}
+
 sub get_ip_id {
     my ($url, $ip, $headers) = @_;
     my $result = PVE::Network::SDN::api_request("GET", "$url/ipam/ip-addresses/?q=$ip", $headers);
diff --git a/src/PVE/Network/SDN/Ipams/PVEPlugin.pm b/src/PVE/Network/SDN/Ipams/PVEPlugin.pm
index 3e8ffc5..bcaef9d 100644
--- a/src/PVE/Network/SDN/Ipams/PVEPlugin.pm
+++ b/src/PVE/Network/SDN/Ipams/PVEPlugin.pm
@@ -82,7 +82,7 @@ sub del_subnet {
 }
 
 sub add_ip {
-    my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway) = @_;
+    my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway) = @_;
 
     my $cidr = $subnet->{cidr};
     my $zone = $subnet->{zone};
@@ -96,8 +96,17 @@ sub add_ip {
 	die "subnet '$cidr' doesn't exist in IPAM DB\n" if !$dbsubnet;
 
 	die "IP '$ip' already exist\n" if (!$is_gateway && defined($dbsubnet->{ips}->{$ip})) || ($is_gateway && defined($dbsubnet->{ips}->{$ip}) && !defined($dbsubnet->{ips}->{$ip}->{gateway}));
-	$dbsubnet->{ips}->{$ip} = {};
-	$dbsubnet->{ips}->{$ip} = {gateway => 1} if $is_gateway;
+
+        my $data = {};
+	if ($is_gateway) {
+	    $data->{gateway} = 1;
+	} else {
+	    $data->{ip} = $ip if $ip;
+	    $data->{hostname} = $hostname if $hostname;
+	    $data->{mac} = $mac if $mac;
+	}
+
+	$dbsubnet->{ips}->{$ip} = $data;
 
 	write_db($db);
     });
@@ -105,12 +114,12 @@ sub add_ip {
 }
 
 sub update_ip {
-    my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway) = @_;
+    my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway) = @_;
     return;
 }
 
 sub add_next_freeip {
-    my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $description) = @_;
+    my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $vmid, $noerr) = @_;
 
     my $cidr = $subnet->{cidr};
     my $network = $subnet->{network};
@@ -156,6 +165,39 @@ sub add_next_freeip {
     return "$freeip/$mask";
 }
 
+sub add_range_next_freeip {
+    my ($class, $plugin_config, $subnet, $range, $data, $noerr) = @_;
+
+    my $cidr = $subnet->{cidr};
+    my $zone = $subnet->{zone};
+
+    cfs_lock_file($ipamdb_file, undef, sub {
+	my $db = read_db();
+
+	my $dbzone = $db->{zones}->{$zone};
+	die "zone '$zone' doesn't exist in IPAM DB\n" if !$dbzone;
+
+	my $dbsubnet = $dbzone->{subnets}->{$cidr};
+	die "subnet '$cidr' doesn't exist in IPAM DB\n" if !$dbsubnet;
+
+	my $ip = new Net::IP ("$range->{'start-address'} - $range->{'end-address'}")
+	    or die "Invalid IP address(es) in Range!\n";
+	my $mac = $data->{mac};
+
+	do {
+	    my $ip_address = $ip->ip();
+	    if (!$dbsubnet->{ips}->{$ip_address}) {
+		$dbsubnet->{ips}->{$ip_address} = $data;
+		write_db($db);
+
+		return $ip_address;
+	    }
+	} while (++$ip);
+
+	die "No free IP left in Range $range->{'start-address'}:$range->{'end-address'}}\n";
+    });
+}
+
 sub del_ip {
     my ($class, $plugin_config, $subnetid, $subnet, $ip) = @_;
 
@@ -172,12 +214,43 @@ sub del_ip {
 
 	die "IP '$ip' does not exist in IPAM DB\n" if !defined($dbsubnet->{ips}->{$ip});
 	delete $dbsubnet->{ips}->{$ip};
-
 	write_db($db);
     });
     die "$@" if $@;
 }
 
+sub get_ips_from_mac {
+    my ($class, $plugin_config, $mac, $zoneid) = @_;
+
+    #just in case, as this should already be cached in local macs.db
+
+    my $ip4 = undef;
+    my $ip6 = undef;
+
+    my $db = read_db();
+    die "zone $zoneid don't exist in ipam db" if !$db->{zones}->{$zoneid};
+    my $dbzone = $db->{zones}->{$zoneid};
+    my $subnets = $dbzone->{subnets};
+
+    for my $subnet ( keys %$subnets) {
+	next if Net::IP::ip_is_ipv4($subnet) && $ip4;
+	next if $ip6;
+	my $ips = $subnets->{$subnet}->{ips};
+	for my $ip (keys %$ips) {
+	    my $ipobject = $ips->{$ip};
+	    if ($ipobject->{mac} && $ipobject->{mac} eq $mac) {
+		if (Net::IP::ip_is_ipv4($ip)) {
+		    $ip4 = $ip;
+		} else {
+		    $ip6 = $ip;
+		}
+	    }
+	}
+	last if $ip4 && $ip6;
+    }
+    return ($ip4, $ip6);
+}
+
 #helpers
 
 sub read_db {
diff --git a/src/PVE/Network/SDN/Ipams/PhpIpamPlugin.pm b/src/PVE/Network/SDN/Ipams/PhpIpamPlugin.pm
index ad5286b..1b7b666 100644
--- a/src/PVE/Network/SDN/Ipams/PhpIpamPlugin.pm
+++ b/src/PVE/Network/SDN/Ipams/PhpIpamPlugin.pm
@@ -204,6 +204,35 @@ sub del_ip {
     }
 }
 
+sub get_ips_from_mac {
+    my ($class, $plugin_config, $mac, $zoneid) = @_;
+
+
+    my $url = $plugin_config->{url};
+    my $token = $plugin_config->{token};
+    my $headers = ['Content-Type' => 'application/json; charset=UTF-8', 'Token' => $token];
+
+    my $ip4 = undef;
+    my $ip6 = undef;
+
+    my $ips = PVE::Network::SDN::api_request("GET", "$url/addresses/search_mac/$mac", $headers);
+
+    #fixme
+    die "parsing of result not yet implemented";
+
+    for my $ip (@$ips) {
+#        if ($ip->{family}->{value} == 4 && !$ip4) {
+#            ($ip4, undef) = split(/\//, $ip->{address});
+#        }
+#
+#        if ($ip->{family}->{value} == 6 && !$ip6) {
+#            ($ip6, undef) = split(/\//, $ip->{address});
+#        }
+    }
+
+    return ($ip4, $ip6);
+}
+
 sub verify_api {
     my ($class, $plugin_config) = @_;
 
diff --git a/src/PVE/Network/SDN/Ipams/Plugin.pm b/src/PVE/Network/SDN/Ipams/Plugin.pm
index c96eeda..05d1416 100644
--- a/src/PVE/Network/SDN/Ipams/Plugin.pm
+++ b/src/PVE/Network/SDN/Ipams/Plugin.pm
@@ -79,13 +79,13 @@ sub del_subnet {
 }
 
 sub add_ip {
-    my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_;
+    my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, $noerr) = @_;
 
     die "please implement inside plugin";
 }
 
 sub update_ip {
-    my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $noerr) = @_;
+    my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, $noerr) = @_;
     # only update ip attributes (mac,hostname,..). Don't change the ip addresses itself, as some ipam
     # don't allow ip address change without del/add
 
@@ -93,7 +93,14 @@ sub update_ip {
 }
 
 sub add_next_freeip {
-    my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $description, $noerr) = @_;
+    my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $vmid, $noerr) = @_;
+
+    die "please implement inside plugin";
+}
+
+
+sub add_range_next_freeip {
+    my ($class, $plugin_config, $subnet, $range, $data, $noerr) = @_;
 
     die "please implement inside plugin";
 }
@@ -104,6 +111,12 @@ sub del_ip {
     die "please implement inside plugin";
 }
 
+sub get_ips_from_mac {
+    my ($class, $plugin_config, $mac, $zone) = @_;
+
+    die "please implement inside plugin";
+}
+
 sub on_update_hook {
     my ($class, $plugin_config)  = @_;
 }
-- 
2.39.2




^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [WIP v3 pve-network 07/22] dhcp: add abstract class for DHCP plugins
  2023-11-14 18:05 [pve-devel] [WIP v3 cluster/network/manager/qemu-server 00/22] Add support for DHCP servers to SDN Stefan Hanreich
                   ` (5 preceding siblings ...)
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 06/22] ipam: plugins: preparations for DHCP Stefan Hanreich
@ 2023-11-14 18:06 ` Stefan Hanreich
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 08/22] sdn: dhcp: add dnsmasq plugin Stefan Hanreich
                   ` (14 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Stefan Hanreich @ 2023-11-14 18:06 UTC (permalink / raw)
  To: pve-devel

Co-Authored-By: Alexandre Derumier <aderumier@odiso.com>
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 src/PVE/Network/SDN/Dhcp/Makefile  |  8 ++++
 src/PVE/Network/SDN/Dhcp/Plugin.pm | 65 ++++++++++++++++++++++++++++++
 src/PVE/Network/SDN/Makefile       |  1 +
 3 files changed, 74 insertions(+)
 create mode 100644 src/PVE/Network/SDN/Dhcp/Makefile
 create mode 100644 src/PVE/Network/SDN/Dhcp/Plugin.pm

diff --git a/src/PVE/Network/SDN/Dhcp/Makefile b/src/PVE/Network/SDN/Dhcp/Makefile
new file mode 100644
index 0000000..6546513
--- /dev/null
+++ b/src/PVE/Network/SDN/Dhcp/Makefile
@@ -0,0 +1,8 @@
+SOURCES=Plugin.pm Dnsmasq.pm
+
+
+PERL5DIR=${DESTDIR}/usr/share/perl5
+
+.PHONY: install
+install:
+	for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/SDN/Dhcp/$$i; done
diff --git a/src/PVE/Network/SDN/Dhcp/Plugin.pm b/src/PVE/Network/SDN/Dhcp/Plugin.pm
new file mode 100644
index 0000000..7b9e9b7
--- /dev/null
+++ b/src/PVE/Network/SDN/Dhcp/Plugin.pm
@@ -0,0 +1,65 @@
+package PVE::Network::SDN::Dhcp::Plugin;
+
+use strict;
+use warnings;
+
+use PVE::Cluster;
+use PVE::JSONSchema qw(get_standard_option);
+
+use base qw(PVE::SectionConfig);
+
+my $defaultData = {
+    propertyList => {
+       type => {
+           description => "Plugin type.",
+           format => 'pve-configid',
+           type => 'string',
+       },
+    },
+};
+
+sub private {
+    return $defaultData;
+}
+
+sub add_ip_mapping {
+    my ($class, $dhcp_config, $mac, $ip) = @_;
+    die 'implement in sub class';
+}
+
+sub del_ip_mapping {
+    my ($class, $dhcp_config, $mac) = @_;
+    die 'implement in sub class';
+}
+
+sub configure_range {
+    my ($class, $dhcp_config, $subnet_config, $range_config) = @_;
+    die 'implement in sub class';
+}
+
+sub configure_subnet {
+    my ($class, $dhcp_config, $subnet_config) = @_;
+    die 'implement in sub class';
+}
+
+sub before_configure {
+    my ($class, $dhcp_config) = @_;
+    die 'implement in sub class';
+}
+
+sub after_configure {
+    my ($class, $dhcp_config) = @_;
+    die 'implement in sub class';
+}
+
+sub before_regenerate {
+    my ($class) = @_;
+    die 'implement in sub class';
+}
+
+sub after_regenerate {
+    my ($class, $dhcp_config) = @_;
+    die 'implement in sub class';
+}
+
+1;
diff --git a/src/PVE/Network/SDN/Makefile b/src/PVE/Network/SDN/Makefile
index 92cfcd0..848f7d4 100644
--- a/src/PVE/Network/SDN/Makefile
+++ b/src/PVE/Network/SDN/Makefile
@@ -10,4 +10,5 @@ install:
 	make -C Zones install
 	make -C Ipams install
 	make -C Dns install
+	make -C Dhcp install
 
-- 
2.39.2




^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [WIP v3 pve-network 08/22] sdn: dhcp: add dnsmasq plugin
  2023-11-14 18:05 [pve-devel] [WIP v3 cluster/network/manager/qemu-server 00/22] Add support for DHCP servers to SDN Stefan Hanreich
                   ` (6 preceding siblings ...)
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 07/22] dhcp: add abstract class for DHCP plugins Stefan Hanreich
@ 2023-11-14 18:06 ` Stefan Hanreich
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 09/22] sdn: dhcp: add helper for creating DHCP leases Stefan Hanreich
                   ` (13 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Stefan Hanreich @ 2023-11-14 18:06 UTC (permalink / raw)
  To: pve-devel

Co-Authored-By: Alexandre Derumier <aderumier@odiso.com>
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 debian/control                      |   1 +
 src/PVE/Network/SDN/Dhcp/Dnsmasq.pm | 198 ++++++++++++++++++++++++++++
 2 files changed, 199 insertions(+)
 create mode 100644 src/PVE/Network/SDN/Dhcp/Dnsmasq.pm

diff --git a/debian/control b/debian/control
index 8b720c3..4424096 100644
--- a/debian/control
+++ b/debian/control
@@ -24,6 +24,7 @@ Depends: libpve-common-perl (>= 5.0-45),
          ${misc:Depends},
          ${perl:Depends},
 Recommends: frr-pythontools (>= 8.5.1~), ifupdown2
+Suggests: dnsmasq
 Description: Proxmox VE's SDN (Software Defined Network) stack
  This package contains the Software Defined Network (tech preview) for
  Proxmox VE.
diff --git a/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm b/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm
new file mode 100644
index 0000000..21a6ddd
--- /dev/null
+++ b/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm
@@ -0,0 +1,198 @@
+package PVE::Network::SDN::Dhcp::Dnsmasq;
+
+use strict;
+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 File::Copy;
+
+my $DNSMASQ_CONFIG_ROOT = '/etc/dnsmasq.d';
+my $DNSMASQ_DEFAULT_ROOT = '/etc/default';
+my $DNSMASQ_LEASE_ROOT = '/var/lib/misc';
+
+sub type {
+    return 'dnsmasq';
+}
+
+sub del_ip_mapping {
+    my ($class, $dhcpid, $mac) = @_;
+
+    my $ethers_file = "$DNSMASQ_CONFIG_ROOT/$dhcpid/ethers";
+    my $ethers_tmp_file = "$ethers_file.tmp";
+
+    my $removeFn = sub {
+	open(my $in, '<', $ethers_file) or die "Could not open file '$ethers_file' $!\n";
+	open(my $out, '>', $ethers_tmp_file) or die "Could not open file '$ethers_tmp_file' $!\n";
+
+        while (my $line = <$in>) {
+	    next if $line =~ m/^$mac/;
+	    print $out $line;
+	}
+
+	close $in;
+	close $out;
+
+	move $ethers_tmp_file, $ethers_file;
+
+	chmod 0644, $ethers_file;
+    };
+
+    PVE::Tools::lock_file($ethers_file, 10, $removeFn);
+
+    if ($@) {
+	warn "Unable to remove $mac from the dnsmasq configuration: $@\n";
+	return;
+    }
+
+    my $service_name = "dnsmasq\@$dhcpid";
+    PVE::Tools::run_command(['systemctl', 'reload', $service_name]);
+}
+
+sub add_ip_mapping {
+    my ($class, $dhcpid, $mac, $ip) = @_;
+
+    my $ethers_file = "$DNSMASQ_CONFIG_ROOT/$dhcpid/ethers";
+    my $ethers_tmp_file = "$ethers_file.tmp";
+
+    my $appendFn = sub {
+	open(my $in, '<', $ethers_file) or die "Could not open file '$ethers_file' $!\n";
+	open(my $out, '>', $ethers_tmp_file) or die "Could not open file '$ethers_tmp_file' $!\n";
+
+        while (my $line = <$in>) {
+	    next if $line =~ m/^$mac/;
+	    print $out $line;
+	}
+
+	print $out "$mac,$ip\n";
+	close $in;
+	close $out;
+	move $ethers_tmp_file, $ethers_file;
+	chmod 0644, $ethers_file;
+    };
+
+    PVE::Tools::lock_file($ethers_file, 10, $appendFn);
+
+    if ($@) {
+	warn "Unable to add $mac/$ip to the dnsmasq configuration: $@\n";
+	return;
+    }
+
+    my $service_name = "dnsmasq\@$dhcpid";
+    PVE::Tools::run_command(['systemctl', 'reload', $service_name]);
+}
+
+sub configure_subnet {
+    my ($class, $dhcpid, $subnet_config) = @_;
+
+    die "No gateway defined for subnet $subnet_config->{id}"
+	if !$subnet_config->{gateway};
+
+    my $tag = $subnet_config->{id};
+
+    my @dnsmasq_config = (
+	"listen-address=$subnet_config->{gateway}",
+    );
+
+    my $option_string;
+    if (ip_is_ipv6($subnet_config->{network})) {
+	$option_string = 'option6';
+	push @dnsmasq_config, "enable-ra";
+    } else {
+	$option_string = 'option';
+	push @dnsmasq_config, "dhcp-option=tag:$tag,$option_string:router,$subnet_config->{gateway}";
+    }
+
+    push @dnsmasq_config, "dhcp-option=tag:$tag,$option_string:dns-server,$subnet_config->{'dhcp-dns-server'}"
+	if $subnet_config->{'dhcp-dns-server'};
+
+    PVE::Tools::file_set_contents(
+	"$DNSMASQ_CONFIG_ROOT/$dhcpid/10-$subnet_config->{id}.conf",
+	join("\n", @dnsmasq_config) . "\n"
+    );
+}
+
+sub configure_range {
+    my ($class, $dhcpid, $subnet_config, $range_config) = @_;
+
+    my $range_file = "$DNSMASQ_CONFIG_ROOT/$dhcpid/10-$subnet_config->{id}.ranges.conf",
+    my $tag = $subnet_config->{id};
+
+    open(my $fh, '>>', $range_file) or die "Could not open file '$range_file' $!\n";
+    print $fh "dhcp-range=set:$tag,$range_config->{'start-address'},$range_config->{'end-address'}\n";
+    close $fh;
+}
+
+sub before_configure {
+    my ($class, $dhcpid) = @_;
+
+    my $config_directory = "$DNSMASQ_CONFIG_ROOT/$dhcpid";
+
+    mkdir($config_directory, 755) if !-d $config_directory;
+
+    my $default_config = <<CFG;
+CONFIG_DIR='$config_directory,\*.conf'
+DNSMASQ_OPTS="--conf-file=/dev/null"
+CFG
+
+    PVE::Tools::file_set_contents(
+	"$DNSMASQ_DEFAULT_ROOT/dnsmasq.$dhcpid",
+	$default_config
+    );
+
+    my $default_dnsmasq_config = <<CFG;
+except-interface=lo
+bind-dynamic
+no-resolv
+no-hosts
+dhcp-leasefile=$DNSMASQ_LEASE_ROOT/dnsmasq.$dhcpid.leases
+dhcp-hostsfile=$config_directory/ethers
+dhcp-ignore=tag:!known
+
+# Send an empty WPAD option. This may be REQUIRED to get windows 7 to behave.
+dhcp-option=252,"\\n"
+
+# Send microsoft-specific option to tell windows to release the DHCP lease
+# when it shuts down. Note the "i" flag, to tell dnsmasq to send the
+# value as a four-byte integer - that's what microsoft wants.
+dhcp-option=vendor:MSFT,2,1i
+
+# If a DHCP client claims that its name is "wpad", ignore that.
+# This fixes a security hole. see CERT Vulnerability VU#598349
+dhcp-name-match=set:wpad-ignore,wpad
+dhcp-ignore-names=tag:wpad-ignore
+CFG
+
+    PVE::Tools::file_set_contents(
+	"$config_directory/00-default.conf",
+	$default_dnsmasq_config
+    );
+
+    unlink glob "$config_directory/10-*.conf";
+}
+
+sub after_configure {
+    my ($class, $dhcpid) = @_;
+
+    my $service_name = "dnsmasq\@$dhcpid";
+
+    PVE::Tools::run_command(['systemctl', 'enable', $service_name]);
+    PVE::Tools::run_command(['systemctl', 'restart', $service_name]);
+}
+
+sub before_regenerate {
+    my ($class) = @_;
+
+    PVE::Tools::run_command(['systemctl', 'stop', "dnsmasq@*"]);
+    PVE::Tools::run_command(['systemctl', 'disable', 'dnsmasq@']);
+}
+
+sub after_regenerate {
+    my ($class) = @_;
+    # noop
+}
+
+1;
-- 
2.39.2




^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [WIP v3 pve-network 09/22] sdn: dhcp: add helper for creating DHCP leases
  2023-11-14 18:05 [pve-devel] [WIP v3 cluster/network/manager/qemu-server 00/22] Add support for DHCP servers to SDN Stefan Hanreich
                   ` (7 preceding siblings ...)
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 08/22] sdn: dhcp: add dnsmasq plugin Stefan Hanreich
@ 2023-11-14 18:06 ` Stefan Hanreich
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 10/22] api: add IPAM endpoints Stefan Hanreich
                   ` (12 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Stefan Hanreich @ 2023-11-14 18:06 UTC (permalink / raw)
  To: pve-devel

Co-Authored-By: Alexandre Derumier <aderumier@odiso.com>
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 src/PVE/Network/SDN/Dhcp.pm  | 115 +++++++++++++++++++++++++++++++++++
 src/PVE/Network/SDN/Makefile |   2 +-
 2 files changed, 116 insertions(+), 1 deletion(-)
 create mode 100644 src/PVE/Network/SDN/Dhcp.pm

diff --git a/src/PVE/Network/SDN/Dhcp.pm b/src/PVE/Network/SDN/Dhcp.pm
new file mode 100644
index 0000000..b178927
--- /dev/null
+++ b/src/PVE/Network/SDN/Dhcp.pm
@@ -0,0 +1,115 @@
+package PVE::Network::SDN::Dhcp;
+
+use strict;
+use warnings;
+
+use PVE::Cluster qw(cfs_read_file);
+
+use PVE::Network::SDN;
+use PVE::Network::SDN::SubnetPlugin;
+use PVE::Network::SDN::Dhcp qw(config);
+use PVE::Network::SDN::Subnets qw(sdn_subnets_config config get_dhcp_ranges);
+use PVE::Network::SDN::Dhcp::Plugin;
+use PVE::Network::SDN::Dhcp::Dnsmasq;
+
+use PVE::INotify qw(nodename);
+
+PVE::Network::SDN::Dhcp::Plugin->init();
+
+PVE::Network::SDN::Dhcp::Dnsmasq->register();
+PVE::Network::SDN::Dhcp::Dnsmasq->init();
+
+sub add_mapping {
+    my ($vnetid, $mac, $ip4, $ip6) = @_;
+
+    my $vnet = PVE::Network::SDN::Vnets::get_vnet($vnetid);
+    return if !$vnet;
+
+    my $zoneid = $vnet->{zone};
+    my $zone = PVE::Network::SDN::Zones::get_zone($zoneid);
+
+    return if !$zone->{ipam} || !$zone->{dhcp};
+
+    my $dhcp_plugin = PVE::Network::SDN::Dhcp::Plugin->lookup($zone->{dhcp});
+    $dhcp_plugin->add_ip_mapping($zoneid, $mac, $ip4) if $ip4;
+    $dhcp_plugin->add_ip_mapping($zoneid, $mac, $ip6) if $ip6;
+}
+
+sub remove_mapping {
+    my ($vnetid, $mac) = @_;
+
+    my $vnet = PVE::Network::SDN::Vnets::get_vnet($vnetid);
+    return if !$vnet;
+
+    my $zoneid = $vnet->{zone};
+    my $zone = PVE::Network::SDN::Zones::get_zone($zoneid);
+
+    return if !$zone->{ipam} || !$zone->{dhcp};
+
+    my $dhcp_plugin = PVE::Network::SDN::Dhcp::Plugin->lookup($zone->{dhcp});
+    $dhcp_plugin->del_ip_mapping($zoneid, $mac);
+}
+
+sub regenerate_config {
+    my ($reload) = @_;
+
+    my $cfg = PVE::Network::SDN::running_config();
+
+    my $zone_cfg = $cfg->{zones};
+    my $subnet_cfg = $cfg->{subnets};
+    return if !$zone_cfg && !$subnet_cfg;
+
+    my $nodename = PVE::INotify::nodename();
+
+    my $plugins = PVE::Network::SDN::Dhcp::Plugin->lookup_types();
+
+    foreach my $plugin_name (@$plugins) {
+	my $plugin = PVE::Network::SDN::Dhcp::Plugin->lookup($plugin_name);
+	eval { $plugin->before_regenerate() };
+	die "Could not run before_regenerate for DHCP plugin $plugin_name $@\n" if $@;
+    }
+
+    foreach my $zoneid (sort keys %{$zone_cfg->{ids}}) {
+        my $zone = $zone_cfg->{ids}->{$zoneid};
+        next if !$zone->{dhcp};
+
+	my $dhcp_plugin_name = $zone->{dhcp};
+	my $dhcp_plugin = PVE::Network::SDN::Dhcp::Plugin->lookup($dhcp_plugin_name);
+
+	die "Could not find DHCP plugin: $dhcp_plugin_name" if !$dhcp_plugin; 
+
+	eval { $dhcp_plugin->before_configure($zoneid) };
+	die "Could not run before_configure for DHCP server $zoneid $@\n" if $@;
+
+
+	foreach my $subnet_id (keys %{$subnet_cfg->{ids}}) {
+	    my $subnet_config = PVE::Network::SDN::Subnets::sdn_subnets_config($subnet_cfg, $subnet_id);
+	    my $dhcp_ranges = PVE::Network::SDN::Subnets::get_dhcp_ranges($subnet_config);
+
+	    my ($zone, $subnet_network, $subnet_mask) = split(/-/, $subnet_id);
+	    next if $zone ne $zoneid;
+	    next if !$dhcp_ranges;
+
+	    eval { $dhcp_plugin->configure_subnet($zoneid, $subnet_config) };
+	    warn "Could not configure subnet $subnet_id: $@\n" if $@;
+
+	    foreach my $dhcp_range (@$dhcp_ranges) {
+		eval { $dhcp_plugin->configure_range($zoneid, $subnet_config, $dhcp_range) };
+		warn "Could not configure DHCP range for $subnet_id: $@\n" if $@;
+	    }
+	}
+
+	eval { $dhcp_plugin->after_configure($zoneid) };
+	warn "Could not run after_configure for DHCP server $zoneid $@\n" if $@;
+
+    }
+
+    foreach my $plugin_name (@$plugins) {
+	my $plugin = PVE::Network::SDN::Dhcp::Plugin->lookup($plugin_name);
+
+	eval { $plugin->after_regenerate() };
+	warn "Could not run after_regenerate for DHCP plugin $plugin_name $@\n" if $@;
+    }
+}
+
+1;
diff --git a/src/PVE/Network/SDN/Makefile b/src/PVE/Network/SDN/Makefile
index 848f7d4..3e6e5fb 100644
--- a/src/PVE/Network/SDN/Makefile
+++ b/src/PVE/Network/SDN/Makefile
@@ -1,4 +1,4 @@
-SOURCES=Vnets.pm VnetPlugin.pm Zones.pm Controllers.pm Subnets.pm SubnetPlugin.pm Ipams.pm Dns.pm
+SOURCES=Vnets.pm VnetPlugin.pm Zones.pm Controllers.pm Subnets.pm SubnetPlugin.pm Ipams.pm Dns.pm Dhcp.pm
 
 
 PERL5DIR=${DESTDIR}/usr/share/perl5
-- 
2.39.2




^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [WIP v3 pve-network 10/22] api: add IPAM endpoints
  2023-11-14 18:05 [pve-devel] [WIP v3 cluster/network/manager/qemu-server 00/22] Add support for DHCP servers to SDN Stefan Hanreich
                   ` (8 preceding siblings ...)
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 09/22] sdn: dhcp: add helper for creating DHCP leases Stefan Hanreich
@ 2023-11-14 18:06 ` Stefan Hanreich
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 11/22] api: subnet: add dhcp ranges Stefan Hanreich
                   ` (11 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Stefan Hanreich @ 2023-11-14 18:06 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 src/PVE/API2/Network/SDN.pm       |   6 ++
 src/PVE/API2/Network/SDN/Ipam.pm  | 172 ++++++++++++++++++++++++++++++
 src/PVE/API2/Network/SDN/Makefile |   2 +-
 3 files changed, 179 insertions(+), 1 deletion(-)
 create mode 100644 src/PVE/API2/Network/SDN/Ipam.pm

diff --git a/src/PVE/API2/Network/SDN.pm b/src/PVE/API2/Network/SDN.pm
index d216e48..551afcf 100644
--- a/src/PVE/API2/Network/SDN.pm
+++ b/src/PVE/API2/Network/SDN.pm
@@ -15,6 +15,7 @@ use PVE::Network::SDN;
 use PVE::API2::Network::SDN::Controllers;
 use PVE::API2::Network::SDN::Vnets;
 use PVE::API2::Network::SDN::Zones;
+use PVE::API2::Network::SDN::Ipam;
 use PVE::API2::Network::SDN::Ipams;
 use PVE::API2::Network::SDN::Dns;
 
@@ -35,6 +36,11 @@ __PACKAGE__->register_method ({
     path => 'controllers',
 });
 
+__PACKAGE__->register_method ({
+    subclass => "PVE::API2::Network::SDN::Ipam",
+    path => 'ipam',
+});
+
 __PACKAGE__->register_method ({
     subclass => "PVE::API2::Network::SDN::Ipams",
     path => 'ipams',
diff --git a/src/PVE/API2/Network/SDN/Ipam.pm b/src/PVE/API2/Network/SDN/Ipam.pm
new file mode 100644
index 0000000..66131e3
--- /dev/null
+++ b/src/PVE/API2/Network/SDN/Ipam.pm
@@ -0,0 +1,172 @@
+package PVE::API2::Network::SDN::Ipam;
+
+use strict;
+use warnings;
+
+use PVE::Tools qw(extract_param);
+use PVE::Cluster qw(cfs_read_file cfs_write_file);
+
+use PVE::Network::SDN;
+use PVE::Network::SDN::Dhcp;
+use PVE::Network::SDN::Vnets;
+use PVE::Network::SDN::Ipams::Plugin;
+
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::RPCEnvironment;
+
+use PVE::RESTHandler;
+
+use base qw(PVE::RESTHandler);
+
+__PACKAGE__->register_method ({
+    name => 'dhcpindex',
+    path => '',
+    method => 'GET',
+    description => 'List DHCP Mappings',
+    protected => 1,
+    permissions => {
+    },
+    parameters => {
+    },
+    returns => {
+	type => 'array',
+    },
+    code => sub {
+	my ($param) = @_;
+
+	my $ipam_plugin = PVE::Network::SDN::Ipams::Plugin->lookup('pve');
+	my $ipam_db = $ipam_plugin->read_db();
+
+	my $result = [];
+
+	for my $zone_id (keys %{$ipam_db->{zones}}) {
+	    my $zone_config = PVE::Network::SDN::Zones::get_zone($zone_id, 1);
+            next if !$zone_config || $zone_config->{ipam} ne 'pve' || !$zone_config->{dhcp};
+
+	    my $zone = $ipam_db->{zones}->{$zone_id};
+
+	    my $vnets = PVE::Network::SDN::Zones::get_vnets($zone_id, 1);
+
+	    for my $subnet_cidr (keys %{$zone->{subnets}}) {
+		my $subnet = $zone->{subnets}->{$subnet_cidr};
+		my $ip = new Net::IP ($subnet_cidr) or die 'Found invalid CIDR in IPAM';
+
+		my $vnet = undef;
+		for my $vnet_id (keys %$vnets) {
+		    eval {
+			my ($zone, $subnetid, $subnet_cfg, $ip) = PVE::Network::SDN::Vnets::get_subnet_from_vnet_ip(
+			    $vnet_id,
+			    $ip->ip(),
+			);
+
+			$vnet = $subnet_cfg->{vnet};
+		    };
+
+		    last if $vnet;
+		}
+
+		for my $ip (keys %{$subnet->{ips}}) {
+		    my $entry = $subnet->{ips}->{$ip};
+		    $entry->{zone} = $zone_id;
+		    $entry->{subnet} = $subnet_cidr;
+		    $entry->{ip} = $ip;
+
+		    if ($vnet) {
+			$entry->{vnet} = $vnet;
+		    }
+
+		    push @$result, $entry;
+		}
+	    }
+	}
+
+	return $result;
+    },
+});
+
+__PACKAGE__->register_method ({
+    name => 'dhcpdelete',
+    path => '{vnet}/{mac}',
+    method => 'DELETE',
+    description => 'Delete DHCP Mapping',
+    protected => 1,
+    permissions => {
+    },
+    parameters => {
+	additionalProperties => 0,
+	mac => { type => 'string' },
+	vnet => { type => 'string' },
+    },
+    returns => { type => 'null' },
+    code => sub {
+	my ($param) = @_;
+
+	my $vnet = extract_param($param, 'vnet');
+	my $mac = extract_param($param, 'mac');
+
+	PVE::Network::SDN::Dhcp::remove_mapping($vnet, $mac);
+	PVE::Network::SDN::Vnets::del_ips_from_mac($vnet, $mac);
+
+	return undef;
+    },
+});
+
+__PACKAGE__->register_method ({
+    name => 'dhcpcreate',
+    path => '{vnet}/{mac}',
+    method => 'POST',
+    description => 'Create DHCP Mapping',
+    protected => 1,
+    permissions => {
+    },
+    parameters => {
+	additionalProperties => 0,
+	mac => { type => 'string' },
+	vnet => { type => 'string' },
+	ip => { type => 'string' },
+    },
+    returns => { type => 'null' },
+    code => sub {
+	my ($param) = @_;
+
+	my $vnet = extract_param($param, 'vnet');
+	my $mac = extract_param($param, 'mac');
+	my $ip = extract_param($param, 'ip');
+
+	PVE::Network::SDN::Vnets::add_ip($vnet, $ip, '', $mac, undef);
+
+	return undef;
+    },
+});
+__PACKAGE__->register_method ({
+    name => 'dhcpupdate',
+    path => '{vnet}/{mac}',
+    method => 'PUT',
+    description => 'Update DHCP Mapping',
+    protected => 1,
+    permissions => {
+    },
+    parameters => {
+	additionalProperties => 0,
+	mac => { type => 'string' },
+	vnet => { type => 'string' },
+	vmid => { type => 'string' },
+	ip => { type => 'string' },
+    },
+    returns => { type => 'null' },
+    code => sub {
+	my ($param) = @_;
+
+	my $vnet = extract_param($param, 'vnet');
+	my $mac = extract_param($param, 'mac');
+	my $vmid = extract_param($param, 'vmid');
+	my $ip = extract_param($param, 'ip');
+
+	PVE::Network::SDN::Vnets::del_ips_from_mac($vnet, $mac, '');
+	PVE::Network::SDN::Vnets::add_ip($vnet, $ip, '', $mac, $vmid);
+
+	return undef;
+    },
+});
+
+1;
diff --git a/src/PVE/API2/Network/SDN/Makefile b/src/PVE/API2/Network/SDN/Makefile
index 3683fa4..2480c09 100644
--- a/src/PVE/API2/Network/SDN/Makefile
+++ b/src/PVE/API2/Network/SDN/Makefile
@@ -1,4 +1,4 @@
-SOURCES=Vnets.pm Zones.pm Controllers.pm Subnets.pm Ipams.pm Dns.pm
+SOURCES=Vnets.pm Zones.pm Controllers.pm Subnets.pm Ipams.pm Ipam.pm Dns.pm
 
 
 PERL5DIR=${DESTDIR}/usr/share/perl5
-- 
2.39.2




^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [WIP v3 pve-network 11/22] api: subnet: add dhcp ranges
  2023-11-14 18:05 [pve-devel] [WIP v3 cluster/network/manager/qemu-server 00/22] Add support for DHCP servers to SDN Stefan Hanreich
                   ` (9 preceding siblings ...)
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 10/22] api: add IPAM endpoints Stefan Hanreich
@ 2023-11-14 18:06 ` Stefan Hanreich
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 12/22] api: zone: add dhcp options Stefan Hanreich
                   ` (10 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Stefan Hanreich @ 2023-11-14 18:06 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 src/PVE/API2/Network/SDN/Subnets.pm | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/PVE/API2/Network/SDN/Subnets.pm b/src/PVE/API2/Network/SDN/Subnets.pm
index eb6b41b..c263cd5 100644
--- a/src/PVE/API2/Network/SDN/Subnets.pm
+++ b/src/PVE/API2/Network/SDN/Subnets.pm
@@ -29,6 +29,7 @@ my $api_sdn_subnets_config = sub {
     my $scfg = dclone(PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $id));
     $scfg->{subnet} = $id;
     $scfg->{digest} = $cfg->{digest};
+    $scfg->{'dhcp-range'} = PVE::Network::SDN::Subnets::get_dhcp_ranges($scfg);
 
     return $scfg;
 };
-- 
2.39.2




^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [WIP v3 pve-network 12/22] api: zone: add dhcp options
  2023-11-14 18:05 [pve-devel] [WIP v3 cluster/network/manager/qemu-server 00/22] Add support for DHCP servers to SDN Stefan Hanreich
                   ` (10 preceding siblings ...)
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 11/22] api: subnet: add dhcp ranges Stefan Hanreich
@ 2023-11-14 18:06 ` Stefan Hanreich
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 13/22] dhcp: regenerate config for DHCP servers on reload Stefan Hanreich
                   ` (9 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Stefan Hanreich @ 2023-11-14 18:06 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 src/PVE/API2/Network/SDN/Zones.pm | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/PVE/API2/Network/SDN/Zones.pm b/src/PVE/API2/Network/SDN/Zones.pm
index 4c8b7e1..1c3356e 100644
--- a/src/PVE/API2/Network/SDN/Zones.pm
+++ b/src/PVE/API2/Network/SDN/Zones.pm
@@ -99,6 +99,7 @@ __PACKAGE__->register_method ({
 			    reversedns => { type => 'string', optional => 1},
 			    dnszone => { type => 'string', optional => 1},
 			    ipam => { type => 'string', optional => 1},
+			    dhcp => { type => 'string', optional => 1},
 			    pending => { optional => 1},
 			    state => { type => 'string', optional => 1},
 			    nodes => { type => 'string', optional => 1},
-- 
2.39.2




^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [WIP v3 pve-network 13/22] dhcp: regenerate config for DHCP servers on reload
  2023-11-14 18:05 [pve-devel] [WIP v3 cluster/network/manager/qemu-server 00/22] Add support for DHCP servers to SDN Stefan Hanreich
                   ` (11 preceding siblings ...)
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 12/22] api: zone: add dhcp options Stefan Hanreich
@ 2023-11-14 18:06 ` Stefan Hanreich
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 14/22] sdn: fix tests Stefan Hanreich
                   ` (8 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Stefan Hanreich @ 2023-11-14 18:06 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 src/PVE/Network/SDN.pm | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/src/PVE/Network/SDN.pm b/src/PVE/Network/SDN.pm
index 057034f..c306527 100644
--- a/src/PVE/Network/SDN.pm
+++ b/src/PVE/Network/SDN.pm
@@ -12,6 +12,7 @@ use PVE::Network::SDN::Vnets;
 use PVE::Network::SDN::Zones;
 use PVE::Network::SDN::Controllers;
 use PVE::Network::SDN::Subnets;
+use PVE::Network::SDN::Dhcp;
 
 use PVE::Tools qw(extract_param dir_glob_regex run_command);
 use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
@@ -155,7 +156,7 @@ sub commit_config {
     my $controllers = { ids => $controllers_cfg->{ids} };
     my $subnets = { ids => $subnets_cfg->{ids} };
 
-     $cfg = { version => $version, vnets => $vnets, zones => $zones, controllers => $controllers, subnets => $subnets };
+    $cfg = { version => $version, vnets => $vnets, zones => $zones, controllers => $controllers, subnets => $subnets };
 
     cfs_write_file($running_cfg, $cfg);
 }
@@ -231,6 +232,12 @@ sub generate_controller_config {
     PVE::Network::SDN::Controllers::reload_controller() if $reload;
 }
 
+sub generate_dhcp_config {
+    my ($reload) = @_;
+
+    PVE::Network::SDN::Dhcp::regenerate_config($reload);
+}
+
 sub encode_value {
     my ($type, $key, $value) = @_;
 
-- 
2.39.2




^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [WIP v3 pve-network 14/22] sdn: fix tests
  2023-11-14 18:05 [pve-devel] [WIP v3 cluster/network/manager/qemu-server 00/22] Add support for DHCP servers to SDN Stefan Hanreich
                   ` (12 preceding siblings ...)
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 13/22] dhcp: regenerate config for DHCP servers on reload Stefan Hanreich
@ 2023-11-14 18:06 ` Stefan Hanreich
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-manager 15/22] sdn: regenerate DHCP config on reload Stefan Hanreich
                   ` (7 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Stefan Hanreich @ 2023-11-14 18:06 UTC (permalink / raw)
  To: pve-devel

From: Alexandre Derumier <aderumier@odiso.com>

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 src/test/run_test_subnets.pl | 8 +++++++-
 src/test/run_test_vnets.pl   | 4 ++--
 2 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/src/test/run_test_subnets.pl b/src/test/run_test_subnets.pl
index f6564e1..c98359a 100755
--- a/src/test/run_test_subnets.pl
+++ b/src/test/run_test_subnets.pl
@@ -109,6 +109,12 @@ foreach my $path (@plugins) {
 	    my $ipam_config = read_sdn_config ("$path/ipam_config");
 	    return $ipam_config;
 	},
+	add_cache_mac_ip => sub {
+	    return;
+	},
+	del_cache_mac_ip => sub {
+	    return;
+	}
     );
 
     ## add_subnet
@@ -192,7 +198,7 @@ foreach my $path (@plugins) {
     $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{"'.$ip.'":{"gateway":1},"'.$ipnextfree.'":{},"'.$ip2.'":{}}}}}}}';
 
     eval {
-	$ip3 = PVE::Network::SDN::Subnets::next_free_ip($zone, $subnetid, $subnet, $hostname, $mac, $description);
+	$ip3 = PVE::Network::SDN::Subnets::add_next_free_ip($zone, $subnetid, $subnet, $hostname, $mac, $description);
     };
 
     if ($@) {
diff --git a/src/test/run_test_vnets.pl b/src/test/run_test_vnets.pl
index 5aeb676..dc9da67 100755
--- a/src/test/run_test_vnets.pl
+++ b/src/test/run_test_vnets.pl
@@ -231,7 +231,7 @@ foreach my $path (@plugins) {
     $expected = $ipam ? $cidr3 : undef;
 
     eval {
-	$result = PVE::Network::SDN::Vnets::get_next_free_cidr($vnetid, $hostname, $mac, $description, $ipversion);
+	$result = PVE::Network::SDN::Vnets::add_next_free_cidr($vnetid, $hostname, $mac, $description);
     };
 
     if ($@) {
@@ -309,7 +309,7 @@ foreach my $path (@plugins) {
     $expected = $ipam ? $cidr1 : undef;
 
     eval {
-	$result = PVE::Network::SDN::Vnets::get_next_free_cidr($vnetid, $hostname, $mac, $description, $ipversion);
+	$result = PVE::Network::SDN::Vnets::add_next_free_cidr($vnetid, $hostname, $mac, $description);
     };
 
     if ($@) {
-- 
2.39.2




^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [WIP v3 pve-manager 15/22] sdn: regenerate DHCP config on reload
  2023-11-14 18:05 [pve-devel] [WIP v3 cluster/network/manager/qemu-server 00/22] Add support for DHCP servers to SDN Stefan Hanreich
                   ` (13 preceding siblings ...)
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 14/22] sdn: fix tests Stefan Hanreich
@ 2023-11-14 18:06 ` Stefan Hanreich
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-manager 16/22] sdn: add DHCP option to Zone dialogue Stefan Hanreich
                   ` (6 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Stefan Hanreich @ 2023-11-14 18:06 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 PVE/API2/Network.pm | 1 +
 1 file changed, 1 insertion(+)

diff --git a/PVE/API2/Network.pm b/PVE/API2/Network.pm
index 00d964a79..f39f04f52 100644
--- a/PVE/API2/Network.pm
+++ b/PVE/API2/Network.pm
@@ -660,6 +660,7 @@ __PACKAGE__->register_method({
 
 	    if ($have_sdn) {
 		PVE::Network::SDN::generate_zone_config();
+		PVE::Network::SDN::generate_dhcp_config();
 	    }
 
 	    my $err = sub {
-- 
2.39.2




^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [WIP v3 pve-manager 16/22] sdn: add DHCP option to Zone dialogue
  2023-11-14 18:05 [pve-devel] [WIP v3 cluster/network/manager/qemu-server 00/22] Add support for DHCP servers to SDN Stefan Hanreich
                   ` (14 preceding siblings ...)
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-manager 15/22] sdn: regenerate DHCP config on reload Stefan Hanreich
@ 2023-11-14 18:06 ` Stefan Hanreich
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-manager 17/22] sdn: subnet: add panel for editing DHCP ranges Stefan Hanreich
                   ` (5 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Stefan Hanreich @ 2023-11-14 18:06 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 www/manager6/sdn/zones/Base.js       |  4 ++--
 www/manager6/sdn/zones/SimpleEdit.js | 10 ++++++++++
 2 files changed, 12 insertions(+), 2 deletions(-)

diff --git a/www/manager6/sdn/zones/Base.js b/www/manager6/sdn/zones/Base.js
index 602e4c16b..80ce51bac 100644
--- a/www/manager6/sdn/zones/Base.js
+++ b/www/manager6/sdn/zones/Base.js
@@ -55,7 +55,7 @@ Ext.define('PVE.panel.SDNZoneBase', {
 	    },
 	);
 
-	me.advancedItems = [
+	me.advancedItems.unshift(
 	    {
 		xtype: 'pveSDNDnsSelector',
 		fieldLabel: gettext('DNS Server'),
@@ -77,7 +77,7 @@ Ext.define('PVE.panel.SDNZoneBase', {
 		fieldLabel: gettext('DNS Zone'),
 		allowBlank: true,
 	    },
-	];
+	);
 
 	me.callParent();
     },
diff --git a/www/manager6/sdn/zones/SimpleEdit.js b/www/manager6/sdn/zones/SimpleEdit.js
index cb7c34035..ee4ac8ec8 100644
--- a/www/manager6/sdn/zones/SimpleEdit.js
+++ b/www/manager6/sdn/zones/SimpleEdit.js
@@ -19,6 +19,16 @@ Ext.define('PVE.sdn.zones.SimpleInputPanel', {
 	var me = this;
 
         me.items = [];
+	me.advancedItems = [
+	    {
+		xtype: 'proxmoxcheckbox',
+		name: 'dhcp',
+		inputValue: 'dnsmasq',
+		uncheckedValue: 0,
+		checked: false,
+		fieldLabel: gettext('automatic DHCP'),
+	    },
+	];
 
 	me.callParent();
     },
-- 
2.39.2




^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [WIP v3 pve-manager 17/22] sdn: subnet: add panel for editing DHCP ranges
  2023-11-14 18:05 [pve-devel] [WIP v3 cluster/network/manager/qemu-server 00/22] Add support for DHCP servers to SDN Stefan Hanreich
                   ` (15 preceding siblings ...)
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-manager 16/22] sdn: add DHCP option to Zone dialogue Stefan Hanreich
@ 2023-11-14 18:06 ` Stefan Hanreich
  2023-11-15 13:09   ` DERUMIER, Alexandre
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-manager 18/22] sdn: dhcp: add view for DHCP mappings Stefan Hanreich
                   ` (4 subsequent siblings)
  21 siblings, 1 reply; 27+ messages in thread
From: Stefan Hanreich @ 2023-11-14 18:06 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 www/manager6/sdn/SubnetEdit.js | 161 ++++++++++++++++++++++++++++++++-
 1 file changed, 160 insertions(+), 1 deletion(-)

diff --git a/www/manager6/sdn/SubnetEdit.js b/www/manager6/sdn/SubnetEdit.js
index b9825d2a3..ab3b9d021 100644
--- a/www/manager6/sdn/SubnetEdit.js
+++ b/www/manager6/sdn/SubnetEdit.js
@@ -56,6 +56,148 @@ Ext.define('PVE.sdn.SubnetInputPanel', {
     ],
 });
 
+Ext.define('PVE.sdn.SubnetDhcpRangePanel', {
+    extend: 'Ext.form.FieldContainer',
+    mixins: ['Ext.form.field.Field'],
+
+    initComponent: function() {
+	let me = this;
+
+	me.callParent();
+	me.initField();
+    },
+
+    getValue: function() {
+	let me = this;
+	let store = me.lookup('grid').getStore();
+
+	let data = [];
+
+	store.getData().each((item) => console.log(item));
+
+	store.getData()
+	    .each((item) =>
+		data.push(`start-address=${item.data['start-address']},end-address=${item.data['end-address']}`),
+	    );
+
+	return data;
+    },
+
+    getSubmitData: function() {
+	let me = this;
+	let data = {};
+
+	let value = me.getValue();
+	if (value) {
+	    data[me.getName()] = value;
+	}
+
+	return data;
+    },
+
+    setValue: function(dhcpRanges) {
+	let me = this;
+	let store = me.lookup('grid').getStore();
+	store.setData(dhcpRanges);
+    },
+
+    getErrors: function() {
+	let me = this;
+        let errors = [];
+
+	return errors;
+    },
+
+    controller: {
+	xclass: 'Ext.app.ViewController',
+
+	addRange: function() {
+	    let me = this;
+	    me.lookup('grid').getStore().add({});
+	},
+
+	removeRange: function(field) {
+	    let me = this;
+	    let record = field.getWidgetRecord();
+
+	    me.lookup('grid').getStore().remove(record);
+	},
+
+	onValueChange: function(field, value) {
+	    let me = this;
+	    let record = field.getWidgetRecord();
+	    let column = field.getWidgetColumn();
+
+	    record.set(column.dataIndex, value);
+	    record.commit();
+	},
+
+	control: {
+	    'grid button': {
+		click: 'removeRange',
+	    },
+	    'field': {
+		change: 'onValueChange',
+	    },
+	},
+    },
+
+    items: [
+	{
+	    xtype: 'grid',
+	    reference: 'grid',
+	    scrollable: true,
+	    store: {
+		fields: ['start-address', 'end-address'],
+	    },
+	    columns: [
+		{
+		    text: gettext('Start Address'),
+		    xtype: 'widgetcolumn',
+		    dataIndex: 'start-address',
+		    flex: 1,
+		    widget: {
+			xtype: 'textfield',
+			vtype: 'IP64Address',
+		    },
+		},
+		{
+		    text: gettext('End Address'),
+		    xtype: 'widgetcolumn',
+		    dataIndex: 'end-address',
+		    flex: 1,
+		    widget: {
+			xtype: 'textfield',
+			vtype: 'IP64Address',
+		    },
+		},
+		{
+		    xtype: 'widgetcolumn',
+		    width: 40,
+		    widget: {
+			xtype: 'button',
+			iconCls: 'fa fa-trash-o',
+		    },
+		},
+	    ],
+	},
+	{
+	    xtype: 'container',
+	    layout: {
+		type: 'hbox',
+	    },
+	    items: [
+		{
+		    xtype: 'button',
+		    text: gettext('Add'),
+		    iconCls: 'fa fa-plus-circle',
+		    handler: 'addRange',
+		},
+	    ],
+	},
+    ],
+});
+
 Ext.define('PVE.sdn.SubnetEdit', {
     extend: 'Proxmox.window.Edit',
 
@@ -67,6 +209,8 @@ Ext.define('PVE.sdn.SubnetEdit', {
 
     base_url: undefined,
 
+    bodyPadding: 0,
+
     initComponent: function() {
 	var me = this;
 
@@ -82,11 +226,22 @@ Ext.define('PVE.sdn.SubnetEdit', {
 
 	let ipanel = Ext.create('PVE.sdn.SubnetInputPanel', {
 	    isCreate: me.isCreate,
+	    title: gettext('General'),
+	});
+
+	let dhcpPanel = Ext.create('PVE.sdn.SubnetDhcpRangePanel', {
+	    isCreate: me.isCreate,
+	    title: gettext('DHCP Ranges'),
+	    name: 'dhcp-range',
 	});
 
 	Ext.apply(me, {
 	    items: [
-		ipanel,
+		{
+		    xtype: 'tabpanel',
+		    bodyPadding: 10,
+		    items: [ipanel, dhcpPanel],
+		},
 	    ],
 	});
 
@@ -97,6 +252,10 @@ Ext.define('PVE.sdn.SubnetEdit', {
 		success: function(response, options) {
 		    let values = response.result.data;
 		    ipanel.setValues(values);
+
+		    if (values['dhcp-range']) {
+			dhcpPanel.setValue(values['dhcp-range']);
+		    }
 		},
 	    });
 	}
-- 
2.39.2




^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [WIP v3 pve-manager 18/22] sdn: dhcp: add view for DHCP mappings
  2023-11-14 18:05 [pve-devel] [WIP v3 cluster/network/manager/qemu-server 00/22] Add support for DHCP servers to SDN Stefan Hanreich
                   ` (16 preceding siblings ...)
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-manager 17/22] sdn: subnet: add panel for editing DHCP ranges Stefan Hanreich
@ 2023-11-14 18:06 ` Stefan Hanreich
  2023-11-15 12:09   ` DERUMIER, Alexandre
  2023-11-14 18:06 ` [pve-devel] [WIP v3 qemu-server 19/22] vmnic add|remove : add|del ip in ipam Stefan Hanreich
                   ` (3 subsequent siblings)
  21 siblings, 1 reply; 27+ messages in thread
From: Stefan Hanreich @ 2023-11-14 18:06 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 www/css/ext6-pve.css            |  10 +-
 www/manager6/Makefile           |   2 +
 www/manager6/dc/Config.js       |  12 +-
 www/manager6/sdn/MappingEdit.js |  65 ++++++++++
 www/manager6/tree/DhcpTree.js   | 215 ++++++++++++++++++++++++++++++++
 5 files changed, 296 insertions(+), 8 deletions(-)
 create mode 100644 www/manager6/sdn/MappingEdit.js
 create mode 100644 www/manager6/tree/DhcpTree.js

diff --git a/www/css/ext6-pve.css b/www/css/ext6-pve.css
index e18b173f5..e5e616832 100644
--- a/www/css/ext6-pve.css
+++ b/www/css/ext6-pve.css
@@ -510,23 +510,21 @@ div.right-aligned {
     content: ' ';
 }
 
-.fa-sdn:before {
+.x-fa-sdn-treelist:before {
     width: 14px;
     height: 14px;
     position: absolute;
     left: 1px;
     top: 4px;
+}
+
+.fa-sdn:before {
     background-image:url(../images/icon-sdn.svg);
     background-size: 14px 14px;
     content: ' ';
 }
 
 .fa-network-wired:before {
-    width: 14px;
-    height: 14px;
-    position: absolute;
-    left: 1px;
-    top: 4px;
     background-image:url(../images/icon-fa-network-wired.svg);
     background-size: 14px 14px;
     content: ' ';
diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index dccd2ba1c..d226c8faa 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -108,6 +108,7 @@ JSSRC= 							\
 	tree/ResourceTree.js				\
 	tree/SnapshotTree.js				\
 	tree/ResourceMapTree.js				\
+	tree/DhcpTree.js				\
 	window/Backup.js				\
 	window/BackupConfig.js				\
 	window/BulkAction.js				\
@@ -274,6 +275,7 @@ JSSRC= 							\
 	sdn/ZoneContentView.js				\
 	sdn/ZoneContentPanel.js				\
 	sdn/ZoneView.js					\
+	sdn/MappingEdit.js				\
 	sdn/OptionsPanel.js				\
 	sdn/controllers/Base.js				\
 	sdn/controllers/EvpnEdit.js			\
diff --git a/www/manager6/dc/Config.js b/www/manager6/dc/Config.js
index 7d01da5fb..0e3948ef4 100644
--- a/www/manager6/dc/Config.js
+++ b/www/manager6/dc/Config.js
@@ -195,7 +195,7 @@ Ext.define('PVE.dc.Config', {
 		    groups: ['sdn'],
 		    title: gettext('Zones'),
 		    hidden: true,
-		    iconCls: 'fa fa-th',
+		    iconCls: 'fa fa-th x-fa-sdn-treelist',
 		    itemId: 'sdnzone',
 		},
 		{
@@ -203,7 +203,7 @@ Ext.define('PVE.dc.Config', {
 		    groups: ['sdn'],
 		    title: 'VNets',
 		    hidden: true,
-		    iconCls: 'fa fa-network-wired',
+		    iconCls: 'fa fa-network-wired x-fa-sdn-treelist',
 		    itemId: 'sdnvnet',
 		},
 		{
@@ -213,6 +213,14 @@ Ext.define('PVE.dc.Config', {
 		    hidden: true,
 		    iconCls: 'fa fa-gear',
 		    itemId: 'sdnoptions',
+		},
+		{
+		    xtype: 'pveDhcpTree',
+		    groups: ['sdn'],
+		    title: 'Dhcp Mappings',
+		    hidden: true,
+		    iconCls: 'fa fa-gear',
+		    itemId: 'sdnmappings',
 		});
 	    }
 
diff --git a/www/manager6/sdn/MappingEdit.js b/www/manager6/sdn/MappingEdit.js
new file mode 100644
index 000000000..533fc6249
--- /dev/null
+++ b/www/manager6/sdn/MappingEdit.js
@@ -0,0 +1,65 @@
+Ext.define('PVE.sdn.DhcpMappingInputPanel', {
+    extend: 'Proxmox.panel.InputPanel',
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    isCreate: false,
+
+    items: [
+	{
+	    xtype: 'pmxDisplayEditField',
+	    name: 'vmid',
+	    fieldLabel: gettext('VMID'),
+	    allowBlank: false,
+	    editable: false,
+	},
+	{
+	    xtype: 'pmxDisplayEditField',
+	    name: 'mac',
+	    fieldLabel: gettext('MAC'),
+	    allowBlank: false,
+	    cbind: {
+		editable: '{isCreate}',
+	    },
+	},
+	{
+	    xtype: 'proxmoxtextfield',
+	    name: 'ip',
+	    fieldLabel: gettext('IP'),
+	    allowBlank: false,
+	},
+    ],
+});
+
+Ext.define('PVE.sdn.MappingEdit', {
+    extend: 'Proxmox.window.Edit',
+
+    subject: gettext('DHCP Mapping'),
+    width: 350,
+
+    isCreate: false,
+    mapping: {},
+
+    submitUrl: function(url, values) {
+	return `${url}/${values.vnet}/${values.mac}`;
+    },
+
+    initComponent: function() {
+	var me = this;
+
+	me.method = me.isCreate ? 'POST' : 'PUT';
+
+	let ipanel = Ext.create('PVE.sdn.DhcpMappingInputPanel', {
+	    isCreate: me.isCreate,
+	});
+
+	Ext.apply(me, {
+	    items: [
+		ipanel,
+	    ],
+	});
+
+	me.callParent();
+
+	ipanel.setValues(me.mapping);
+    },
+});
diff --git a/www/manager6/tree/DhcpTree.js b/www/manager6/tree/DhcpTree.js
new file mode 100644
index 000000000..c714f0ff0
--- /dev/null
+++ b/www/manager6/tree/DhcpTree.js
@@ -0,0 +1,215 @@
+Ext.define('PVE.sdn.DhcpTree', {
+    extend: 'Ext.tree.Panel',
+    xtype: 'pveDhcpTree',
+
+    layout: 'fit',
+    rootVisible: false,
+    animate: false,
+
+    store: {
+	sorters: ['ip', 'name'],
+    },
+
+    controller: {
+	xclass: 'Ext.app.ViewController',
+	reload: function() {
+	    let me = this;
+
+	    Proxmox.Utils.API2Request({
+		url: `/cluster/sdn/ipam`,
+		method: 'GET',
+		success: function(response, opts) {
+		    let root = {
+			name: '__root',
+			expanded: true,
+			children: [],
+		    };
+
+		    let zones = {};
+		    let vnets = {};
+		    let subnets = {};
+
+		    response.result.data.forEach((element) => {
+			element.leaf = true;
+
+			if (!(element.zone in zones)) {
+			    let zone = {
+				name: element.zone,
+				type: 'zone',
+				iconCls: 'fa fa-th',
+				expanded: true,
+				children: [],
+			    };
+
+			    zones[element.zone] = zone;
+			    root.children.push(zone);
+			}
+
+			if (!(element.vnet in vnets)) {
+			    let vnet = {
+				name: element.vnet,
+				type: 'vnet',
+				iconCls: 'fa fa-network-wired',
+				expanded: true,
+				children: [],
+			    };
+
+			    vnets[element.vnet] = vnet;
+			    zones[element.zone].children.push(vnet);
+			}
+
+			if (!(element.subnet in subnets)) {
+			    let subnet = {
+				name: element.subnet,
+				type: 'subnet',
+				expanded: true,
+				children: [],
+			    };
+
+			    subnets[element.subnet] = subnet;
+			    vnets[element.vnet].children.push(subnet);
+			}
+
+			element.type = 'mapping';
+			subnets[element.subnet].children.push(element);
+		    });
+
+		    me.getView().setRootNode(root);
+		},
+	    });
+	},
+	init: function(view) {
+	    let me = this;
+	    me.reload();
+	},
+	onDelete: function(table, rI, cI, item, e, { data }) {
+	    let me = this;
+	    let view = me.getView();
+
+	    Ext.Msg.show({
+		title: gettext('Confirm'),
+		icon: Ext.Msg.WARNING,
+		message: gettext('Are you sure you want to remove DHCP mapping {0}'),
+		buttons: Ext.Msg.YESNO,
+		defaultFocus: 'no',
+		callback: function(btn) {
+		    if (btn !== 'yes') {
+		        return;
+		    }
+
+		    Proxmox.Utils.API2Request({
+			url: `/cluster/sdn/ipam/${data.vnet}/${data.mac}`,
+			method: 'DELETE',
+			waitMsgTarget: view,
+			failure: function(response, opts) {
+			    Ext.Msg.alert(gettext('Error'), response.htmlStatus);
+			},
+			callback: me.reload.bind(me),
+		    });
+		},
+	    });
+	},
+    },
+
+    columns: [
+	{
+	    xtype: 'treecolumn',
+	    text: 'Name / VMID',
+	    dataIndex: 'name',
+	    width: 200,
+	    renderer: function(value, meta, record) {
+		if (record.get('gateway')) {
+		    return 'Gateway';
+		}
+
+		return record.get('name') ?? record.get('vmid') ?? '';
+	    },
+	},
+	{
+	    text: 'IP',
+	    dataIndex: 'ip',
+	    width: 200,
+	},
+	{
+	    text: 'MAC',
+	    dataIndex: 'mac',
+	    width: 200,
+	},
+	{
+	    text: 'Gateway',
+	    dataIndex: 'gateway',
+	    width: 200,
+	},
+	{
+	    header: gettext('Actions'),
+	    xtype: 'actioncolumn',
+	    dataIndex: 'text',
+	    width: 150,
+	    items: [
+		{
+		    handler: function(table, rI, cI, item, e, { data }) {
+			let me = this;
+
+			let win = Ext.create('PVE.sdn.MappingEdit', {
+			    autoShow: true,
+			    mapping: {},
+			    url: `/cluster/sdn/ipam`,
+			    method: 'POST',
+			    isCreate: true,
+			    extraRequestParams: {
+				vnet: data.name,
+			    },
+			});
+
+			win.on('destroy', me.reload);
+		    },
+		    getTip: (v, m, rec) => gettext('Add'),
+		    getClass: (v, m, { data }) => {
+			if (data.type === 'vnet') {
+			    return 'fa fa-plus-square';
+			}
+
+			return 'pmx-hidden';
+		    },
+                },
+		{
+		    handler: function(table, rI, cI, item, e, { data }) {
+			let me = this;
+
+			let win = Ext.create('PVE.sdn.MappingEdit', {
+			    autoShow: true,
+			    mapping: data,
+			    url: `/cluster/sdn/ipam`,
+			    method: 'PUT',
+			    extraRequestParams: {
+				vmid: data.vmid,
+				vnet: data.vnet,
+			    },
+			});
+
+			win.on('destroy', me.reload);
+		    },
+		    getTip: (v, m, rec) => 'Edit',
+		    getClass: (v, m, { data }) => {
+			if (data.type === 'mapping' && !data.gateway) {
+			    return 'fa fa-pencil fa-fw';
+			}
+
+			return 'pmx-hidden';
+		    },
+                },
+		{
+		    handler: 'onDelete',
+		    getTip: (v, m, rec) => 'Delete',
+		    getClass: (v, m, { data }) => {
+			if (data.type === 'mapping' && !data.gateway) {
+			    return 'fa critical fa-trash-o';
+			}
+
+			return 'pmx-hidden';
+		    },
+                },
+	    ],
+	},
+    ],
+});
-- 
2.39.2




^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [WIP v3 qemu-server 19/22] vmnic add|remove : add|del ip in ipam
  2023-11-14 18:05 [pve-devel] [WIP v3 cluster/network/manager/qemu-server 00/22] Add support for DHCP servers to SDN Stefan Hanreich
                   ` (17 preceding siblings ...)
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-manager 18/22] sdn: dhcp: add view for DHCP mappings Stefan Hanreich
@ 2023-11-14 18:06 ` Stefan Hanreich
  2023-11-14 18:06 ` [pve-devel] [WIP v3 qemu-server 20/22] vm_start : vm-network-scripts: get ip from ipam and add dhcp reservation Stefan Hanreich
                   ` (2 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Stefan Hanreich @ 2023-11-14 18:06 UTC (permalink / raw)
  To: pve-devel

From: Alexandre Derumier <aderumier@odiso.com>

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 PVE/QemuServer.pm | 38 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm
index c465fb6..1ae1cb0 100644
--- a/PVE/QemuServer.pm
+++ b/PVE/QemuServer.pm
@@ -64,6 +64,8 @@ use PVE::QemuServer::USB;
 my $have_sdn;
 eval {
     require PVE::Network::SDN::Zones;
+    require PVE::Network::SDN::Vnets;
+    require PVE::Network::SDN::Dhcp;
     $have_sdn = 1;
 };
 
@@ -4998,6 +5000,11 @@ sub vmconfig_hotplug_pending {
 	    } elsif ($opt =~ m/^net(\d+)$/) {
 		die "skip\n" if !$hotplug_features->{network};
 		vm_deviceunplug($vmid, $conf, $opt);
+		if($have_sdn) {
+		    my $net = PVE::QemuServer::parse_net($conf->{$opt});
+		    PVE::Network::SDN::Dhcp::remove_mapping($net->{bridge}, $net->{macaddr});
+		    PVE::Network::SDN::Vnets::del_ips_from_mac($net->{bridge}, $net->{macaddr}, $conf->{name});
+		}
 	    } elsif (is_valid_drivename($opt)) {
 		die "skip\n" if !$hotplug_features->{disk} || $opt =~ m/(ide|sata)(\d+)/;
 		vm_deviceunplug($vmid, $conf, $opt);
@@ -5203,6 +5210,12 @@ sub vmconfig_apply_pending {
 		die "internal error";
 	    } elsif (defined($conf->{$opt}) && is_valid_drivename($opt)) {
 		vmconfig_delete_or_detach_drive($vmid, $storecfg, $conf, $opt, $force);
+	    } elsif (defined($conf->{$opt}) && $opt =~ m/^net\d+$/) {
+		if($have_sdn) {
+		    my $net = PVE::QemuServer::parse_net($conf->{$opt});
+		    PVE::Network::SDN::Dhcp::remove_mapping($net->{bridge}, $net->{macaddr});
+		    PVE::Network::SDN::Vnets::del_ips_from_mac($net->{bridge}, $net->{macaddr}, $conf->{name});
+		}
 	    }
 	};
 	if (my $err = $@) {
@@ -5222,6 +5235,21 @@ sub vmconfig_apply_pending {
 	eval {
 	    if (defined($conf->{$opt}) && is_valid_drivename($opt)) {
 		vmconfig_register_unused_drive($storecfg, $vmid, $conf, parse_drive($opt, $conf->{$opt}))
+	    } elsif (defined($conf->{pending}->{$opt}) && $opt =~ m/^net\d+$/) {
+		if($have_sdn) {
+                    my $new_net = PVE::QemuServer::parse_net($conf->{pending}->{$opt});
+		    if ($conf->{$opt}){
+		        my $old_net = PVE::QemuServer::parse_net($conf->{$opt});
+
+			if ($old_net->{bridge} ne $new_net->{bridge} ||
+			    $old_net->{macaddr} ne $new_net->{macaddr}) {
+			    PVE::Network::SDN::Dhcp::remove_mapping($old_net->{bridge}, $old_net->{macaddr});
+			    PVE::Network::SDN::Vnets::del_ips_from_mac($old_net->{bridge}, $old_net->{macaddr}, $conf->{name});
+			}
+		   }
+		   #fixme: reuse ip if mac change && same bridge
+		   PVE::Network::SDN::Vnets::add_next_free_cidr($new_net->{bridge}, $conf->{name}, $new_net->{macaddr}, $vmid, undef, 1);
+		}
 	    }
 	};
 	if (my $err = $@) {
@@ -5265,6 +5293,13 @@ sub vmconfig_update_net {
             # for non online change, we try to hot-unplug
 	    die "skip\n" if !$hotplug;
 	    vm_deviceunplug($vmid, $conf, $opt);
+
+	    # fixme: force device_unplug on bridge change if mac is present in dhcp, to force guest os to retrieve a new ip
+	    if($have_sdn) {
+		PVE::Network::SDN::Dhcp::remove_mapping($oldnet->{bridge}, $oldnet->{macaddr});
+		PVE::Network::SDN::Vnets::del_ips_from_mac($oldnet->{bridge}, $oldnet->{macaddr}, $conf->{name});
+	    }
+
 	} else {
 
 	    die "internal error" if $opt !~ m/net(\d+)/;
@@ -5296,6 +5331,9 @@ sub vmconfig_update_net {
     }
 
     if ($hotplug) {
+	if ($have_sdn) {
+	    PVE::Network::SDN::Vnets::add_next_free_cidr($newnet->{bridge}, $conf->{name}, $newnet->{macaddr}, "vmid:$vmid", undef, 1);
+	}
 	vm_deviceplug($storecfg, $conf, $vmid, $opt, $newnet, $arch, $machine_type);
     } else {
 	die "skip\n";
-- 
2.39.2




^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [WIP v3 qemu-server 20/22] vm_start : vm-network-scripts: get ip from ipam and add dhcp reservation
  2023-11-14 18:05 [pve-devel] [WIP v3 cluster/network/manager/qemu-server 00/22] Add support for DHCP servers to SDN Stefan Hanreich
                   ` (18 preceding siblings ...)
  2023-11-14 18:06 ` [pve-devel] [WIP v3 qemu-server 19/22] vmnic add|remove : add|del ip in ipam Stefan Hanreich
@ 2023-11-14 18:06 ` Stefan Hanreich
  2023-11-14 18:06 ` [pve-devel] [WIP v3 qemu-server 21/22] api2: create|restore|clone: add_free_ip Stefan Hanreich
  2023-11-14 18:06 ` [pve-devel] [WIP v3 qemu-server 22/22] vm_destroy: delete ip from ipam && dhcp Stefan Hanreich
  21 siblings, 0 replies; 27+ messages in thread
From: Stefan Hanreich @ 2023-11-14 18:06 UTC (permalink / raw)
  To: pve-devel

From: Alexandre Derumier <aderumier@odiso.com>

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 vm-network-scripts/pve-bridge | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/vm-network-scripts/pve-bridge b/vm-network-scripts/pve-bridge
index d37ce33..24efaad 100755
--- a/vm-network-scripts/pve-bridge
+++ b/vm-network-scripts/pve-bridge
@@ -10,6 +10,8 @@ use PVE::Network;
 my $have_sdn;
 eval {
     require PVE::Network::SDN::Zones;
+    require PVE::Network::SDN::Vnets;
+    require PVE::Network::SDN::Dhcp;
     $have_sdn = 1;
 };
 
@@ -44,6 +46,9 @@ my $net = PVE::QemuServer::parse_net($netconf);
 die "unable to parse network config '$netid'\n" if !$net;
 
 if ($have_sdn) {
+    my ($ip4, $ip6) = PVE::Network::SDN::Vnets::get_ips_from_mac($net->{bridge}, $net->{macaddr});
+    PVE::Network::SDN::Dhcp::add_mapping($net->{bridge}, $net->{macaddr}, $ip4, $ip6);
+
     PVE::Network::SDN::Zones::tap_create($iface, $net->{bridge});
     PVE::Network::SDN::Zones::tap_plug($iface, $net->{bridge}, $net->{tag}, $net->{firewall}, $net->{trunks}, $net->{rate});
 } else {
-- 
2.39.2




^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [WIP v3 qemu-server 21/22] api2: create|restore|clone: add_free_ip
  2023-11-14 18:05 [pve-devel] [WIP v3 cluster/network/manager/qemu-server 00/22] Add support for DHCP servers to SDN Stefan Hanreich
                   ` (19 preceding siblings ...)
  2023-11-14 18:06 ` [pve-devel] [WIP v3 qemu-server 20/22] vm_start : vm-network-scripts: get ip from ipam and add dhcp reservation Stefan Hanreich
@ 2023-11-14 18:06 ` Stefan Hanreich
  2023-11-14 18:06 ` [pve-devel] [WIP v3 qemu-server 22/22] vm_destroy: delete ip from ipam && dhcp Stefan Hanreich
  21 siblings, 0 replies; 27+ messages in thread
From: Stefan Hanreich @ 2023-11-14 18:06 UTC (permalink / raw)
  To: pve-devel

From: Alexandre Derumier <aderumier@odiso.com>

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 PVE/API2/Qemu.pm  |  6 ++++++
 PVE/QemuServer.pm | 31 +++++++++++++++++++++++++++++++
 2 files changed, 37 insertions(+)

diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
index 38bdaab..a0f8243 100644
--- a/PVE/API2/Qemu.pm
+++ b/PVE/API2/Qemu.pm
@@ -991,6 +991,8 @@ __PACKAGE__->register_method({
 		    eval { PVE::QemuServer::template_create($vmid, $restored_conf) };
 		    warn $@ if $@;
 		}
+
+		PVE::QemuServer::create_ifaces_ipams_ips($restored_conf, $vmid) if $unique;
 	    };
 
 	    # ensure no old replication state are exists
@@ -1066,6 +1068,8 @@ __PACKAGE__->register_method({
 		}
 
 		PVE::AccessControl::add_vm_to_pool($vmid, $pool) if $pool;
+
+		PVE::QemuServer::create_ifaces_ipams_ips($conf, $vmid);
 	    };
 
 	    PVE::QemuConfig->lock_config_full($vmid, 1, $realcmd);
@@ -3763,6 +3767,8 @@ __PACKAGE__->register_method({
 
 		PVE::QemuConfig->write_config($newid, $newconf);
 
+		PVE::QemuServer::create_ifaces_ipams_ips($newconf, $vmid);
+
 		if ($target) {
 		    # always deactivate volumes - avoid lvm LVs to be active on several nodes
 		    PVE::Storage::deactivate_volumes($storecfg, $vollist, $snapname) if !$running;
diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm
index 1ae1cb0..fecdb9c 100644
--- a/PVE/QemuServer.pm
+++ b/PVE/QemuServer.pm
@@ -8626,4 +8626,35 @@ sub del_nets_bridge_fdb {
     }
 }
 
+sub create_ifaces_ipams_ips {
+    my ($conf, $vmid) = @_;
+
+    return if !$have_sdn;
+
+    foreach my $opt (keys %$conf) {
+        if ($opt =~ m/^net(\d+)$/) {
+            my $value = $conf->{$opt};
+            my $net = PVE::QemuServer::parse_net($value);
+            eval { PVE::Network::SDN::Vnets::add_next_free_cidr($net->{bridge}, $conf->{name}, $net->{macaddr}, $vmid, undef, 1) };
+            warn $@ if $@;
+        }
+    }
+}
+
+sub delete_ifaces_ipams_ips {
+    my ($conf, $vmid) = @_;
+
+    return if !$have_sdn;
+
+    foreach my $opt (keys %$conf) {
+	if ($opt =~ m/^net(\d+)$/) {
+	    my $net = PVE::QemuServer::parse_net($conf->{$opt});
+	    eval { PVE::Network::SDN::Dhcp::remove_mapping($net->{bridge}, $net->{macaddr}) };
+	    warn $@ if $@;
+	    eval { PVE::Network::SDN::Vnets::del_ips_from_mac($net->{bridge}, $net->{macaddr}, $conf->{name}) };
+	    warn $@ if $@;
+	}
+    }
+}
+
 1;
-- 
2.39.2




^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [WIP v3 qemu-server 22/22] vm_destroy: delete ip from ipam && dhcp
  2023-11-14 18:05 [pve-devel] [WIP v3 cluster/network/manager/qemu-server 00/22] Add support for DHCP servers to SDN Stefan Hanreich
                   ` (20 preceding siblings ...)
  2023-11-14 18:06 ` [pve-devel] [WIP v3 qemu-server 21/22] api2: create|restore|clone: add_free_ip Stefan Hanreich
@ 2023-11-14 18:06 ` Stefan Hanreich
  21 siblings, 0 replies; 27+ messages in thread
From: Stefan Hanreich @ 2023-11-14 18:06 UTC (permalink / raw)
  To: pve-devel

From: Alexandre Derumier <aderumier@odiso.com>

Co-Authored-By: Stefan Hanreich <s.hanreich@proxmox.com>
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 PVE/QemuServer.pm | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm
index fecdb9c..c9c061c 100644
--- a/PVE/QemuServer.pm
+++ b/PVE/QemuServer.pm
@@ -2342,6 +2342,9 @@ sub destroy_vm {
 	});
     }
 
+    eval { delete_ifaces_ipams_ips($conf, $vmid)};
+    warn $@ if $@;
+
     if (defined $replacement_conf) {
 	PVE::QemuConfig->write_config($vmid, $replacement_conf);
     } else {
@@ -6153,6 +6156,18 @@ sub cleanup_pci_devices {
     PVE::QemuServer::PCI::remove_pci_reservation($vmid);
 }
 
+sub cleanup_sdn_dhcp {
+    my ($vmid, $conf) = @_;
+
+    for my $k (keys %$conf) {
+	next if $k !~ /^net(\d+)/;
+	my $netconf = $conf->{$k};
+	my $net = PVE::QemuServer::parse_net($netconf);
+
+	PVE::Network::SDN::Dhcp::remove_mapping($net->{bridge}, $net->{macaddr});
+    }
+}
+
 sub vm_stop_cleanup {
     my ($storecfg, $vmid, $conf, $keepActive, $apply_pending_changes) = @_;
 
@@ -6186,6 +6201,8 @@ sub vm_stop_cleanup {
 
 	cleanup_pci_devices($vmid, $conf);
 
+	cleanup_sdn_dhcp($vmid, $conf);
+
 	vmconfig_apply_pending($vmid, $conf, $storecfg) if $apply_pending_changes;
     };
     warn $@ if $@; # avoid errors - just warn
-- 
2.39.2




^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [pve-devel] [WIP v3 pve-manager 18/22] sdn: dhcp: add view for DHCP mappings
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-manager 18/22] sdn: dhcp: add view for DHCP mappings Stefan Hanreich
@ 2023-11-15 12:09   ` DERUMIER, Alexandre
  2023-11-15 12:17     ` Stefan Hanreich
  0 siblings, 1 reply; 27+ messages in thread
From: DERUMIER, Alexandre @ 2023-11-15 12:09 UTC (permalink / raw)
  To: pve-devel

I think that this panel could be named "Ipam" instead  "Dhcp Mappings"

As it's don't manage dhcp mapping of dhcp server, but ips in ipam
database


-------- Message initial --------
De: Stefan Hanreich <s.hanreich@proxmox.com>
Répondre à: Proxmox VE development discussion <pve-
devel@lists.proxmox.com>
À: pve-devel@lists.proxmox.com
Objet: [pve-devel] [WIP v3 pve-manager 18/22] sdn: dhcp: add view for
DHCP mappings
Date: 14/11/2023 19:06:16

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 www/css/ext6-pve.css            |  10 +-
 www/manager6/Makefile           |   2 +
 www/manager6/dc/Config.js       |  12 +-
 www/manager6/sdn/MappingEdit.js |  65 ++++++++++
 www/manager6/tree/DhcpTree.js   | 215 ++++++++++++++++++++++++++++++++
 5 files changed, 296 insertions(+), 8 deletions(-)
 create mode 100644 www/manager6/sdn/MappingEdit.js
 create mode 100644 www/manager6/tree/DhcpTree.js

diff --git a/www/css/ext6-pve.css b/www/css/ext6-pve.css
index e18b173f5..e5e616832 100644
--- a/www/css/ext6-pve.css
+++ b/www/css/ext6-pve.css
@@ -510,23 +510,21 @@ div.right-aligned {
     content: ' ';
 }
 
-.fa-sdn:before {
+.x-fa-sdn-treelist:before {
     width: 14px;
     height: 14px;
     position: absolute;
     left: 1px;
     top: 4px;
+}
+
+.fa-sdn:before {
     background-image:url(../images/icon-sdn.svg);
     background-size: 14px 14px;
     content: ' ';
 }
 
 .fa-network-wired:before {
-    width: 14px;
-    height: 14px;
-    position: absolute;
-    left: 1px;
-    top: 4px;
     background-image:url(../images/icon-fa-network-wired.svg);
     background-size: 14px 14px;
     content: ' ';
diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index dccd2ba1c..d226c8faa 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -108,6 +108,7 @@
JSSRC= 							\
 	tree/ResourceTree.js				\
 	tree/SnapshotTree.js				\
 	tree/ResourceMapTree.js				\
+	tree/DhcpTree.js				\
 	window/Backup.js				\
 	window/BackupConfig.js				\
 	window/BulkAction.js				\
@@ -274,6 +275,7 @@
JSSRC= 							\
 	sdn/ZoneContentView.js				\
 	sdn/ZoneContentPanel.js				\
 	sdn/ZoneView.js					\
+	sdn/MappingEdit.js				\
 	sdn/OptionsPanel.js				\
 	sdn/controllers/Base.js				\
 	sdn/controllers/EvpnEdit.js			\
diff --git a/www/manager6/dc/Config.js b/www/manager6/dc/Config.js
index 7d01da5fb..0e3948ef4 100644
--- a/www/manager6/dc/Config.js
+++ b/www/manager6/dc/Config.js
@@ -195,7 +195,7 @@ Ext.define('PVE.dc.Config', {
 		    groups: ['sdn'],
 		    title: gettext('Zones'),
 		    hidden: true,
-		    iconCls: 'fa fa-th',
+		    iconCls: 'fa fa-th x-fa-sdn-treelist',
 		    itemId: 'sdnzone',
 		},
 		{
@@ -203,7 +203,7 @@ Ext.define('PVE.dc.Config', {
 		    groups: ['sdn'],
 		    title: 'VNets',
 		    hidden: true,
-		    iconCls: 'fa fa-network-wired',
+		    iconCls: 'fa fa-network-wired x-fa-sdn-treelist',
 		    itemId: 'sdnvnet',
 		},
 		{
@@ -213,6 +213,14 @@ Ext.define('PVE.dc.Config', {
 		    hidden: true,
 		    iconCls: 'fa fa-gear',
 		    itemId: 'sdnoptions',
+		},
+		{
+		    xtype: 'pveDhcpTree',
+		    groups: ['sdn'],
+		    title: 'Dhcp Mappings',
+		    hidden: true,
+		    iconCls: 'fa fa-gear',
+		    itemId: 'sdnmappings',
 		});
 	    }
 
diff --git a/www/manager6/sdn/MappingEdit.js
b/www/manager6/sdn/MappingEdit.js
new file mode 100644
index 000000000..533fc6249
--- /dev/null
+++ b/www/manager6/sdn/MappingEdit.js
@@ -0,0 +1,65 @@
+Ext.define('PVE.sdn.DhcpMappingInputPanel', {
+    extend: 'Proxmox.panel.InputPanel',
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    isCreate: false,
+
+    items: [
+	{
+	    xtype: 'pmxDisplayEditField',
+	    name: 'vmid',
+	    fieldLabel: gettext('VMID'),
+	    allowBlank: false,
+	    editable: false,
+	},
+	{
+	    xtype: 'pmxDisplayEditField',
+	    name: 'mac',
+	    fieldLabel: gettext('MAC'),
+	    allowBlank: false,
+	    cbind: {
+		editable: '{isCreate}',
+	    },
+	},
+	{
+	    xtype: 'proxmoxtextfield',
+	    name: 'ip',
+	    fieldLabel: gettext('IP'),
+	    allowBlank: false,
+	},
+    ],
+});
+
+Ext.define('PVE.sdn.MappingEdit', {
+    extend: 'Proxmox.window.Edit',
+
+    subject: gettext('DHCP Mapping'),
+    width: 350,
+
+    isCreate: false,
+    mapping: {},
+
+    submitUrl: function(url, values) {
+	return `${url}/${values.vnet}/${values.mac}`;
+    },
+
+    initComponent: function() {
+	var me = this;
+
+	me.method = me.isCreate ? 'POST' : 'PUT';
+
+	let ipanel = Ext.create('PVE.sdn.DhcpMappingInputPanel', {
+	    isCreate: me.isCreate,
+	});
+
+	Ext.apply(me, {
+	    items: [
+		ipanel,
+	    ],
+	});
+
+	me.callParent();
+
+	ipanel.setValues(me.mapping);
+    },
+});
diff --git a/www/manager6/tree/DhcpTree.js
b/www/manager6/tree/DhcpTree.js
new file mode 100644
index 000000000..c714f0ff0
--- /dev/null
+++ b/www/manager6/tree/DhcpTree.js
@@ -0,0 +1,215 @@
+Ext.define('PVE.sdn.DhcpTree', {
+    extend: 'Ext.tree.Panel',
+    xtype: 'pveDhcpTree',
+
+    layout: 'fit',
+    rootVisible: false,
+    animate: false,
+
+    store: {
+	sorters: ['ip', 'name'],
+    },
+
+    controller: {
+	xclass: 'Ext.app.ViewController',
+	reload: function() {
+	    let me = this;
+
+	    Proxmox.Utils.API2Request({
+		url: `/cluster/sdn/ipam`,
+		method: 'GET',
+		success: function(response, opts) {
+		    let root = {
+			name: '__root',
+			expanded: true,
+			children: [],
+		    };
+
+		    let zones = {};
+		    let vnets = {};
+		    let subnets = {};
+
+		    response.result.data.forEach((element) => {
+			element.leaf = true;
+
+			if (!(element.zone in zones)) {
+			    let zone = {
+				name: element.zone,
+				type: 'zone',
+				iconCls: 'fa fa-th',
+				expanded: true,
+				children: [],
+			    };
+
+			    zones[element.zone] = zone;
+			    root.children.push(zone);
+			}
+
+			if (!(element.vnet in vnets)) {
+			    let vnet = {
+				name: element.vnet,
+				type: 'vnet',
+				iconCls: 'fa fa-network-wired',
+				expanded: true,
+				children: [],
+			    };
+
+			    vnets[element.vnet] = vnet;
+			    zones[element.zone].children.push(vnet);
+			}
+
+			if (!(element.subnet in subnets)) {
+			    let subnet = {
+				name: element.subnet,
+				type: 'subnet',
+				expanded: true,
+				children: [],
+			    };
+
+			    subnets[element.subnet] = subnet;
+			    vnets[element.vnet].children.push(subnet);
+			}
+
+			element.type = 'mapping';
+			subnets[element.subnet].children.push(element)
;
+		    });
+
+		    me.getView().setRootNode(root);
+		},
+	    });
+	},
+	init: function(view) {
+	    let me = this;
+	    me.reload();
+	},
+	onDelete: function(table, rI, cI, item, e, { data }) {
+	    let me = this;
+	    let view = me.getView();
+
+	    Ext.Msg.show({
+		title: gettext('Confirm'),
+		icon: Ext.Msg.WARNING,
+		message: gettext('Are you sure you want to remove DHCP
mapping {0}'),
+		buttons: Ext.Msg.YESNO,
+		defaultFocus: 'no',
+		callback: function(btn) {
+		    if (btn !== 'yes') {
+		        return;
+		    }
+
+		    Proxmox.Utils.API2Request({
+			url:
`/cluster/sdn/ipam/${data.vnet}/${data.mac}`,
+			method: 'DELETE',
+			waitMsgTarget: view,
+			failure: function(response, opts) {
+			    Ext.Msg.alert(gettext('Error'),
response.htmlStatus);
+			},
+			callback: me.reload.bind(me),
+		    });
+		},
+	    });
+	},
+    },
+
+    columns: [
+	{
+	    xtype: 'treecolumn',
+	    text: 'Name / VMID',
+	    dataIndex: 'name',
+	    width: 200,
+	    renderer: function(value, meta, record) {
+		if (record.get('gateway')) {
+		    return 'Gateway';
+		}
+
+		return record.get('name') ?? record.get('vmid') ?? '';
+	    },
+	},
+	{
+	    text: 'IP',
+	    dataIndex: 'ip',
+	    width: 200,
+	},
+	{
+	    text: 'MAC',
+	    dataIndex: 'mac',
+	    width: 200,
+	},
+	{
+	    text: 'Gateway',
+	    dataIndex: 'gateway',
+	    width: 200,
+	},
+	{
+	    header: gettext('Actions'),
+	    xtype: 'actioncolumn',
+	    dataIndex: 'text',
+	    width: 150,
+	    items: [
+		{
+		    handler: function(table, rI, cI, item, e, { data
}) {
+			let me = this;
+
+			let win = Ext.create('PVE.sdn.MappingEdit', {
+			    autoShow: true,
+			    mapping: {},
+			    url: `/cluster/sdn/ipam`,
+			    method: 'POST',
+			    isCreate: true,
+			    extraRequestParams: {
+				vnet: data.name,
+			    },
+			});
+
+			win.on('destroy', me.reload);
+		    },
+		    getTip: (v, m, rec) => gettext('Add'),
+		    getClass: (v, m, { data }) => {
+			if (data.type === 'vnet') {
+			    return 'fa fa-plus-square';
+			}
+
+			return 'pmx-hidden';
+		    },
+                },
+		{
+		    handler: function(table, rI, cI, item, e, { data
}) {
+			let me = this;
+
+			let win = Ext.create('PVE.sdn.MappingEdit', {
+			    autoShow: true,
+			    mapping: data,
+			    url: `/cluster/sdn/ipam`,
+			    method: 'PUT',
+			    extraRequestParams: {
+				vmid: data.vmid,
+				vnet: data.vnet,
+			    },
+			});
+
+			win.on('destroy', me.reload);
+		    },
+		    getTip: (v, m, rec) => 'Edit',
+		    getClass: (v, m, { data }) => {
+			if (data.type === 'mapping' && !data.gateway)
{
+			    return 'fa fa-pencil fa-fw';
+			}
+
+			return 'pmx-hidden';
+		    },
+                },
+		{
+		    handler: 'onDelete',
+		    getTip: (v, m, rec) => 'Delete',
+		    getClass: (v, m, { data }) => {
+			if (data.type === 'mapping' && !data.gateway)
{
+			    return 'fa critical fa-trash-o';
+			}
+
+			return 'pmx-hidden';
+		    },
+                },
+	    ],
+	},
+    ],
+});


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [pve-devel] [WIP v3 pve-manager 18/22] sdn: dhcp: add view for DHCP mappings
  2023-11-15 12:09   ` DERUMIER, Alexandre
@ 2023-11-15 12:17     ` Stefan Hanreich
  0 siblings, 0 replies; 27+ messages in thread
From: Stefan Hanreich @ 2023-11-15 12:17 UTC (permalink / raw)
  To: Proxmox VE development discussion, DERUMIER, Alexandre

On 11/15/23 13:09, DERUMIER, Alexandre wrote:
> I think that this panel could be named "Ipam" instead  "Dhcp Mappings"

Yes, good point, I've actually already renamed it locally.




^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [pve-devel] [WIP v3 pve-manager 17/22] sdn: subnet: add panel for editing DHCP ranges
  2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-manager 17/22] sdn: subnet: add panel for editing DHCP ranges Stefan Hanreich
@ 2023-11-15 13:09   ` DERUMIER, Alexandre
  2023-11-15 13:24     ` Stefan Hanreich
  0 siblings, 1 reply; 27+ messages in thread
From: DERUMIER, Alexandre @ 2023-11-15 13:09 UTC (permalink / raw)
  To: pve-devel

Creating a new subnet  without dhcp range is failing with

"
Parameter verification failed. (400)
dhcp-range: type check ('array') failed
"



-------- Message initial --------
De: Stefan Hanreich <s.hanreich@proxmox.com>
Répondre à: Proxmox VE development discussion <pve-
devel@lists.proxmox.com>
À: pve-devel@lists.proxmox.com
Objet: [pve-devel] [WIP v3 pve-manager 17/22] sdn: subnet: add panel
for editing DHCP ranges
Date: 14/11/2023 19:06:15

Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
 www/manager6/sdn/SubnetEdit.js | 161 ++++++++++++++++++++++++++++++++-
 1 file changed, 160 insertions(+), 1 deletion(-)

diff --git a/www/manager6/sdn/SubnetEdit.js
b/www/manager6/sdn/SubnetEdit.js
index b9825d2a3..ab3b9d021 100644
--- a/www/manager6/sdn/SubnetEdit.js
+++ b/www/manager6/sdn/SubnetEdit.js
@@ -56,6 +56,148 @@ Ext.define('PVE.sdn.SubnetInputPanel', {
     ],
 });
 
+Ext.define('PVE.sdn.SubnetDhcpRangePanel', {
+    extend: 'Ext.form.FieldContainer',
+    mixins: ['Ext.form.field.Field'],
+
+    initComponent: function() {
+	let me = this;
+
+	me.callParent();
+	me.initField();
+    },
+
+    getValue: function() {
+	let me = this;
+	let store = me.lookup('grid').getStore();
+
+	let data = [];
+
+	store.getData().each((item) => console.log(item));
+
+	store.getData()
+	    .each((item) =>
+		data.push(`start-address=${item.data['start-
address']},end-address=${item.data['end-address']}`),
+	    );
+
+	return data;
+    },
+
+    getSubmitData: function() {
+	let me = this;
+	let data = {};
+
+	let value = me.getValue();
+	if (value) {
+	    data[me.getName()] = value;
+	}
+
+	return data;
+    },
+
+    setValue: function(dhcpRanges) {
+	let me = this;
+	let store = me.lookup('grid').getStore();
+	store.setData(dhcpRanges);
+    },
+
+    getErrors: function() {
+	let me = this;
+        let errors = [];
+
+	return errors;
+    },
+
+    controller: {
+	xclass: 'Ext.app.ViewController',
+
+	addRange: function() {
+	    let me = this;
+	    me.lookup('grid').getStore().add({});
+	},
+
+	removeRange: function(field) {
+	    let me = this;
+	    let record = field.getWidgetRecord();
+
+	    me.lookup('grid').getStore().remove(record);
+	},
+
+	onValueChange: function(field, value) {
+	    let me = this;
+	    let record = field.getWidgetRecord();
+	    let column = field.getWidgetColumn();
+
+	    record.set(column.dataIndex, value);
+	    record.commit();
+	},
+
+	control: {
+	    'grid button': {
+		click: 'removeRange',
+	    },
+	    'field': {
+		change: 'onValueChange',
+	    },
+	},
+    },
+
+    items: [
+	{
+	    xtype: 'grid',
+	    reference: 'grid',
+	    scrollable: true,
+	    store: {
+		fields: ['start-address', 'end-address'],
+	    },
+	    columns: [
+		{
+		    text: gettext('Start Address'),
+		    xtype: 'widgetcolumn',
+		    dataIndex: 'start-address',
+		    flex: 1,
+		    widget: {
+			xtype: 'textfield',
+			vtype: 'IP64Address',
+		    },
+		},
+		{
+		    text: gettext('End Address'),
+		    xtype: 'widgetcolumn',
+		    dataIndex: 'end-address',
+		    flex: 1,
+		    widget: {
+			xtype: 'textfield',
+			vtype: 'IP64Address',
+		    },
+		},
+		{
+		    xtype: 'widgetcolumn',
+		    width: 40,
+		    widget: {
+			xtype: 'button',
+			iconCls: 'fa fa-trash-o',
+		    },
+		},
+	    ],
+	},
+	{
+	    xtype: 'container',
+	    layout: {
+		type: 'hbox',
+	    },
+	    items: [
+		{
+		    xtype: 'button',
+		    text: gettext('Add'),
+		    iconCls: 'fa fa-plus-circle',
+		    handler: 'addRange',
+		},
+	    ],
+	},
+    ],
+});
+
 Ext.define('PVE.sdn.SubnetEdit', {
     extend: 'Proxmox.window.Edit',
 
@@ -67,6 +209,8 @@ Ext.define('PVE.sdn.SubnetEdit', {
 
     base_url: undefined,
 
+    bodyPadding: 0,
+
     initComponent: function() {
 	var me = this;
 
@@ -82,11 +226,22 @@ Ext.define('PVE.sdn.SubnetEdit', {
 
 	let ipanel = Ext.create('PVE.sdn.SubnetInputPanel', {
 	    isCreate: me.isCreate,
+	    title: gettext('General'),
+	});
+
+	let dhcpPanel = Ext.create('PVE.sdn.SubnetDhcpRangePanel', {
+	    isCreate: me.isCreate,
+	    title: gettext('DHCP Ranges'),
+	    name: 'dhcp-range',
 	});
 
 	Ext.apply(me, {
 	    items: [
-		ipanel,
+		{
+		    xtype: 'tabpanel',
+		    bodyPadding: 10,
+		    items: [ipanel, dhcpPanel],
+		},
 	    ],
 	});
 
@@ -97,6 +252,10 @@ Ext.define('PVE.sdn.SubnetEdit', {
 		success: function(response, options) {
 		    let values = response.result.data;
 		    ipanel.setValues(values);
+
+		    if (values['dhcp-range']) {
+			dhcpPanel.setValue(values['dhcp-range']);
+		    }
 		},
 	    });
 	}


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [pve-devel] [WIP v3 pve-manager 17/22] sdn: subnet: add panel for editing DHCP ranges
  2023-11-15 13:09   ` DERUMIER, Alexandre
@ 2023-11-15 13:24     ` Stefan Hanreich
  0 siblings, 0 replies; 27+ messages in thread
From: Stefan Hanreich @ 2023-11-15 13:24 UTC (permalink / raw)
  To: Proxmox VE development discussion, DERUMIER, Alexandre



On 11/15/23 14:09, DERUMIER, Alexandre wrote:
> Creating a new subnet  without dhcp range is failing with
> 
> "
> Parameter verification failed. (400)
> dhcp-range: type check ('array') failed

Thanks for the report! Already fixed it!

Also, creating a new Zone that is not Simple also failed, this is also
already fixed!




^ permalink raw reply	[flat|nested] 27+ messages in thread

end of thread, other threads:[~2023-11-15 13:25 UTC | newest]

Thread overview: 27+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-11-14 18:05 [pve-devel] [WIP v3 cluster/network/manager/qemu-server 00/22] Add support for DHCP servers to SDN Stefan Hanreich
2023-11-14 18:05 ` [pve-devel] [WIP v3 pve-cluster 01/22] add priv/macs.db Stefan Hanreich
2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 02/22] sdn: preparations for DHCP plugin Stefan Hanreich
2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 03/22] subnet: add dhcp options Stefan Hanreich
2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 04/22] sdn: zone: " Stefan Hanreich
2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 05/22] sdn: subnet: vnet: refactor IPAM related methods Stefan Hanreich
2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 06/22] ipam: plugins: preparations for DHCP Stefan Hanreich
2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 07/22] dhcp: add abstract class for DHCP plugins Stefan Hanreich
2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 08/22] sdn: dhcp: add dnsmasq plugin Stefan Hanreich
2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 09/22] sdn: dhcp: add helper for creating DHCP leases Stefan Hanreich
2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 10/22] api: add IPAM endpoints Stefan Hanreich
2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 11/22] api: subnet: add dhcp ranges Stefan Hanreich
2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 12/22] api: zone: add dhcp options Stefan Hanreich
2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 13/22] dhcp: regenerate config for DHCP servers on reload Stefan Hanreich
2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-network 14/22] sdn: fix tests Stefan Hanreich
2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-manager 15/22] sdn: regenerate DHCP config on reload Stefan Hanreich
2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-manager 16/22] sdn: add DHCP option to Zone dialogue Stefan Hanreich
2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-manager 17/22] sdn: subnet: add panel for editing DHCP ranges Stefan Hanreich
2023-11-15 13:09   ` DERUMIER, Alexandre
2023-11-15 13:24     ` Stefan Hanreich
2023-11-14 18:06 ` [pve-devel] [WIP v3 pve-manager 18/22] sdn: dhcp: add view for DHCP mappings Stefan Hanreich
2023-11-15 12:09   ` DERUMIER, Alexandre
2023-11-15 12:17     ` Stefan Hanreich
2023-11-14 18:06 ` [pve-devel] [WIP v3 qemu-server 19/22] vmnic add|remove : add|del ip in ipam Stefan Hanreich
2023-11-14 18:06 ` [pve-devel] [WIP v3 qemu-server 20/22] vm_start : vm-network-scripts: get ip from ipam and add dhcp reservation Stefan Hanreich
2023-11-14 18:06 ` [pve-devel] [WIP v3 qemu-server 21/22] api2: create|restore|clone: add_free_ip Stefan Hanreich
2023-11-14 18:06 ` [pve-devel] [WIP v3 qemu-server 22/22] vm_destroy: delete ip from ipam && dhcp Stefan Hanreich

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal