]> git.proxmox.com Git - pve-network.git/blame - src/PVE/Network/SDN/Subnets.pm
ipam: plugins: preparations for DHCP
[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 {
e8736dac 101 my ($ip, $mask, $subnets) = @_;
70b03506
AD
102
103 my $subnet = undef;
104 my $subnetid = undef;
105
e612faf6 106 foreach my $id (sort keys %{$subnets}) {
e8736dac
AD
107
108 next if $mask ne $subnets->{$id}->{mask};
109 my $cidr = $subnets->{$id}->{cidr};
e612faf6
AD
110 my $subnet_matcher = subnet_matcher($cidr);
111 next if !$subnet_matcher->($ip);
112 $subnet = $subnets->{$id};
113 $subnetid = $id;
114 last;
70b03506
AD
115 }
116 die "can't find any subnet for ip $ip" if !$subnet;
117
118 return ($subnetid, $subnet);
119}
120
b61e93a5 121sub verify_dns_zone {
ee4f339e
AD
122 my ($zone, $dns) = @_;
123
124 return if !$zone || !$dns;
125
126 my $dns_cfg = PVE::Network::SDN::Dns::config();
127 my $plugin_config = $dns_cfg->{ids}->{$dns};
128 my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type});
129 $plugin->verify_zone($plugin_config, $zone);
b61e93a5 130}
ee4f339e 131
b61e93a5 132sub get_reversedns_zone {
e8736dac 133 my ($subnetid, $subnet, $dns, $ip) = @_;
4ad78442
AD
134
135 return if !$subnetid || !$dns || !$ip;
136
137 my $dns_cfg = PVE::Network::SDN::Dns::config();
138 my $plugin_config = $dns_cfg->{ids}->{$dns};
139 my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type});
e8736dac 140 $plugin->get_reversedns_zone($plugin_config, $subnetid, $subnet, $ip);
b61e93a5 141}
4ad78442 142
b61e93a5 143sub add_dns_record {
ceb972a9 144 my ($zone, $dns, $hostname, $ip) = @_;
ee4f339e
AD
145 return if !$zone || !$dns || !$hostname || !$ip;
146
ee4f339e
AD
147 my $dns_cfg = PVE::Network::SDN::Dns::config();
148 my $plugin_config = $dns_cfg->{ids}->{$dns};
149 my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type});
150 $plugin->add_a_record($plugin_config, $zone, $hostname, $ip);
151
b61e93a5 152}
ee4f339e 153
b61e93a5 154sub add_dns_ptr_record {
ceb972a9 155 my ($reversezone, $zone, $dns, $hostname, $ip) = @_;
ee4f339e
AD
156
157 return if !$zone || !$reversezone || !$dns || !$hostname || !$ip;
158
ee4f339e
AD
159 $hostname .= ".$zone";
160 my $dns_cfg = PVE::Network::SDN::Dns::config();
161 my $plugin_config = $dns_cfg->{ids}->{$dns};
162 my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type});
163 $plugin->add_ptr_record($plugin_config, $reversezone, $hostname, $ip);
b61e93a5 164}
ee4f339e 165
b61e93a5 166sub del_dns_record {
ceb972a9 167 my ($zone, $dns, $hostname, $ip) = @_;
ee4f339e
AD
168
169 return if !$zone || !$dns || !$hostname || !$ip;
170
ee4f339e
AD
171 my $dns_cfg = PVE::Network::SDN::Dns::config();
172 my $plugin_config = $dns_cfg->{ids}->{$dns};
173 my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type});
174 $plugin->del_a_record($plugin_config, $zone, $hostname, $ip);
b61e93a5 175}
ee4f339e 176
b61e93a5 177sub del_dns_ptr_record {
ee4f339e
AD
178 my ($reversezone, $dns, $ip) = @_;
179
180 return if !$reversezone || !$dns || !$ip;
181
182 my $dns_cfg = PVE::Network::SDN::Dns::config();
183 my $plugin_config = $dns_cfg->{ids}->{$dns};
184 my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type});
185 $plugin->del_ptr_record($plugin_config, $reversezone, $ip);
b61e93a5 186}
ee4f339e 187
77ec7eb2
AD
188sub add_subnet {
189 my ($zone, $subnetid, $subnet) = @_;
190
191 my $ipam = $zone->{ipam};
d6557a2d 192 return if !$ipam;
77ec7eb2
AD
193 my $ipam_cfg = PVE::Network::SDN::Ipams::config();
194 my $plugin_config = $ipam_cfg->{ids}->{$ipam};
195 my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
196 $plugin->add_subnet($plugin_config, $subnetid, $subnet);
197}
198
199sub del_subnet {
200 my ($zone, $subnetid, $subnet) = @_;
201
202 my $ipam = $zone->{ipam};
d6557a2d 203 return if !$ipam;
77ec7eb2
AD
204 my $ipam_cfg = PVE::Network::SDN::Ipams::config();
205 my $plugin_config = $ipam_cfg->{ids}->{$ipam};
206 my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
207 $plugin->del_subnet($plugin_config, $subnetid, $subnet);
208}
209
70b03506 210sub next_free_ip {
83dcfd57 211 my ($zone, $subnetid, $subnet, $hostname, $mac, $description, $skipdns) = @_;
ee4f339e
AD
212
213 my $cidr = undef;
214 my $ip = undef;
ceb972a9 215 $description = '' if !$description;
70b03506 216
331e2330 217 my $ipamid = $zone->{ipam};
4ad78442
AD
218 my $dns = $zone->{dns};
219 my $dnszone = $zone->{dnszone};
220 my $reversedns = $zone->{reversedns};
ee4f339e
AD
221 my $dnszoneprefix = $subnet->{dnszoneprefix};
222
ceb972a9
AD
223 $hostname .= ".$dnszoneprefix" if $dnszoneprefix;
224
ee4f339e 225 #verify dns zones before ipam
83dcfd57 226 verify_dns_zone($dnszone, $dns) if !$skipdns;
ee4f339e
AD
227
228 if($ipamid) {
229 my $ipam_cfg = PVE::Network::SDN::Ipams::config();
230 my $plugin_config = $ipam_cfg->{ids}->{$ipamid};
231 my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
e612faf6 232 eval {
e9365ab0 233 $cidr = $plugin->add_next_freeip($plugin_config, $subnetid, $subnet, $hostname, $mac, $description);
e612faf6
AD
234 ($ip, undef) = split(/\//, $cidr);
235 };
236 die $@ if $@;
ee4f339e 237 }
70b03506 238
ee4f339e 239 eval {
b61e93a5 240 my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip);
4ad78442 241
83dcfd57
AD
242 if(!$skipdns) {
243 #add dns
244 add_dns_record($dnszone, $dns, $hostname, $ip);
245 #add reverse dns
246 add_dns_ptr_record($reversednszone, $dnszone, $reversedns, $hostname, $ip);
247 }
ee4f339e
AD
248 };
249 if ($@) {
250 #rollback
251 my $err = $@;
252 eval {
0720c17e 253 PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname)
ee4f339e
AD
254 };
255 die $err;
256 }
257 return $cidr;
70b03506
AD
258}
259
260sub add_ip {
83dcfd57 261 my ($zone, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, $skipdns) = @_;
70b03506 262
b184ebc3 263 return if !$subnet || !$ip;
e612faf6 264
533eb3d4 265 my $ipaddr = NetAddr::IP->new($ip);
aba0731c
AD
266 $ip = $ipaddr->canon();
267
331e2330 268 my $ipamid = $zone->{ipam};
4ad78442
AD
269 my $dns = $zone->{dns};
270 my $dnszone = $zone->{dnszone};
271 my $reversedns = $zone->{reversedns};
b61e93a5 272 my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip);
ee4f339e
AD
273 my $dnszoneprefix = $subnet->{dnszoneprefix};
274
ceb972a9
AD
275 $hostname .= ".$dnszoneprefix" if $dnszoneprefix;
276
ee4f339e 277 #verify dns zones before ipam
83dcfd57
AD
278 if(!$skipdns) {
279 verify_dns_zone($dnszone, $dns);
280 verify_dns_zone($reversednszone, $reversedns);
281 }
ee4f339e
AD
282
283 if ($ipamid) {
5221635a 284
ee4f339e
AD
285 my $ipam_cfg = PVE::Network::SDN::Ipams::config();
286 my $plugin_config = $ipam_cfg->{ids}->{$ipamid};
287 my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
5221635a 288
e612faf6 289 eval {
34c4c6d7 290 $plugin->add_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway);
e612faf6
AD
291 };
292 die $@ if $@;
ee4f339e 293 }
70b03506 294
ee4f339e 295 eval {
83dcfd57
AD
296 if(!$skipdns) {
297 #add dns
298 add_dns_record($dnszone, $dns, $hostname, $ip);
299 #add reverse dns
300 add_dns_ptr_record($reversednszone, $dnszone, $reversedns, $hostname, $ip);
301 }
ee4f339e
AD
302 };
303 if ($@) {
304 #rollback
305 my $err = $@;
306 eval {
0720c17e 307 PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname)
ee4f339e
AD
308 };
309 die $err;
310 }
70b03506
AD
311}
312
dd54b5a3 313sub update_ip {
83dcfd57 314 my ($zone, $subnetid, $subnet, $ip, $hostname, $oldhostname, $mac, $description, $skipdns) = @_;
dd54b5a3
AD
315
316 return if !$subnet || !$ip;
317
533eb3d4 318 my $ipaddr = NetAddr::IP->new($ip);
dd54b5a3
AD
319 $ip = $ipaddr->canon();
320
321 my $ipamid = $zone->{ipam};
322 my $dns = $zone->{dns};
323 my $dnszone = $zone->{dnszone};
324 my $reversedns = $zone->{reversedns};
b61e93a5 325 my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip);
dd54b5a3
AD
326 my $dnszoneprefix = $subnet->{dnszoneprefix};
327
328 $hostname .= ".$dnszoneprefix" if $dnszoneprefix;
329
330 #verify dns zones before ipam
83dcfd57
AD
331 if(!$skipdns) {
332 verify_dns_zone($dnszone, $dns);
333 verify_dns_zone($reversednszone, $reversedns);
334 }
dd54b5a3
AD
335
336 if ($ipamid) {
337 my $ipam_cfg = PVE::Network::SDN::Ipams::config();
338 my $plugin_config = $ipam_cfg->{ids}->{$ipamid};
339 my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
340 eval {
341 $plugin->update_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description);
342 };
343 die $@ if $@;
344 }
345
0d2396b0
AD
346 return if $hostname eq $oldhostname;
347
dd54b5a3 348 eval {
83dcfd57
AD
349 if(!$skipdns) {
350 #add dns
351 del_dns_record($dnszone, $dns, $oldhostname, $ip);
352 add_dns_record($dnszone, $dns, $hostname, $ip);
353 #add reverse dns
354 del_dns_ptr_record($reversednszone, $reversedns, $ip);
355 add_dns_ptr_record($reversednszone, $dnszone, $reversedns, $hostname, $ip);
356 }
dd54b5a3
AD
357 };
358}
359
70b03506 360sub del_ip {
83dcfd57 361 my ($zone, $subnetid, $subnet, $ip, $hostname, $skipdns) = @_;
70b03506 362
aba0731c
AD
363 return if !$subnet || !$ip;
364
533eb3d4 365 my $ipaddr = NetAddr::IP->new($ip);
aba0731c 366 $ip = $ipaddr->canon();
e612faf6 367
331e2330 368 my $ipamid = $zone->{ipam};
4ad78442
AD
369 my $dns = $zone->{dns};
370 my $dnszone = $zone->{dnszone};
371 my $reversedns = $zone->{reversedns};
b61e93a5 372 my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip);
ee4f339e 373 my $dnszoneprefix = $subnet->{dnszoneprefix};
ceb972a9
AD
374 $hostname .= ".$dnszoneprefix" if $dnszoneprefix;
375
83dcfd57
AD
376 if(!$skipdns) {
377 verify_dns_zone($dnszone, $dns);
378 verify_dns_zone($reversednszone, $reversedns);
379 }
ee4f339e
AD
380
381 if ($ipamid) {
382 my $ipam_cfg = PVE::Network::SDN::Ipams::config();
383 my $plugin_config = $ipam_cfg->{ids}->{$ipamid};
384 my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
e8736dac 385 $plugin->del_ip($plugin_config, $subnetid, $subnet, $ip);
ee4f339e 386 }
70b03506 387
ee4f339e 388 eval {
83dcfd57
AD
389 if(!$skipdns) {
390 del_dns_record($dnszone, $dns, $hostname, $ip);
391 del_dns_ptr_record($reversednszone, $reversedns, $ip);
392 }
ee4f339e
AD
393 };
394 if ($@) {
395 warn $@;
396 }
70b03506
AD
397}
398
c33dd818 3991;