]>
Commit | Line | Data |
---|---|---|
c33dd818 AD |
1 | package PVE::Network::SDN::Subnets; |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
70b03506 | 6 | use Net::Subnet qw(subnet_matcher); |
ee4f339e | 7 | use Net::IP; |
aba0731c | 8 | use NetAddr::IP qw(:lower); |
c33dd818 | 9 | |
d1ab9bdb | 10 | use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file); |
290fa5c9 | 11 | use PVE::JSONSchema qw(parse_property_string); |
ee4f339e | 12 | use PVE::Network::SDN::Dns; |
d1ab9bdb TL |
13 | use PVE::Network::SDN::Ipams; |
14 | ||
c33dd818 AD |
15 | use PVE::Network::SDN::SubnetPlugin; |
16 | PVE::Network::SDN::SubnetPlugin->register(); | |
17 | PVE::Network::SDN::SubnetPlugin->init(); | |
18 | ||
19 | sub 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 |
40 | sub 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 | 62 | sub 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 | ||
73 | sub write_config { | |
74 | my ($cfg) = @_; | |
75 | ||
76 | cfs_write_file("sdn/subnets.cfg", $cfg); | |
77 | } | |
78 | ||
79 | sub sdn_subnets_ids { | |
80 | my ($cfg) = @_; | |
81 | ||
b184ebc3 | 82 | return sort keys %{$cfg->{ids}}; |
c33dd818 AD |
83 | } |
84 | ||
85 | sub 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 | ||
93 | sub 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 | 100 | sub 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 | 119 | sub 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 | 130 | sub 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 | 141 | sub 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 | 152 | sub 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 | 164 | sub 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 | 175 | sub 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 |
186 | sub 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 | ||
197 | sub 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 |
208 | sub 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 | ||
275 | sub 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 | 331 | sub 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 | 378 | sub 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 | 422 | 1; |