cc45e5629eaad833527eac65fbb8fa671e5007e5
[strongswan.git] / src / libhydra / attributes / mem_pool.c
1 /*
2 * Copyright (C) 2010 Tobias Brunner
3 * Copyright (C) 2008-2010 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 <library.h>
20 #include <hydra.h>
21 #include <utils/debug.h>
22 #include <collections/hashtable.h>
23 #include <collections/array.h>
24 #include <threading/mutex.h>
25
26 #define POOL_LIMIT (sizeof(u_int)*8 - 1)
27
28 typedef struct private_mem_pool_t private_mem_pool_t;
29
30 /**
31 * private data of mem_pool_t
32 */
33 struct private_mem_pool_t {
34 /**
35 * public interface
36 */
37 mem_pool_t public;
38
39 /**
40 * name of the pool
41 */
42 char *name;
43
44 /**
45 * base address of the pool
46 */
47 host_t *base;
48
49 /**
50 * size of the pool
51 */
52 u_int size;
53
54 /**
55 * next unused address
56 */
57 u_int unused;
58
59 /**
60 * lease hashtable [identity => entry]
61 */
62 hashtable_t *leases;
63
64 /**
65 * lock to safely access the pool
66 */
67 mutex_t *mutex;
68
69 /**
70 * Do we reassign online leases to the same identity, if requested?
71 */
72 bool reassign_online;
73 };
74
75 /**
76 * Lease entry.
77 */
78 typedef struct {
79 /* identitiy reference */
80 identification_t *id;
81 /* array of online leases, as u_int offset */
82 array_t *online;
83 /* array of offline leases, as u_int offset */
84 array_t *offline;
85 } entry_t;
86
87 /**
88 * Create a new entry
89 */
90 static entry_t* entry_create(identification_t *id)
91 {
92 entry_t *entry;
93
94 INIT(entry,
95 .id = id->clone(id),
96 .online = array_create(sizeof(u_int), 0),
97 .offline = array_create(sizeof(u_int), 0),
98 );
99 return entry;
100 }
101
102 /**
103 * hashtable hash function for identities
104 */
105 static u_int id_hash(identification_t *id)
106 {
107 return chunk_hash(id->get_encoding(id));
108 }
109
110 /**
111 * hashtable equals function for identities
112 */
113 static bool id_equals(identification_t *a, identification_t *b)
114 {
115 return a->equals(a, b);
116 }
117
118 /**
119 * convert a pool offset to an address
120 */
121 static host_t* offset2host(private_mem_pool_t *pool, int offset)
122 {
123 chunk_t addr;
124 host_t *host;
125 u_int32_t *pos;
126
127 offset--;
128 if (offset > pool->size)
129 {
130 return NULL;
131 }
132
133 addr = chunk_clone(pool->base->get_address(pool->base));
134 if (pool->base->get_family(pool->base) == AF_INET6)
135 {
136 pos = (u_int32_t*)(addr.ptr + 12);
137 }
138 else
139 {
140 pos = (u_int32_t*)addr.ptr;
141 }
142 *pos = htonl(offset + ntohl(*pos));
143 host = host_create_from_chunk(pool->base->get_family(pool->base), addr, 0);
144 free(addr.ptr);
145 return host;
146 }
147
148 /**
149 * convert a host to a pool offset
150 */
151 static int host2offset(private_mem_pool_t *pool, host_t *addr)
152 {
153 chunk_t host, base;
154 u_int32_t hosti, basei;
155
156 if (addr->get_family(addr) != pool->base->get_family(pool->base))
157 {
158 return -1;
159 }
160 host = addr->get_address(addr);
161 base = pool->base->get_address(pool->base);
162 if (addr->get_family(addr) == AF_INET6)
163 {
164 /* only look at last /32 block */
165 if (!memeq(host.ptr, base.ptr, 12))
166 {
167 return -1;
168 }
169 host = chunk_skip(host, 12);
170 base = chunk_skip(base, 12);
171 }
172 hosti = ntohl(*(u_int32_t*)(host.ptr));
173 basei = ntohl(*(u_int32_t*)(base.ptr));
174 if (hosti > basei + pool->size)
175 {
176 return -1;
177 }
178 return hosti - basei + 1;
179 }
180
181 METHOD(mem_pool_t, get_name, const char*,
182 private_mem_pool_t *this)
183 {
184 return this->name;
185 }
186
187 METHOD(mem_pool_t, get_base, host_t*,
188 private_mem_pool_t *this)
189 {
190 return this->base;
191 }
192
193 METHOD(mem_pool_t, get_size, u_int,
194 private_mem_pool_t *this)
195 {
196 return this->size;
197 }
198
199 METHOD(mem_pool_t, get_online, u_int,
200 private_mem_pool_t *this)
201 {
202 enumerator_t *enumerator;
203 entry_t *entry;
204 u_int count = 0;
205
206 this->mutex->lock(this->mutex);
207 enumerator = this->leases->create_enumerator(this->leases);
208 while (enumerator->enumerate(enumerator, NULL, &entry))
209 {
210 count += array_count(entry->online);
211 }
212 enumerator->destroy(enumerator);
213 this->mutex->unlock(this->mutex);
214
215 return count;
216 }
217
218 METHOD(mem_pool_t, get_offline, u_int,
219 private_mem_pool_t *this)
220 {
221 enumerator_t *enumerator;
222 entry_t *entry;
223 u_int count = 0;
224
225 this->mutex->lock(this->mutex);
226 enumerator = this->leases->create_enumerator(this->leases);
227 while (enumerator->enumerate(enumerator, NULL, &entry))
228 {
229 count += array_count(entry->offline);
230 }
231 enumerator->destroy(enumerator);
232 this->mutex->unlock(this->mutex);
233
234 return count;
235 }
236
237 /**
238 * Get an existing lease for id
239 */
240 static int get_existing(private_mem_pool_t *this, identification_t *id,
241 host_t *requested)
242 {
243 enumerator_t *enumerator;
244 u_int *current;
245 entry_t *entry;
246 int offset = 0;
247
248 entry = this->leases->get(this->leases, id);
249 if (!entry)
250 {
251 return 0;
252 }
253
254 /* check for a valid offline lease, refresh */
255 enumerator = array_create_enumerator(entry->offline);
256 if (enumerator->enumerate(enumerator, &current))
257 {
258 offset = *current;
259 array_insert(entry->online, ARRAY_TAIL, current);
260 array_remove_at(entry->offline, enumerator);
261 }
262 enumerator->destroy(enumerator);
263 if (offset)
264 {
265 DBG1(DBG_CFG, "reassigning offline lease to '%Y'", id);
266 return offset;
267 }
268 if (!this->reassign_online)
269 {
270 return 0;
271 }
272 /* check for a valid online lease to reassign */
273 enumerator = array_create_enumerator(entry->online);
274 while (enumerator->enumerate(enumerator, &current))
275 {
276 if (*current == host2offset(this, requested))
277 {
278 offset = *current;
279 /* add an additional "online" entry */
280 array_insert(entry->online, ARRAY_TAIL, current);
281 break;
282 }
283 }
284 enumerator->destroy(enumerator);
285 if (offset)
286 {
287 DBG1(DBG_CFG, "reassigning online lease to '%Y'", id);
288 }
289 return offset;
290 }
291
292 /**
293 * Get a new lease for id
294 */
295 static int get_new(private_mem_pool_t *this, identification_t *id)
296 {
297 entry_t *entry;
298 u_int offset = 0;
299
300 if (this->unused < this->size)
301 {
302 entry = this->leases->get(this->leases, id);
303 if (!entry)
304 {
305 entry = entry_create(id);
306 this->leases->put(this->leases, entry->id, entry);
307 }
308 /* assigning offset, starting by 1 */
309 offset = ++this->unused;
310 array_insert(entry->online, ARRAY_TAIL, &offset);
311 DBG1(DBG_CFG, "assigning new lease to '%Y'", id);
312 }
313 return offset;
314 }
315
316 /**
317 * Get a reassigned lease for id in case the pool is full
318 */
319 static int get_reassigned(private_mem_pool_t *this, identification_t *id)
320 {
321 enumerator_t *enumerator;
322 entry_t *entry;
323 u_int current, offset = 0;
324
325 enumerator = this->leases->create_enumerator(this->leases);
326 while (enumerator->enumerate(enumerator, NULL, &entry))
327 {
328 if (array_remove(entry->offline, ARRAY_HEAD, &current))
329 {
330 offset = current;
331 DBG1(DBG_CFG, "reassigning existing offline lease by '%Y'"
332 " to '%Y'", entry->id, id);
333 break;
334 }
335 }
336 enumerator->destroy(enumerator);
337
338 if (offset)
339 {
340 entry = entry_create(id);
341 array_insert(entry->online, ARRAY_TAIL, &offset);
342 this->leases->put(this->leases, entry->id, entry);
343 }
344 return offset;
345 }
346
347 METHOD(mem_pool_t, acquire_address, host_t*,
348 private_mem_pool_t *this, identification_t *id, host_t *requested,
349 mem_pool_op_t operation)
350 {
351 int offset = 0;
352
353 /* if the pool is empty (e.g. in the %config case) we simply return the
354 * requested address */
355 if (this->size == 0)
356 {
357 return requested->clone(requested);
358 }
359
360 if (requested->get_family(requested) !=
361 this->base->get_family(this->base))
362 {
363 return NULL;
364 }
365
366 this->mutex->lock(this->mutex);
367 switch (operation)
368 {
369 case MEM_POOL_EXISTING:
370 offset = get_existing(this, id, requested);
371 break;
372 case MEM_POOL_NEW:
373 offset = get_new(this, id);
374 break;
375 case MEM_POOL_REASSIGN:
376 offset = get_reassigned(this, id);
377 if (!offset)
378 {
379 DBG1(DBG_CFG, "pool '%s' is full, unable to assign address",
380 this->name);
381 }
382 break;
383 default:
384 break;
385 }
386 this->mutex->unlock(this->mutex);
387
388 if (offset)
389 {
390 return offset2host(this, offset);
391 }
392 return NULL;
393 }
394
395 METHOD(mem_pool_t, release_address, bool,
396 private_mem_pool_t *this, host_t *address, identification_t *id)
397 {
398 enumerator_t *enumerator;
399 bool found = FALSE, more = FALSE;
400 entry_t *entry;
401 u_int offset, *current;
402
403 if (this->size != 0)
404 {
405 this->mutex->lock(this->mutex);
406 entry = this->leases->get(this->leases, id);
407 if (entry)
408 {
409 offset = host2offset(this, address);
410
411 enumerator = array_create_enumerator(entry->online);
412 while (enumerator->enumerate(enumerator, &current))
413 {
414 if (*current == offset)
415 {
416 if (!found)
417 { /* remove the first entry only */
418 array_remove_at(entry->online, enumerator);
419 found = TRUE;
420 }
421 else
422 { /* but check for more entries */
423 more = TRUE;
424 break;
425 }
426 }
427 }
428 enumerator->destroy(enumerator);
429
430 if (found && !more)
431 {
432 /* no tunnels are online anymore for this lease, make offline */
433 array_insert(entry->offline, ARRAY_TAIL, &offset);
434 DBG1(DBG_CFG, "lease %H by '%Y' went offline", address, id);
435 }
436 }
437 this->mutex->unlock(this->mutex);
438 }
439 return found;
440 }
441
442 /**
443 * lease enumerator
444 */
445 typedef struct {
446 /** implemented enumerator interface */
447 enumerator_t public;
448 /** hash-table enumerator */
449 enumerator_t *entries;
450 /** online enumerator */
451 enumerator_t *online;
452 /** offline enumerator */
453 enumerator_t *offline;
454 /** enumerated pool */
455 private_mem_pool_t *pool;
456 /** currently enumerated entry */
457 entry_t *entry;
458 /** currently enumerated lease address */
459 host_t *addr;
460 } lease_enumerator_t;
461
462 METHOD(enumerator_t, lease_enumerate, bool,
463 lease_enumerator_t *this, identification_t **id, host_t **addr, bool *online)
464 {
465 u_int *offset;
466
467 DESTROY_IF(this->addr);
468 this->addr = NULL;
469
470 while (TRUE)
471 {
472 if (this->entry)
473 {
474 if (this->online->enumerate(this->online, &offset))
475 {
476 *id = this->entry->id;
477 *addr = this->addr = offset2host(this->pool, *offset);
478 *online = TRUE;
479 return TRUE;
480 }
481 if (this->offline->enumerate(this->offline, &offset))
482 {
483 *id = this->entry->id;
484 *addr = this->addr = offset2host(this->pool, *offset);
485 *online = FALSE;
486 return TRUE;
487 }
488 this->online->destroy(this->online);
489 this->offline->destroy(this->offline);
490 this->online = this->offline = NULL;
491 }
492 if (!this->entries->enumerate(this->entries, NULL, &this->entry))
493 {
494 return FALSE;
495 }
496 this->online = array_create_enumerator(this->entry->online);
497 this->offline = array_create_enumerator(this->entry->offline);
498 }
499 }
500
501 METHOD(enumerator_t, lease_enumerator_destroy, void,
502 lease_enumerator_t *this)
503 {
504 DESTROY_IF(this->addr);
505 DESTROY_IF(this->online);
506 DESTROY_IF(this->offline);
507 this->entries->destroy(this->entries);
508 this->pool->mutex->unlock(this->pool->mutex);
509 free(this);
510 }
511
512 METHOD(mem_pool_t, create_lease_enumerator, enumerator_t*,
513 private_mem_pool_t *this)
514 {
515 lease_enumerator_t *enumerator;
516
517 this->mutex->lock(this->mutex);
518 INIT(enumerator,
519 .public = {
520 .enumerate = (void*)_lease_enumerate,
521 .destroy = _lease_enumerator_destroy,
522 },
523 .pool = this,
524 .entries = this->leases->create_enumerator(this->leases),
525 );
526 return &enumerator->public;
527 }
528
529 METHOD(mem_pool_t, destroy, void,
530 private_mem_pool_t *this)
531 {
532 enumerator_t *enumerator;
533 entry_t *entry;
534
535 enumerator = this->leases->create_enumerator(this->leases);
536 while (enumerator->enumerate(enumerator, NULL, &entry))
537 {
538 entry->id->destroy(entry->id);
539 array_destroy(entry->online);
540 array_destroy(entry->offline);
541 free(entry);
542 }
543 enumerator->destroy(enumerator);
544
545 this->leases->destroy(this->leases);
546 this->mutex->destroy(this->mutex);
547 DESTROY_IF(this->base);
548 free(this->name);
549 free(this);
550 }
551
552 /**
553 * Generic constructor
554 */
555 static private_mem_pool_t *create_generic(char *name)
556 {
557 private_mem_pool_t *this;
558
559 INIT(this,
560 .public = {
561 .get_name = _get_name,
562 .get_base = _get_base,
563 .get_size = _get_size,
564 .get_online = _get_online,
565 .get_offline = _get_offline,
566 .acquire_address = _acquire_address,
567 .release_address = _release_address,
568 .create_lease_enumerator = _create_lease_enumerator,
569 .destroy = _destroy,
570 },
571 .name = strdup(name),
572 .leases = hashtable_create((hashtable_hash_t)id_hash,
573 (hashtable_equals_t)id_equals, 16),
574 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
575 .reassign_online = lib->settings->get_bool(lib->settings,
576 "%s.mem-pool.reassign_online", FALSE, lib->ns),
577 );
578
579 return this;
580 }
581
582 /**
583 * Described in header
584 */
585 mem_pool_t *mem_pool_create(char *name, host_t *base, int bits)
586 {
587 private_mem_pool_t *this;
588 int addr_bits;
589
590 this = create_generic(name);
591 if (base)
592 {
593 addr_bits = base->get_family(base) == AF_INET ? 32 : 128;
594 bits = max(0, min(bits, base->get_family(base) == AF_INET ? 32 : 128));
595 /* net bits -> host bits */
596 bits = addr_bits - bits;
597 if (bits > POOL_LIMIT)
598 {
599 bits = POOL_LIMIT;
600 DBG1(DBG_CFG, "virtual IP pool too large, limiting to %H/%d",
601 base, addr_bits - bits);
602 }
603 this->size = 1 << bits;
604
605 if (this->size > 2)
606 { /* do not use first and last addresses of a block */
607 this->unused++;
608 this->size -= 2;
609 }
610 this->base = base->clone(base);
611 }
612
613 return &this->public;
614 }
615
616 /**
617 * Described in header
618 */
619 mem_pool_t *mem_pool_create_range(char *name, host_t *from, host_t *to)
620 {
621 private_mem_pool_t *this;
622 chunk_t fromaddr, toaddr;
623 u_int32_t diff;
624
625 fromaddr = from->get_address(from);
626 toaddr = to->get_address(to);
627
628 if (from->get_family(from) != to->get_family(to) ||
629 fromaddr.len != toaddr.len || fromaddr.len < sizeof(diff) ||
630 memcmp(fromaddr.ptr, toaddr.ptr, toaddr.len) > 0)
631 {
632 DBG1(DBG_CFG, "invalid IP address range: %H-%H", from, to);
633 return NULL;
634 }
635 if (fromaddr.len > sizeof(diff) &&
636 !chunk_equals(chunk_create(fromaddr.ptr, fromaddr.len - sizeof(diff)),
637 chunk_create(toaddr.ptr, toaddr.len - sizeof(diff))))
638 {
639 DBG1(DBG_CFG, "IP address range too large: %H-%H", from, to);
640 return NULL;
641 }
642 this = create_generic(name);
643 this->base = from->clone(from);
644 diff = untoh32(toaddr.ptr + toaddr.len - sizeof(diff)) -
645 untoh32(fromaddr.ptr + fromaddr.len - sizeof(diff));
646 this->size = diff + 1;
647
648 return &this->public;
649 }