Extracted in-memory IP address pool from stroke plugin to libhydra.
[strongswan.git] / src / libhydra / attributes / mem_pool.c
1 /*
2 * Copyright (C) 2010 Tobias Brunner
3 * Copyright (C) 2008 Martin Willi
4 * Hochschule fuer Technik Rapperswil
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2 of the License, or (at your
9 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
13 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * for more details.
15 */
16
17 #include "mem_pool.h"
18
19 #include <debug.h>
20 #include <utils/hashtable.h>
21
22 #define POOL_LIMIT (sizeof(uintptr_t)*8)
23
24 typedef struct private_mem_pool_t private_mem_pool_t;
25
26 /**
27 * private data of mem_pool_t
28 */
29 struct private_mem_pool_t {
30 /**
31 * public interface
32 */
33 mem_pool_t public;
34
35 /**
36 * name of the pool
37 */
38 char *name;
39
40 /**
41 * base address of the pool
42 */
43 host_t *base;
44
45 /**
46 * size of the pool
47 */
48 u_int size;
49
50 /**
51 * next unused address
52 */
53 u_int unused;
54
55 /**
56 * hashtable [identity => offset], for online leases
57 */
58 hashtable_t *online;
59
60 /**
61 * hashtable [identity => offset], for offline leases
62 */
63 hashtable_t *offline;
64
65 /**
66 * hashtable [identity => identity], handles identity references
67 */
68 hashtable_t *ids;
69 };
70
71 /**
72 * hashtable hash function for identities
73 */
74 static u_int id_hash(identification_t *id)
75 {
76 return chunk_hash(id->get_encoding(id));
77 }
78
79 /**
80 * hashtable equals function for identities
81 */
82 static bool id_equals(identification_t *a, identification_t *b)
83 {
84 return a->equals(a, b);
85 }
86
87 /**
88 * convert a pool offset to an address
89 */
90 static host_t* offset2host(private_mem_pool_t *pool, int offset)
91 {
92 chunk_t addr;
93 host_t *host;
94 u_int32_t *pos;
95
96 offset--;
97 if (offset > pool->size)
98 {
99 return NULL;
100 }
101
102 addr = chunk_clone(pool->base->get_address(pool->base));
103 if (pool->base->get_family(pool->base) == AF_INET6)
104 {
105 pos = (u_int32_t*)(addr.ptr + 12);
106 }
107 else
108 {
109 pos = (u_int32_t*)addr.ptr;
110 }
111 *pos = htonl(offset + ntohl(*pos));
112 host = host_create_from_chunk(pool->base->get_family(pool->base), addr, 0);
113 free(addr.ptr);
114 return host;
115 }
116
117 /**
118 * convert a host to a pool offset
119 */
120 static int host2offset(private_mem_pool_t *pool, host_t *addr)
121 {
122 chunk_t host, base;
123 u_int32_t hosti, basei;
124
125 if (addr->get_family(addr) != pool->base->get_family(pool->base))
126 {
127 return -1;
128 }
129 host = addr->get_address(addr);
130 base = pool->base->get_address(pool->base);
131 if (addr->get_family(addr) == AF_INET6)
132 {
133 /* only look at last /32 block */
134 if (!memeq(host.ptr, base.ptr, 12))
135 {
136 return -1;
137 }
138 host = chunk_skip(host, 12);
139 base = chunk_skip(base, 12);
140 }
141 hosti = ntohl(*(u_int32_t*)(host.ptr));
142 basei = ntohl(*(u_int32_t*)(base.ptr));
143 if (hosti > basei + pool->size)
144 {
145 return -1;
146 }
147 return hosti - basei + 1;
148 }
149
150 METHOD(mem_pool_t, get_name, const char*,
151 private_mem_pool_t *this)
152 {
153 return this->name;
154 }
155
156 METHOD(mem_pool_t, get_size, u_int,
157 private_mem_pool_t *this)
158 {
159 return this->size;
160 }
161
162 METHOD(mem_pool_t, get_online, u_int,
163 private_mem_pool_t *this)
164 {
165 return this->online->get_count(this->online);
166 }
167
168 METHOD(mem_pool_t, get_offline, u_int,
169 private_mem_pool_t *this)
170 {
171 return this->offline->get_count(this->offline);
172 }
173
174 METHOD(mem_pool_t, acquire_address, host_t*,
175 private_mem_pool_t *this, identification_t *id, host_t *requested)
176 {
177 uintptr_t offset = 0;
178 enumerator_t *enumerator;
179 identification_t *old_id;
180
181 /* if the pool is empty (e.g. in the %config case) we simply return the
182 * requested address */
183 if (this->size == 0)
184 {
185 return requested->clone(requested);
186 }
187
188 while (TRUE)
189 {
190 if (!requested->is_anyaddr(requested) &&
191 requested->get_family(requested) !=
192 this->base->get_family(this->base))
193 {
194 DBG1("IP pool address family mismatch");
195 break;
196 }
197
198 /* check for a valid offline lease, refresh */
199 offset = (uintptr_t)this->offline->remove(this->offline, id);
200 if (offset)
201 {
202 id = this->ids->get(this->ids, id);
203 if (id)
204 {
205 DBG1("reassigning offline lease to '%Y'", id);
206 this->online->put(this->online, id, (void*)offset);
207 break;
208 }
209 }
210
211 /* check for a valid online lease, reassign */
212 offset = (uintptr_t)this->online->get(this->online, id);
213 if (offset && offset == host2offset(this, requested))
214 {
215 DBG1("reassigning online lease to '%Y'", id);
216 break;
217 }
218
219 if (this->unused < this->size)
220 {
221 /* assigning offset, starting by 1. Handling 0 in hashtable
222 * is difficult. */
223 offset = ++this->unused;
224 id = id->clone(id);
225 this->ids->put(this->ids, id, id);
226 this->online->put(this->online, id, (void*)offset);
227 DBG1("assigning new lease to '%Y'", id);
228 break;
229 }
230
231 /* no more addresses, replace the first found offline lease */
232 enumerator = this->offline->create_enumerator(this->offline);
233 if (enumerator->enumerate(enumerator, &old_id, &offset))
234 {
235 offset = (uintptr_t)this->offline->remove(this->offline, old_id);
236 if (offset)
237 {
238 /* destroy reference to old ID */
239 old_id = this->ids->remove(this->ids, old_id);
240 DBG1("reassigning existing offline lease by '%Y' to '%Y'",
241 old_id, id);
242 if (old_id)
243 {
244 old_id->destroy(old_id);
245 }
246 id = id->clone(id);
247 this->ids->put(this->ids, id, id);
248 this->online->put(this->online, id, (void*)offset);
249 enumerator->destroy(enumerator);
250 break;
251 }
252 }
253 enumerator->destroy(enumerator);
254
255 DBG1("pool '%s' is full, unable to assign address", this->name);
256 break;
257 }
258
259 if (offset)
260 {
261 return offset2host(this, offset);
262 }
263 return NULL;
264 }
265
266 METHOD(mem_pool_t, release_address, bool,
267 private_mem_pool_t *this, host_t *address, identification_t *id)
268 {
269 uintptr_t offset;
270 if (this->size != 0)
271 {
272 offset = (uintptr_t)this->online->remove(this->online, id);
273 if (offset)
274 {
275 id = this->ids->get(this->ids, id);
276 if (id)
277 {
278 DBG1("lease %H by '%Y' went offline", address, id);
279 this->offline->put(this->offline, id, (void*)offset);
280 return TRUE;
281 }
282 }
283 }
284 return FALSE;
285 }
286
287 /**
288 * lease enumerator
289 */
290 typedef struct {
291 /** implemented enumerator interface */
292 enumerator_t public;
293 /** inner hash-table enumerator */
294 enumerator_t *inner;
295 /** enumerated pool */
296 private_mem_pool_t *pool;
297 /** currently enumerated lease address */
298 host_t *current;
299 } lease_enumerator_t;
300
301 METHOD(enumerator_t, lease_enumerate, bool,
302 lease_enumerator_t *this, identification_t **id_out, host_t **addr_out,
303 bool *online)
304 {
305 identification_t *id;
306 uintptr_t offset;
307
308 DESTROY_IF(this->current);
309 this->current = NULL;
310
311 if (this->inner->enumerate(this->inner, &id, NULL))
312 {
313 offset = (uintptr_t)this->pool->online->get(this->pool->online, id);
314 if (offset)
315 {
316 *id_out = id;
317 *addr_out = this->current = offset2host(this->pool, offset);
318 *online = TRUE;
319 return TRUE;
320 }
321 offset = (uintptr_t)this->pool->offline->get(this->pool->offline, id);
322 if (offset)
323 {
324 *id_out = id;
325 *addr_out = this->current = offset2host(this->pool, offset);
326 *online = FALSE;
327 return TRUE;
328 }
329 }
330 return FALSE;
331 }
332
333 METHOD(enumerator_t, lease_enumerator_destroy, void,
334 lease_enumerator_t *this)
335 {
336 DESTROY_IF(this->current);
337 this->inner->destroy(this->inner);
338 free(this);
339 }
340
341 METHOD(mem_pool_t, create_lease_enumerator, enumerator_t*,
342 private_mem_pool_t *this)
343 {
344 lease_enumerator_t *enumerator;
345
346 INIT(enumerator,
347 .public = {
348 .enumerate = (void*)_lease_enumerate,
349 .destroy = (void*)_lease_enumerator_destroy,
350 },
351 .pool = this,
352 .inner = this->ids->create_enumerator(this->ids),
353 );
354
355 return &enumerator->public;
356 }
357
358 METHOD(mem_pool_t, destroy, void,
359 private_mem_pool_t *this)
360 {
361 enumerator_t *enumerator;
362 identification_t *id;
363
364 enumerator = this->ids->create_enumerator(this->ids);
365 while (enumerator->enumerate(enumerator, &id, NULL))
366 {
367 id->destroy(id);
368 }
369 enumerator->destroy(enumerator);
370
371 this->ids->destroy(this->ids);
372 this->online->destroy(this->online);
373 this->offline->destroy(this->offline);
374 DESTROY_IF(this->base);
375 free(this->name);
376 free(this);
377 }
378
379 /**
380 * Described in header
381 */
382 mem_pool_t *mem_pool_create(char *name, host_t *base, int bits)
383 {
384 private_mem_pool_t *this;
385
386 INIT(this,
387 .public = {
388 .get_name = _get_name,
389 .get_size = _get_size,
390 .get_online = _get_online,
391 .get_offline = _get_offline,
392 .acquire_address = _acquire_address,
393 .release_address = _release_address,
394 .create_lease_enumerator = _create_lease_enumerator,
395 .destroy = _destroy,
396 },
397 .name = strdup(name),
398 .online = hashtable_create((hashtable_hash_t)id_hash,
399 (hashtable_equals_t)id_equals, 16),
400 .offline = hashtable_create((hashtable_hash_t)id_hash,
401 (hashtable_equals_t)id_equals, 16),
402 .ids = hashtable_create((hashtable_hash_t)id_hash,
403 (hashtable_equals_t)id_equals, 16),
404 );
405
406 if (base)
407 {
408 int addr_bits = base->get_family(base) == AF_INET ? 32 : 128;
409 /* net bits -> host bits */
410 bits = addr_bits - bits;
411 if (bits > POOL_LIMIT)
412 {
413 bits = POOL_LIMIT;
414 DBG1("virtual IP pool too large, limiting to %H/%d",
415 base, addr_bits - bits);
416 }
417 this->size = 1 << (bits);
418
419 if (this->size > 2)
420 { /* do not use first and last addresses of a block */
421 this->unused++;
422 this->size--;
423 }
424 this->base = base->clone(base);
425 }
426
427 return &this->public;
428 }
429