]>
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 { |
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 | 121 | sub 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 | 132 | sub 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 | 143 | sub 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 | 154 | sub 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 | 166 | sub 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 | 177 | sub 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 |
188 | sub 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 | ||
199 | sub 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 | 210 | sub 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 | ||
260 | sub 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 | 313 | sub 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 | 360 | sub 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 | 399 | 1; |