]>
Commit | Line | Data |
---|---|---|
1 | package PVE::Network::SDN::Subnets; | |
2 | ||
3 | use strict; | |
4 | use warnings; | |
5 | ||
6 | use Net::Subnet qw(subnet_matcher); | |
7 | use Net::IP; | |
8 | use NetAddr::IP qw(:lower); | |
9 | ||
10 | use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file); | |
11 | use PVE::JSONSchema qw(parse_property_string); | |
12 | use PVE::Network::SDN::Dns; | |
13 | use PVE::Network::SDN::Ipams; | |
14 | ||
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 | ||
27 | if ($scfg) { | |
28 | $scfg->{id} = $id; | |
29 | ||
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 | ||
37 | return $scfg; | |
38 | } | |
39 | ||
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 | ||
62 | sub config { | |
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"); | |
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 | ||
82 | return sort keys %{$cfg->{ids}}; | |
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 { | |
94 | my ($subnetid, $running) = @_; | |
95 | ||
96 | my $cfg = PVE::Network::SDN::Subnets::config($running); | |
97 | return PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $subnetid, 1); | |
98 | } | |
99 | ||
100 | sub find_ip_subnet { | |
101 | my ($ip, $subnets) = @_; | |
102 | ||
103 | my $subnet = undef; | |
104 | my $subnetid = undef; | |
105 | ||
106 | foreach my $id (sort keys %{$subnets}) { | |
107 | my $cidr = $subnets->{$id}->{cidr}; | |
108 | my $subnet_matcher = subnet_matcher($cidr); | |
109 | next if !$subnet_matcher->($ip); | |
110 | $subnet = $subnets->{$id}; | |
111 | $subnetid = $id; | |
112 | last; | |
113 | } | |
114 | die "can't find any subnet for ip $ip" if !$subnet; | |
115 | ||
116 | return ($subnetid, $subnet); | |
117 | } | |
118 | ||
119 | sub verify_dns_zone { | |
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); | |
128 | } | |
129 | ||
130 | sub get_reversedns_zone { | |
131 | my ($subnetid, $subnet, $dns, $ip) = @_; | |
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}); | |
138 | $plugin->get_reversedns_zone($plugin_config, $subnetid, $subnet, $ip); | |
139 | } | |
140 | ||
141 | sub add_dns_record { | |
142 | my ($zone, $dns, $hostname, $ip) = @_; | |
143 | return if !$zone || !$dns || !$hostname || !$ip; | |
144 | ||
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 | ||
150 | } | |
151 | ||
152 | sub add_dns_ptr_record { | |
153 | my ($reversezone, $zone, $dns, $hostname, $ip) = @_; | |
154 | ||
155 | return if !$zone || !$reversezone || !$dns || !$hostname || !$ip; | |
156 | ||
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); | |
162 | } | |
163 | ||
164 | sub del_dns_record { | |
165 | my ($zone, $dns, $hostname, $ip) = @_; | |
166 | ||
167 | return if !$zone || !$dns || !$hostname || !$ip; | |
168 | ||
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); | |
173 | } | |
174 | ||
175 | sub del_dns_ptr_record { | |
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); | |
184 | } | |
185 | ||
186 | sub add_subnet { | |
187 | my ($zone, $subnetid, $subnet) = @_; | |
188 | ||
189 | my $ipam = $zone->{ipam}; | |
190 | return if !$ipam; | |
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}; | |
201 | return if !$ipam; | |
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 | ||
208 | sub add_next_free_ip { | |
209 | my ($zone, $subnetid, $subnet, $hostname, $mac, $vmid, $skipdns, $dhcprange) = @_; | |
210 | ||
211 | my $cidr = undef; | |
212 | my $ip = undef; | |
213 | ||
214 | my $ipamid = $zone->{ipam}; | |
215 | my $dns = $zone->{dns}; | |
216 | my $dnszone = $zone->{dnszone}; | |
217 | my $reversedns = $zone->{reversedns}; | |
218 | my $dnszoneprefix = $subnet->{dnszoneprefix}; | |
219 | ||
220 | $hostname .= ".$dnszoneprefix" if $dnszoneprefix; | |
221 | ||
222 | #verify dns zones before ipam | |
223 | verify_dns_zone($dnszone, $dns) if !$skipdns; | |
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}); | |
229 | eval { | |
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); | |
241 | last if $ip; | |
242 | } | |
243 | } else { | |
244 | $ip = $plugin->add_next_freeip($plugin_config, $subnetid, $subnet, $hostname, $mac, $vmid); | |
245 | } | |
246 | }; | |
247 | ||
248 | die $@ if $@; | |
249 | ||
250 | eval { PVE::Network::SDN::Ipams::add_cache_mac_ip($mac, $ip); }; | |
251 | warn $@ if $@; | |
252 | } | |
253 | ||
254 | eval { | |
255 | my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip); | |
256 | ||
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 | } | |
263 | }; | |
264 | if ($@) { | |
265 | #rollback | |
266 | my $err = $@; | |
267 | eval { | |
268 | PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac) | |
269 | }; | |
270 | die $err; | |
271 | } | |
272 | return $ip; | |
273 | } | |
274 | ||
275 | sub add_ip { | |
276 | my ($zone, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway, $skipdns) = @_; | |
277 | ||
278 | return if !$subnet || !$ip; | |
279 | ||
280 | my $ipaddr = NetAddr::IP->new($ip); | |
281 | $ip = $ipaddr->canon(); | |
282 | ||
283 | my $ipamid = $zone->{ipam}; | |
284 | my $dns = $zone->{dns}; | |
285 | my $dnszone = $zone->{dnszone}; | |
286 | my $reversedns = $zone->{reversedns}; | |
287 | my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip); | |
288 | my $dnszoneprefix = $subnet->{dnszoneprefix}; | |
289 | ||
290 | $hostname .= ".$dnszoneprefix" if $dnszoneprefix; | |
291 | ||
292 | #verify dns zones before ipam | |
293 | if(!$skipdns) { | |
294 | verify_dns_zone($dnszone, $dns); | |
295 | verify_dns_zone($reversednszone, $reversedns); | |
296 | } | |
297 | ||
298 | if ($ipamid) { | |
299 | ||
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}); | |
303 | ||
304 | eval { | |
305 | $plugin->add_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway); | |
306 | }; | |
307 | die $@ if $@; | |
308 | ||
309 | eval { PVE::Network::SDN::Ipams::add_cache_mac_ip($mac, $ip) if $mac; }; | |
310 | warn $@ if $@; | |
311 | } | |
312 | ||
313 | eval { | |
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 | } | |
320 | }; | |
321 | if ($@) { | |
322 | #rollback | |
323 | my $err = $@; | |
324 | eval { | |
325 | PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac) | |
326 | }; | |
327 | die $err; | |
328 | } | |
329 | } | |
330 | ||
331 | sub update_ip { | |
332 | my ($zone, $subnetid, $subnet, $ip, $hostname, $oldhostname, $mac, $vmid, $skipdns) = @_; | |
333 | ||
334 | return if !$subnet || !$ip; | |
335 | ||
336 | my $ipaddr = NetAddr::IP->new($ip); | |
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}; | |
343 | my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip); | |
344 | my $dnszoneprefix = $subnet->{dnszoneprefix}; | |
345 | ||
346 | $hostname .= ".$dnszoneprefix" if $dnszoneprefix; | |
347 | ||
348 | #verify dns zones before ipam | |
349 | if(!$skipdns) { | |
350 | verify_dns_zone($dnszone, $dns); | |
351 | verify_dns_zone($reversednszone, $reversedns); | |
352 | } | |
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 { | |
359 | $plugin->update_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid); | |
360 | }; | |
361 | die $@ if $@; | |
362 | } | |
363 | ||
364 | return if $hostname eq $oldhostname; | |
365 | ||
366 | eval { | |
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 | } | |
375 | }; | |
376 | } | |
377 | ||
378 | sub del_ip { | |
379 | my ($zone, $subnetid, $subnet, $ip, $hostname, $mac, $skipdns) = @_; | |
380 | ||
381 | return if !$subnet || !$ip; | |
382 | ||
383 | my $ipaddr = NetAddr::IP->new($ip); | |
384 | $ip = $ipaddr->canon(); | |
385 | ||
386 | my $ipamid = $zone->{ipam}; | |
387 | my $dns = $zone->{dns}; | |
388 | my $dnszone = $zone->{dnszone}; | |
389 | my $reversedns = $zone->{reversedns}; | |
390 | my $reversednszone = get_reversedns_zone($subnetid, $subnet, $reversedns, $ip); | |
391 | my $dnszoneprefix = $subnet->{dnszoneprefix}; | |
392 | $hostname .= ".$dnszoneprefix" if $dnszoneprefix; | |
393 | ||
394 | if(!$skipdns) { | |
395 | verify_dns_zone($dnszone, $dns); | |
396 | verify_dns_zone($reversednszone, $reversedns); | |
397 | } | |
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}); | |
403 | $plugin->del_ip($plugin_config, $subnetid, $subnet, $ip); | |
404 | ||
405 | if ($mac) { | |
406 | eval { PVE::Network::SDN::Ipams::del_cache_mac_ip($mac, $ip) }; | |
407 | warn $@ if $@; | |
408 | } | |
409 | } | |
410 | ||
411 | eval { | |
412 | if(!$skipdns) { | |
413 | del_dns_record($dnszone, $dns, $hostname, $ip); | |
414 | del_dns_ptr_record($reversednszone, $reversedns, $ip); | |
415 | } | |
416 | }; | |
417 | if ($@) { | |
418 | warn $@; | |
419 | } | |
420 | } | |
421 | ||
422 | 1; |