]> git.proxmox.com Git - pve-network.git/blame - src/PVE/Network/SDN/Subnets.pm
ipam: improve update / delete behavior
[pve-network.git] / src / PVE / Network / SDN / Subnets.pm
CommitLineData
c33dd818
AD
1package PVE::Network::SDN::Subnets;
2
3use strict;
4use warnings;
5
70b03506 6use Net::Subnet qw(subnet_matcher);
ee4f339e 7use Net::IP;
aba0731c 8use NetAddr::IP qw(:lower);
c33dd818 9
d1ab9bdb 10use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
290fa5c9 11use PVE::JSONSchema qw(parse_property_string);
ee4f339e 12use PVE::Network::SDN::Dns;
d1ab9bdb
TL
13use PVE::Network::SDN::Ipams;
14
c33dd818
AD
15use PVE::Network::SDN::SubnetPlugin;
16PVE::Network::SDN::SubnetPlugin->register();
17PVE::Network::SDN::SubnetPlugin->init();
18
19sub sdn_subnets_config {
20 my ($cfg, $id, $noerr) = @_;
21
22 die "no sdn subnet ID specified\n" if !$id;
23
24 my $scfg = $cfg->{ids}->{$id};
25 die "sdn subnet '$id' does not exist\n" if (!$noerr && !$scfg);
26
a1845dad
SH
27 if ($scfg) {
28 $scfg->{id} = $id;
29
e8736dac
AD
30 my ($zone, $network, $mask) = split(/-/, $id);
31 $scfg->{cidr} = "$network/$mask";
32 $scfg->{zone} = $zone;
33 $scfg->{network} = $network;
34 $scfg->{mask} = $mask;
35 }
36
c33dd818
AD
37 return $scfg;
38}
39
290fa5c9
SH
40sub get_dhcp_ranges {
41 my ($subnet_config) = @_;
42
43 my @dhcp_ranges = ();
44
45 if ($subnet_config->{'dhcp-range'}) {
46 foreach my $element (@{$subnet_config->{'dhcp-range'}}) {
47 my $dhcp_range = eval { parse_property_string('pve-sdn-dhcp-range', $element) };
48
49 if ($@ || !$dhcp_range) {
50 warn "Unable to parse dhcp-range string: $element\n";
51 warn "$@\n" if $@;
52 next;
53 }
54
55 push @dhcp_ranges, $dhcp_range;
56 }
57 }
58
59 return \@dhcp_ranges;
60}
61
c33dd818 62sub config {
a1845dad
SH
63 my ($running) = @_;
64
65 if ($running) {
66 my $cfg = PVE::Network::SDN::running_config();
67 return $cfg->{subnets};
68 }
69
70 return cfs_read_file("sdn/subnets.cfg");
c33dd818
AD
71}
72
73sub write_config {
74 my ($cfg) = @_;
75
76 cfs_write_file("sdn/subnets.cfg", $cfg);
77}
78
79sub sdn_subnets_ids {
80 my ($cfg) = @_;
81
b184ebc3 82 return sort keys %{$cfg->{ids}};
c33dd818
AD
83}
84
85sub complete_sdn_subnet {
86 my ($cmdname, $pname, $cvalue) = @_;
87
88 my $cfg = PVE::Network::SDN::Subnets::config();
89
90 return $cmdname eq 'add' ? [] : [ PVE::Network::SDN::Subnets::sdn_subnets_ids($cfg) ];
91}
92
93sub get_subnet {
5d3e0248
AD
94 my ($subnetid, $running) = @_;
95
a1845dad
SH
96 my $cfg = PVE::Network::SDN::Subnets::config($running);
97 return PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $subnetid, 1);
c33dd818
AD
98}
99
70b03506 100sub find_ip_subnet {
7ba17817 101 my ($ip, $subnets) = @_;
70b03506
AD
102
103 my $subnet = undef;
104 my $subnetid = undef;
105
e612faf6 106 foreach my $id (sort keys %{$subnets}) {
e8736dac 107 my $cidr = $subnets->{$id}->{cidr};
e612faf6
AD
108 my $subnet_matcher = subnet_matcher($cidr);
109 next if !$subnet_matcher->($ip);
110 $subnet = $subnets->{$id};
111 $subnetid = $id;
112 last;
70b03506
AD
113 }
114 die "can't find any subnet for ip $ip" if !$subnet;
115
116 return ($subnetid, $subnet);
117}
118
b61e93a5 119sub verify_dns_zone {
ee4f339e
AD
120 my ($zone, $dns) = @_;
121
122 return if !$zone || !$dns;
123
124 my $dns_cfg = PVE::Network::SDN::Dns::config();
125 my $plugin_config = $dns_cfg->{ids}->{$dns};
126 my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type});
127 $plugin->verify_zone($plugin_config, $zone);
b61e93a5 128}
ee4f339e 129
b61e93a5 130sub get_reversedns_zone {
e8736dac 131 my ($subnetid, $subnet, $dns, $ip) = @_;
4ad78442
AD
132
133 return if !$subnetid || !$dns || !$ip;
134
135 my $dns_cfg = PVE::Network::SDN::Dns::config();
136 my $plugin_config = $dns_cfg->{ids}->{$dns};
137 my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type});
e8736dac 138 $plugin->get_reversedns_zone($plugin_config, $subnetid, $subnet, $ip);
b61e93a5 139}
4ad78442 140
b61e93a5 141sub add_dns_record {
ceb972a9 142 my ($zone, $dns, $hostname, $ip) = @_;
ee4f339e
AD
143 return if !$zone || !$dns || !$hostname || !$ip;
144
ee4f339e
AD
145 my $dns_cfg = PVE::Network::SDN::Dns::config();
146 my $plugin_config = $dns_cfg->{ids}->{$dns};
147 my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type});
148 $plugin->add_a_record($plugin_config, $zone, $hostname, $ip);
149
b61e93a5 150}
ee4f339e 151
b61e93a5 152sub add_dns_ptr_record {
ceb972a9 153 my ($reversezone, $zone, $dns, $hostname, $ip) = @_;
ee4f339e
AD
154
155 return if !$zone || !$reversezone || !$dns || !$hostname || !$ip;
156
ee4f339e
AD
157 $hostname .= ".$zone";
158 my $dns_cfg = PVE::Network::SDN::Dns::config();
159 my $plugin_config = $dns_cfg->{ids}->{$dns};
160 my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type});
161 $plugin->add_ptr_record($plugin_config, $reversezone, $hostname, $ip);
b61e93a5 162}
ee4f339e 163
b61e93a5 164sub del_dns_record {
ceb972a9 165 my ($zone, $dns, $hostname, $ip) = @_;
ee4f339e
AD
166
167 return if !$zone || !$dns || !$hostname || !$ip;
168
ee4f339e
AD
169 my $dns_cfg = PVE::Network::SDN::Dns::config();
170 my $plugin_config = $dns_cfg->{ids}->{$dns};
171 my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type});
172 $plugin->del_a_record($plugin_config, $zone, $hostname, $ip);
b61e93a5 173}
ee4f339e 174
b61e93a5 175sub del_dns_ptr_record {
ee4f339e
AD
176 my ($reversezone, $dns, $ip) = @_;
177
178 return if !$reversezone || !$dns || !$ip;
179
180 my $dns_cfg = PVE::Network::SDN::Dns::config();
181 my $plugin_config = $dns_cfg->{ids}->{$dns};
182 my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type});
183 $plugin->del_ptr_record($plugin_config, $reversezone, $ip);
b61e93a5 184}
ee4f339e 185
77ec7eb2
AD
186sub add_subnet {
187 my ($zone, $subnetid, $subnet) = @_;
188
189 my $ipam = $zone->{ipam};
d6557a2d 190 return if !$ipam;
77ec7eb2
AD
191 my $ipam_cfg = PVE::Network::SDN::Ipams::config();
192 my $plugin_config = $ipam_cfg->{ids}->{$ipam};
193 my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
194 $plugin->add_subnet($plugin_config, $subnetid, $subnet);
195}
196
197sub del_subnet {
198 my ($zone, $subnetid, $subnet) = @_;
199
200 my $ipam = $zone->{ipam};
d6557a2d 201 return if !$ipam;
77ec7eb2
AD
202 my $ipam_cfg = PVE::Network::SDN::Ipams::config();
203 my $plugin_config = $ipam_cfg->{ids}->{$ipam};
204 my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
205 $plugin->del_subnet($plugin_config, $subnetid, $subnet);
206}
207
7ba17817
SH
208sub add_next_free_ip {
209 my ($zone, $subnetid, $subnet, $hostname, $mac, $vmid, $skipdns, $dhcprange) = @_;
ee4f339e
AD
210
211 my $cidr = undef;
212 my $ip = undef;
70b03506 213
331e2330 214 my $ipamid = $zone->{ipam};
4ad78442
AD
215 my $dns = $zone->{dns};
216 my $dnszone = $zone->{dnszone};
217 my $reversedns = $zone->{reversedns};
ee4f339e
AD
218 my $dnszoneprefix = $subnet->{dnszoneprefix};
219
ceb972a9
AD
220 $hostname .= ".$dnszoneprefix" if $dnszoneprefix;
221
ee4f339e 222 #verify dns zones before ipam
83dcfd57 223 verify_dns_zone($dnszone, $dns) if !$skipdns;
ee4f339e
AD
224
225 if($ipamid) {
226 my $ipam_cfg = PVE::Network::SDN::Ipams::config();
227 my $plugin_config = $ipam_cfg->{ids}->{$ipamid};
228 my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
e612faf6 229 eval {
7ba17817
SH
230 if ($dhcprange) {
231 my $data = {
232 mac => $mac,
233 hostname => $hostname,
234 vmid => $vmid,
235 };
236
237 my $dhcp_ranges = PVE::Network::SDN::Subnets::get_dhcp_ranges($subnet);
238
239 foreach my $range (@$dhcp_ranges) {
240 $ip = $plugin->add_range_next_freeip($plugin_config, $subnet, $range, $data);
04e1c8ed 241 last if $ip;
7ba17817
SH
242 }
243 } else {
244 $ip = $plugin->add_next_freeip($plugin_config, $subnetid, $subnet, $hostname, $mac, $vmid);
245 }
e612faf6 246 };
7ba17817 247
e612faf6 248 die $@ if $@;
7ba17817
SH
249
250 eval { PVE::Network::SDN::Ipams::add_cache_mac_ip($mac, $ip); };
251 warn $@ if $@;
ee4f339e 252 }
70b03506 253
ee4f339e 254 eval {
b61e93a5 255 my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip);
4ad78442 256
83dcfd57
AD
257 if(!$skipdns) {
258 #add dns
259 add_dns_record($dnszone, $dns, $hostname, $ip);
260 #add reverse dns
261 add_dns_ptr_record($reversednszone, $dnszone, $reversedns, $hostname, $ip);
262 }
ee4f339e
AD
263 };
264 if ($@) {
265 #rollback
266 my $err = $@;
267 eval {
7ba17817 268 PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac)
ee4f339e
AD
269 };
270 die $err;
271 }
7ba17817 272 return $ip;
70b03506
AD
273}
274
275sub add_ip {
7ba17817 276 my ($zone, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, $skipdns) = @_;
70b03506 277
b184ebc3 278 return if !$subnet || !$ip;
e612faf6 279
533eb3d4 280 my $ipaddr = NetAddr::IP->new($ip);
aba0731c
AD
281 $ip = $ipaddr->canon();
282
331e2330 283 my $ipamid = $zone->{ipam};
4ad78442
AD
284 my $dns = $zone->{dns};
285 my $dnszone = $zone->{dnszone};
286 my $reversedns = $zone->{reversedns};
b61e93a5 287 my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip);
ee4f339e
AD
288 my $dnszoneprefix = $subnet->{dnszoneprefix};
289
ceb972a9
AD
290 $hostname .= ".$dnszoneprefix" if $dnszoneprefix;
291
ee4f339e 292 #verify dns zones before ipam
83dcfd57
AD
293 if(!$skipdns) {
294 verify_dns_zone($dnszone, $dns);
295 verify_dns_zone($reversednszone, $reversedns);
296 }
ee4f339e
AD
297
298 if ($ipamid) {
5221635a 299
ee4f339e
AD
300 my $ipam_cfg = PVE::Network::SDN::Ipams::config();
301 my $plugin_config = $ipam_cfg->{ids}->{$ipamid};
302 my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
5221635a 303
e612faf6 304 eval {
7ba17817 305 $plugin->add_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway);
e612faf6
AD
306 };
307 die $@ if $@;
fb045d8c
SH
308
309 eval { PVE::Network::SDN::Ipams::add_cache_mac_ip($mac, $ip) if $mac; };
310 warn $@ if $@;
ee4f339e 311 }
70b03506 312
ee4f339e 313 eval {
83dcfd57
AD
314 if(!$skipdns) {
315 #add dns
316 add_dns_record($dnszone, $dns, $hostname, $ip);
317 #add reverse dns
318 add_dns_ptr_record($reversednszone, $dnszone, $reversedns, $hostname, $ip);
319 }
ee4f339e
AD
320 };
321 if ($@) {
322 #rollback
323 my $err = $@;
324 eval {
7ba17817 325 PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac)
ee4f339e
AD
326 };
327 die $err;
328 }
70b03506
AD
329}
330
dd54b5a3 331sub update_ip {
7ba17817 332 my ($zone, $subnetid, $subnet, $ip, $hostname, $oldhostname, $mac, $vmid, $skipdns) = @_;
dd54b5a3
AD
333
334 return if !$subnet || !$ip;
335
533eb3d4 336 my $ipaddr = NetAddr::IP->new($ip);
dd54b5a3
AD
337 $ip = $ipaddr->canon();
338
339 my $ipamid = $zone->{ipam};
340 my $dns = $zone->{dns};
341 my $dnszone = $zone->{dnszone};
342 my $reversedns = $zone->{reversedns};
b61e93a5 343 my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip);
dd54b5a3
AD
344 my $dnszoneprefix = $subnet->{dnszoneprefix};
345
346 $hostname .= ".$dnszoneprefix" if $dnszoneprefix;
347
348 #verify dns zones before ipam
83dcfd57
AD
349 if(!$skipdns) {
350 verify_dns_zone($dnszone, $dns);
351 verify_dns_zone($reversednszone, $reversedns);
352 }
dd54b5a3
AD
353
354 if ($ipamid) {
355 my $ipam_cfg = PVE::Network::SDN::Ipams::config();
356 my $plugin_config = $ipam_cfg->{ids}->{$ipamid};
357 my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
358 eval {
7ba17817 359 $plugin->update_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid);
dd54b5a3
AD
360 };
361 die $@ if $@;
362 }
363
0d2396b0
AD
364 return if $hostname eq $oldhostname;
365
dd54b5a3 366 eval {
83dcfd57
AD
367 if(!$skipdns) {
368 #add dns
369 del_dns_record($dnszone, $dns, $oldhostname, $ip);
370 add_dns_record($dnszone, $dns, $hostname, $ip);
371 #add reverse dns
372 del_dns_ptr_record($reversednszone, $reversedns, $ip);
373 add_dns_ptr_record($reversednszone, $dnszone, $reversedns, $hostname, $ip);
374 }
dd54b5a3
AD
375 };
376}
377
70b03506 378sub del_ip {
7ba17817 379 my ($zone, $subnetid, $subnet, $ip, $hostname, $mac, $skipdns) = @_;
70b03506 380
aba0731c
AD
381 return if !$subnet || !$ip;
382
533eb3d4 383 my $ipaddr = NetAddr::IP->new($ip);
aba0731c 384 $ip = $ipaddr->canon();
e612faf6 385
331e2330 386 my $ipamid = $zone->{ipam};
4ad78442
AD
387 my $dns = $zone->{dns};
388 my $dnszone = $zone->{dnszone};
389 my $reversedns = $zone->{reversedns};
b61e93a5 390 my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip);
ee4f339e 391 my $dnszoneprefix = $subnet->{dnszoneprefix};
ceb972a9
AD
392 $hostname .= ".$dnszoneprefix" if $dnszoneprefix;
393
83dcfd57
AD
394 if(!$skipdns) {
395 verify_dns_zone($dnszone, $dns);
396 verify_dns_zone($reversednszone, $reversedns);
397 }
ee4f339e
AD
398
399 if ($ipamid) {
400 my $ipam_cfg = PVE::Network::SDN::Ipams::config();
401 my $plugin_config = $ipam_cfg->{ids}->{$ipamid};
402 my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
e8736dac 403 $plugin->del_ip($plugin_config, $subnetid, $subnet, $ip);
7ba17817 404
5469161c
TL
405 if ($mac) {
406 eval { PVE::Network::SDN::Ipams::del_cache_mac_ip($mac, $ip) };
407 warn $@ if $@;
408 }
ee4f339e 409 }
70b03506 410
ee4f339e 411 eval {
83dcfd57
AD
412 if(!$skipdns) {
413 del_dns_record($dnszone, $dns, $hostname, $ip);
414 del_dns_ptr_record($reversednszone, $reversedns, $ip);
415 }
ee4f339e
AD
416 };
417 if ($@) {
418 warn $@;
419 }
70b03506
AD
420}
421
c33dd818 4221;