]> git.proxmox.com Git - pve-network.git/blob - src/PVE/Network/SDN/Ipams/PVEPlugin.pm
ipams : add_next_freeip : return ip not cidr
[pve-network.git] / src / PVE / Network / SDN / Ipams / PVEPlugin.pm
1 package PVE::Network::SDN::Ipams::PVEPlugin;
2
3 use strict;
4 use warnings;
5 use PVE::INotify;
6 use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_register_file cfs_lock_file);
7 use PVE::Tools;
8 use JSON;
9 use NetAddr::IP qw(:lower);
10
11 use Net::IP;
12 use Digest::SHA;
13
14 use base('PVE::Network::SDN::Ipams::Plugin');
15
16
17 my $ipamdb_file = "priv/ipam.db";
18
19 PVE::Cluster::cfs_register_file($ipamdb_file,
20 sub { PVE::Network::SDN::Ipams::PVEPlugin->parse_config(@_); },
21 sub { PVE::Network::SDN::Ipams::PVEPlugin->write_config(@_); });
22
23 sub type {
24 return 'pve';
25 }
26
27 sub properties {
28 }
29
30 sub options {
31 }
32
33 # Plugin implementation
34
35 sub add_subnet {
36 my ($class, $plugin_config, $subnetid, $subnet) = @_;
37
38 my $cidr = $subnet->{cidr};
39 my $zone = $subnet->{zone};
40 my $gateway = $subnet->{gateway};
41
42
43 cfs_lock_file($ipamdb_file, undef, sub {
44 my $db = {};
45 $db = read_db();
46
47 $db->{zones}->{$zone} = {} if !$db->{zones}->{$zone};
48 my $zonedb = $db->{zones}->{$zone};
49
50 if(!$zonedb->{subnets}->{$cidr}) {
51 #create subnet
52 $zonedb->{subnets}->{$cidr}->{ips} = {};
53 write_db($db);
54 }
55 });
56 die "$@" if $@;
57 }
58
59 sub only_gateway_remains {
60 my ($ips) = @_;
61
62 if (keys %{$ips} == 1 &&
63 (values %{$ips})[0]->{gateway} == 1) {
64 return 1;
65 }
66 return 0;
67 };
68
69 sub del_subnet {
70 my ($class, $plugin_config, $subnetid, $subnet) = @_;
71
72 my $cidr = $subnet->{cidr};
73 my $zone = $subnet->{zone};
74
75 cfs_lock_file($ipamdb_file, undef, sub {
76
77 my $db = read_db();
78
79 my $dbzone = $db->{zones}->{$zone};
80 die "zone '$zone' doesn't exist in IPAM DB\n" if !$dbzone;
81 my $dbsubnet = $dbzone->{subnets}->{$cidr};
82 die "subnet '$cidr' doesn't exist in IPAM DB\n" if !$dbsubnet;
83
84 my $ips = $dbsubnet->{ips};
85
86 if (keys %{$ips} > 0 && !only_gateway_remains($ips)) {
87 die "cannot delete subnet '$cidr', not empty\n";
88 }
89
90 delete $dbzone->{subnets}->{$cidr};
91
92 write_db($db);
93 });
94 die "$@" if $@;
95
96 }
97
98 sub add_ip {
99 my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway) = @_;
100
101 my $cidr = $subnet->{cidr};
102 my $zone = $subnet->{zone};
103
104 cfs_lock_file($ipamdb_file, undef, sub {
105
106 my $db = read_db();
107 my $dbzone = $db->{zones}->{$zone};
108 die "zone '$zone' doesn't exist in IPAM DB\n" if !$dbzone;
109 my $dbsubnet = $dbzone->{subnets}->{$cidr};
110 die "subnet '$cidr' doesn't exist in IPAM DB\n" if !$dbsubnet;
111
112 die "IP '$ip' already exist\n" if (!$is_gateway && defined($dbsubnet->{ips}->{$ip})) || ($is_gateway && defined($dbsubnet->{ips}->{$ip}) && !defined($dbsubnet->{ips}->{$ip}->{gateway}));
113
114 my $data = {};
115 if ($is_gateway) {
116 $data->{gateway} = 1;
117 } else {
118 $data->{vmid} = $vmid if $vmid;
119 $data->{hostname} = $hostname if $hostname;
120 $data->{mac} = $mac if $mac;
121 }
122
123 $dbsubnet->{ips}->{$ip} = $data;
124
125 write_db($db);
126 });
127 die "$@" if $@;
128 }
129
130 sub update_ip {
131 my ($class, $plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $vmid, $is_gateway) = @_;
132 return;
133 }
134
135 sub add_next_freeip {
136 my ($class, $plugin_config, $subnetid, $subnet, $hostname, $mac, $vmid, $noerr) = @_;
137
138 my $cidr = $subnet->{cidr};
139 my $network = $subnet->{network};
140 my $zone = $subnet->{zone};
141 my $mask = $subnet->{mask};
142 my $freeip = undef;
143
144 cfs_lock_file($ipamdb_file, undef, sub {
145
146 my $db = read_db();
147 my $dbzone = $db->{zones}->{$zone};
148 die "zone '$zone' doesn't exist in IPAM DB\n" if !$dbzone;
149 my $dbsubnet = $dbzone->{subnets}->{$cidr};
150 die "subnet '$cidr' doesn't exist in IPAM DB" if !$dbsubnet;
151
152 if (Net::IP::ip_is_ipv4($network) && $mask == 32) {
153 die "cannot find free IP in subnet '$cidr'\n" if defined($dbsubnet->{ips}->{$network});
154 $freeip = $network;
155 } else {
156 my $iplist = NetAddr::IP->new($cidr);
157 my $lastip = $iplist->last()->canon();
158 $iplist++ if Net::IP::ip_is_ipv4($network); #skip network address for ipv4
159 while(1) {
160 my $ip = $iplist->canon();
161 if (defined($dbsubnet->{ips}->{$ip})) {
162 last if $ip eq $lastip;
163 $iplist++;
164 next;
165 }
166 $freeip = $ip;
167 last;
168 }
169 }
170
171 die "can't find free ip in subnet '$cidr'\n" if !$freeip;
172
173 $dbsubnet->{ips}->{$freeip} = {};
174
175 write_db($db);
176 });
177 die "$@" if $@;
178
179 return $freeip;
180 }
181
182 sub add_range_next_freeip {
183 my ($class, $plugin_config, $subnet, $range, $data, $noerr) = @_;
184
185 my $cidr = $subnet->{cidr};
186 my $zone = $subnet->{zone};
187
188 cfs_lock_file($ipamdb_file, undef, sub {
189 my $db = read_db();
190
191 my $dbzone = $db->{zones}->{$zone};
192 die "zone '$zone' doesn't exist in IPAM DB\n" if !$dbzone;
193
194 my $dbsubnet = $dbzone->{subnets}->{$cidr};
195 die "subnet '$cidr' doesn't exist in IPAM DB\n" if !$dbsubnet;
196
197 my $ip = new Net::IP ("$range->{'start-address'} - $range->{'end-address'}")
198 or die "Invalid IP address(es) in Range!\n";
199 my $mac = $data->{mac};
200
201 do {
202 my $ip_address = $ip->version() == 6 ? $ip->short() : $ip->ip();
203 if (!$dbsubnet->{ips}->{$ip_address}) {
204 $dbsubnet->{ips}->{$ip_address} = $data;
205 write_db($db);
206
207 return $ip_address;
208 }
209 } while (++$ip);
210
211 die "No free IP left in Range $range->{'start-address'}:$range->{'end-address'}}\n";
212 });
213 }
214
215 sub del_ip {
216 my ($class, $plugin_config, $subnetid, $subnet, $ip) = @_;
217
218 my $cidr = $subnet->{cidr};
219 my $zone = $subnet->{zone};
220
221 cfs_lock_file($ipamdb_file, undef, sub {
222
223 my $db = read_db();
224 die "zone $zone don't exist in ipam db" if !$db->{zones}->{$zone};
225 my $dbzone = $db->{zones}->{$zone};
226 die "subnet $cidr don't exist in ipam db" if !$dbzone->{subnets}->{$cidr};
227 my $dbsubnet = $dbzone->{subnets}->{$cidr};
228
229 die "IP '$ip' does not exist in IPAM DB\n" if !defined($dbsubnet->{ips}->{$ip});
230 delete $dbsubnet->{ips}->{$ip};
231 write_db($db);
232 });
233 die "$@" if $@;
234 }
235
236 sub get_ips_from_mac {
237 my ($class, $plugin_config, $mac, $zoneid) = @_;
238
239 #just in case, as this should already be cached in local macs.db
240
241 my $ip4 = undef;
242 my $ip6 = undef;
243
244 my $db = read_db();
245 die "zone $zoneid don't exist in ipam db" if !$db->{zones}->{$zoneid};
246 my $dbzone = $db->{zones}->{$zoneid};
247 my $subnets = $dbzone->{subnets};
248
249 for my $subnet ( keys %$subnets) {
250 next if Net::IP::ip_is_ipv4($subnet) && $ip4;
251 next if $ip6;
252 my $ips = $subnets->{$subnet}->{ips};
253 for my $ip (keys %$ips) {
254 my $ipobject = $ips->{$ip};
255 if ($ipobject->{mac} && $ipobject->{mac} eq $mac) {
256 if (Net::IP::ip_is_ipv4($ip)) {
257 $ip4 = $ip;
258 } else {
259 $ip6 = $ip;
260 }
261 }
262 }
263 last if $ip4 && $ip6;
264 }
265 return ($ip4, $ip6);
266 }
267
268 #helpers
269
270 sub read_db {
271 my $db = cfs_read_file($ipamdb_file);
272 return $db;
273 }
274
275 sub write_db {
276 my ($cfg) = @_;
277
278 my $json = to_json($cfg);
279 cfs_write_file($ipamdb_file, $json);
280 }
281
282 sub write_config {
283 my ($class, $filename, $cfg) = @_;
284
285 return $cfg;
286 }
287
288 sub parse_config {
289 my ($class, $filename, $raw) = @_;
290
291 $raw = '{}' if !defined($raw) ||$raw eq '';
292 my $cfg = from_json($raw);
293
294 return $cfg;
295 }
296
297 1;