vici: Add option to query leases of pools
[strongswan.git] / src / libcharon / plugins / vici / vici_attribute.c
1 /*
2 * Copyright (C) 2014-2015 Tobias Brunner
3 * Hochschule fuer Technik Rapperswil
4 *
5 * Copyright (C) 2014 Martin Willi
6 * Copyright (C) 2014 revosec AG
7 *
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation; either version 2 of the License, or (at your
11 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 * for more details.
17 */
18
19 #include "vici_attribute.h"
20 #include "vici_builder.h"
21
22 #include <daemon.h>
23 #include <collections/hashtable.h>
24 #include <collections/array.h>
25 #include <threading/rwlock.h>
26 #include <attributes/mem_pool.h>
27
28 typedef struct private_vici_attribute_t private_vici_attribute_t;
29
30 /**
31 * private data of vici_attribute
32 */
33 struct private_vici_attribute_t {
34
35 /**
36 * public functions
37 */
38 vici_attribute_t public;
39
40 /**
41 * vici connection dispatcher
42 */
43 vici_dispatcher_t *dispatcher;
44
45 /**
46 * Configured pools, as char* => pool_t
47 */
48 hashtable_t *pools;
49
50 /**
51 * rwlock to lock access to pools
52 */
53 rwlock_t *lock;
54 };
55
56 /**
57 * Single configuration attribute with type
58 */
59 typedef struct {
60 /** type of attribute */
61 configuration_attribute_type_t type;
62 /** attribute value */
63 chunk_t value;
64 } attribute_t;
65
66 /**
67 * Clean up an attribute
68 */
69 static void attribute_destroy(attribute_t *attr)
70 {
71 free(attr->value.ptr);
72 free(attr);
73 }
74
75 /**
76 * Pool instances with associated attributes
77 */
78 typedef struct {
79 /** in-memory virtual IP pool */
80 mem_pool_t *vips;
81 /** configuration attributes, as attribute_t */
82 array_t *attrs;
83 } pool_t;
84
85 /**
86 * Clean up a pool instance
87 */
88 static void pool_destroy(pool_t *pool)
89 {
90 DESTROY_IF(pool->vips);
91 array_destroy_function(pool->attrs, (void*)attribute_destroy, NULL);
92 free(pool);
93 }
94
95 /**
96 * Find an existing or not yet existing lease
97 */
98 static host_t *find_addr(private_vici_attribute_t *this, linked_list_t *pools,
99 identification_t *id, host_t *requested,
100 mem_pool_op_t op, host_t *peer)
101 {
102 enumerator_t *enumerator;
103 host_t *addr = NULL;
104 pool_t *pool;
105 char *name;
106
107 enumerator = pools->create_enumerator(pools);
108 while (enumerator->enumerate(enumerator, &name))
109 {
110 pool = this->pools->get(this->pools, name);
111 if (pool)
112 {
113 addr = pool->vips->acquire_address(pool->vips, id, requested,
114 op, peer);
115 if (addr)
116 {
117 break;
118 }
119 }
120 }
121 enumerator->destroy(enumerator);
122
123 return addr;
124 }
125
126 METHOD(attribute_provider_t, acquire_address, host_t*,
127 private_vici_attribute_t *this, linked_list_t *pools, ike_sa_t *ike_sa,
128 host_t *requested)
129 {
130 identification_t *id;
131 host_t *addr, *peer;
132
133 id = ike_sa->get_other_eap_id(ike_sa);
134 peer = ike_sa->get_other_host(ike_sa);
135
136 this->lock->read_lock(this->lock);
137
138 addr = find_addr(this, pools, id, requested, MEM_POOL_EXISTING, peer);
139 if (!addr)
140 {
141 addr = find_addr(this, pools, id, requested, MEM_POOL_NEW, peer);
142 if (!addr)
143 {
144 addr = find_addr(this, pools, id, requested, MEM_POOL_REASSIGN, peer);
145 }
146 }
147
148 this->lock->unlock(this->lock);
149
150 return addr;
151 }
152
153 METHOD(attribute_provider_t, release_address, bool,
154 private_vici_attribute_t *this, linked_list_t *pools, host_t *address,
155 ike_sa_t *ike_sa)
156 {
157 enumerator_t *enumerator;
158 identification_t *id;
159 bool found = FALSE;
160 pool_t *pool;
161 char *name;
162
163 id = ike_sa->get_other_eap_id(ike_sa);
164
165 this->lock->read_lock(this->lock);
166
167 enumerator = pools->create_enumerator(pools);
168 while (enumerator->enumerate(enumerator, &name))
169 {
170 pool = this->pools->get(this->pools, name);
171 if (pool)
172 {
173 found = pool->vips->release_address(pool->vips, address, id);
174 if (found)
175 {
176 break;
177 }
178 }
179 }
180 enumerator->destroy(enumerator);
181
182 this->lock->unlock(this->lock);
183
184 return found;
185 }
186
187 /**
188 * Filter mapping attribute_t to enumerated type/value arguments
189 */
190 static bool attr_filter(void *data, attribute_t **attr,
191 configuration_attribute_type_t *type,
192 void *in, chunk_t *value)
193 {
194 *type = (*attr)->type;
195 *value = (*attr)->value;
196 return TRUE;
197 }
198
199 /**
200 * Create nested inner enumerator over pool attributes
201 */
202 CALLBACK(create_nested, enumerator_t*,
203 pool_t *pool, void *this)
204 {
205 return enumerator_create_filter(array_create_enumerator(pool->attrs),
206 (void*)attr_filter, NULL, NULL);
207 }
208
209 /**
210 * Data associated to nested enumerator cleanup
211 */
212 typedef struct {
213 private_vici_attribute_t *this;
214 linked_list_t *list;
215 } nested_data_t;
216
217 /**
218 * Clean up nested enumerator data
219 */
220 CALLBACK(nested_cleanup, void,
221 nested_data_t *data)
222 {
223 data->this->lock->unlock(data->this->lock);
224 data->list->destroy(data->list);
225 free(data);
226 }
227
228 /**
229 * Check if any of vips is from pool
230 */
231 static bool have_vips_from_pool(mem_pool_t *pool, linked_list_t *vips)
232 {
233 enumerator_t *enumerator;
234 host_t *host;
235 chunk_t start, end, current;
236 u_int32_t size;
237 bool found = FALSE;
238
239 host = pool->get_base(pool);
240 start = host->get_address(host);
241
242 if (start.len >= sizeof(size))
243 {
244 end = chunk_clone(start);
245
246 /* mem_pool is currenty limited to 2^31 addresses, so 32-bit
247 * calculations should be sufficient. */
248 size = untoh32(start.ptr + start.len - sizeof(size));
249 htoun32(end.ptr + end.len - sizeof(size), size + pool->get_size(pool));
250
251 enumerator = vips->create_enumerator(vips);
252 while (enumerator->enumerate(enumerator, &host))
253 {
254 current = host->get_address(host);
255 if (chunk_compare(current, start) >= 0 &&
256 chunk_compare(current, end) < 0)
257 {
258 found = TRUE;
259 break;
260 }
261 }
262 enumerator->destroy(enumerator);
263
264 free(end.ptr);
265 }
266 return found;
267 }
268
269 METHOD(attribute_provider_t, create_attribute_enumerator, enumerator_t*,
270 private_vici_attribute_t *this, linked_list_t *pools,
271 ike_sa_t *ike_sa, linked_list_t *vips)
272 {
273 enumerator_t *enumerator;
274 nested_data_t *data;
275 pool_t *pool;
276 char *name;
277
278 INIT(data,
279 .this = this,
280 .list = linked_list_create(),
281 );
282
283 this->lock->read_lock(this->lock);
284
285 enumerator = pools->create_enumerator(pools);
286 while (enumerator->enumerate(enumerator, &name))
287 {
288 pool = this->pools->get(this->pools, name);
289 if (pool && have_vips_from_pool(pool->vips, vips))
290 {
291 data->list->insert_last(data->list, pool);
292 }
293 }
294 enumerator->destroy(enumerator);
295
296 return enumerator_create_nested(data->list->create_enumerator(data->list),
297 create_nested, data, nested_cleanup);
298 }
299
300 /**
301 * Merge a pool configuration with existing ones
302 */
303 static bool merge_pool(private_vici_attribute_t *this, pool_t *new)
304 {
305 mem_pool_t *tmp;
306 host_t *base;
307 pool_t *old;
308 const char *name;
309 u_int size;
310
311 name = new->vips->get_name(new->vips);
312 base = new->vips->get_base(new->vips);
313 size = new->vips->get_size(new->vips);
314
315 old = this->pools->remove(this->pools, name);
316 if (!old)
317 {
318 this->pools->put(this->pools, name, new);
319 DBG1(DBG_CFG, "added vici pool %s: %H, %u entries", name, base, size);
320 return TRUE;
321 }
322
323 if (base->ip_equals(base, old->vips->get_base(old->vips)) &&
324 size == old->vips->get_size(old->vips))
325 {
326 /* no changes in pool, so keep existing, but use new attributes */
327 DBG1(DBG_CFG, "updated vici pool %s: %H, %u entries", name, base, size);
328 tmp = new->vips;
329 new->vips = old->vips;
330 old->vips = tmp;
331 this->pools->put(this->pools, new->vips->get_name(new->vips), new);
332 pool_destroy(old);
333 return TRUE;
334 }
335 if (old->vips->get_online(old->vips) == 0)
336 {
337 /* can replace old pool, no online leases */
338 DBG1(DBG_CFG, "replaced vici pool %s: %H, %u entries", name, base, size);
339 this->pools->put(this->pools, name, new);
340 pool_destroy(old);
341 return TRUE;
342 }
343 /* have online leases, unable to replace, TODO: migrate leases? */
344 DBG1(DBG_CFG, "vici pool %s has %u online leases, unable to replace",
345 name, old->vips->get_online(old->vips));
346 this->pools->put(this->pools, old->vips->get_name(old->vips), old);
347 return FALSE;
348 }
349
350 /**
351 * Create a (error) reply message
352 */
353 static vici_message_t* create_reply(char *fmt, ...)
354 {
355 vici_builder_t *builder;
356 va_list args;
357
358 builder = vici_builder_create();
359 builder->add_kv(builder, "success", fmt ? "no" : "yes");
360 if (fmt)
361 {
362 va_start(args, fmt);
363 builder->vadd_kv(builder, "errmsg", fmt, args);
364 va_end(args);
365 }
366 return builder->finalize(builder);
367 }
368
369 /**
370 * Parse a range definition of an address pool
371 */
372 static mem_pool_t *create_pool_range(char *name, char *buf)
373 {
374 mem_pool_t *pool;
375 host_t *from, *to;
376
377 if (!host_create_from_range(buf, &from, &to))
378 {
379 return NULL;
380 }
381 pool = mem_pool_create_range(name, from, to);
382 from->destroy(from);
383 to->destroy(to);
384 return pool;
385 }
386
387 /**
388 * Parse callback data, passed to each callback
389 */
390 typedef struct {
391 private_vici_attribute_t *this;
392 vici_message_t *reply;
393 } request_data_t;
394
395 /**
396 * Data associated to a pool load
397 */
398 typedef struct {
399 request_data_t *request;
400 char *name;
401 pool_t *pool;
402 } load_data_t;
403
404 CALLBACK(pool_li, bool,
405 load_data_t *data, vici_message_t *message, char *name, chunk_t value)
406 {
407 struct {
408 char *name;
409 configuration_attribute_type_t v4;
410 configuration_attribute_type_t v6;
411 } keys[] = {
412 {"address", INTERNAL_IP4_ADDRESS, INTERNAL_IP6_ADDRESS },
413 {"dns", INTERNAL_IP4_DNS, INTERNAL_IP6_DNS },
414 {"nbns", INTERNAL_IP4_NBNS, INTERNAL_IP6_NBNS },
415 {"dhcp", INTERNAL_IP4_DHCP, INTERNAL_IP6_DHCP },
416 {"netmask", INTERNAL_IP4_NETMASK, INTERNAL_IP6_NETMASK },
417 {"server", INTERNAL_IP4_SERVER, INTERNAL_IP6_SERVER },
418 {"subnet", INTERNAL_IP4_SUBNET, INTERNAL_IP6_SUBNET },
419 {"split_include", UNITY_SPLIT_INCLUDE, UNITY_SPLIT_INCLUDE },
420 {"split_exclude", UNITY_LOCAL_LAN, UNITY_LOCAL_LAN },
421 };
422 char buf[256];
423 int i, index = -1, mask = -1, type = 0;
424 chunk_t encoding;
425 attribute_t *attr;
426 host_t *host = NULL;
427
428 for (i = 0; i < countof(keys); i++)
429 {
430 if (streq(name, keys[i].name))
431 {
432 index = i;
433 break;
434 }
435 }
436 if (index == -1)
437 {
438 type = atoi(name);
439 if (!type)
440 {
441 data->request->reply = create_reply("invalid attribute: %s", name);
442 return FALSE;
443 }
444 }
445
446 if (vici_stringify(value, buf, sizeof(buf)))
447 {
448 if (strchr(buf, '/'))
449 {
450 host = host_create_from_subnet(buf, &mask);
451 }
452 else
453 {
454 host = host_create_from_string(buf, 0);
455 }
456 }
457 if (host)
458 {
459 if (index != -1)
460 {
461 switch (host->get_family(host))
462 {
463 case AF_INET:
464 type = keys[index].v4;
465 break;
466 case AF_INET6:
467 default:
468 type = keys[index].v6;
469 break;
470 }
471 }
472 if (mask == -1)
473 {
474 encoding = chunk_clone(host->get_address(host));
475 }
476 else
477 {
478 if (host->get_family(host) == AF_INET)
479 { /* IPv4 attributes contain a subnet mask */
480 u_int32_t netmask = 0;
481
482 if (mask)
483 { /* shifting u_int32_t by 32 or more is undefined */
484 mask = 32 - mask;
485 netmask = htonl((0xFFFFFFFF >> mask) << mask);
486 }
487 encoding = chunk_cat("cc", host->get_address(host),
488 chunk_from_thing(netmask));
489 }
490 else
491 { /* IPv6 addresses the prefix only */
492 encoding = chunk_cat("cc", host->get_address(host),
493 chunk_from_chars(mask));
494 }
495 }
496 host->destroy(host);
497 }
498 else
499 {
500 if (index != -1)
501 {
502 data->request->reply = create_reply("invalid attribute value "
503 "for %s", name);
504 return FALSE;
505 }
506 /* use raw binary data for numbered attributes */
507 encoding = chunk_clone(value);
508 }
509 INIT(attr,
510 .type = type,
511 .value = encoding,
512 );
513 array_insert_create(&data->pool->attrs, ARRAY_TAIL, attr);
514 return TRUE;
515 }
516
517 CALLBACK(pool_kv, bool,
518 load_data_t *data, vici_message_t *message, char *name, chunk_t value)
519 {
520 if (streq(name, "addrs"))
521 {
522 char buf[128];
523 mem_pool_t *pool;
524 host_t *base = NULL;
525 int bits;
526
527 if (data->pool->vips)
528 {
529 data->request->reply = create_reply("multiple addrs defined");
530 return FALSE;
531 }
532 if (!vici_stringify(value, buf, sizeof(buf)))
533 {
534 data->request->reply = create_reply("invalid addrs value");
535 return FALSE;
536 }
537 pool = create_pool_range(data->name, buf);
538 if (!pool)
539 {
540 base = host_create_from_subnet(buf, &bits);
541 if (base)
542 {
543 pool = mem_pool_create(data->name, base, bits);
544 base->destroy(base);
545 }
546 }
547 if (!pool)
548 {
549 data->request->reply = create_reply("invalid addrs value: %s", buf);
550 return FALSE;
551 }
552 data->pool->vips = pool;
553 return TRUE;
554 }
555 data->request->reply = create_reply("invalid attribute: %s", name);
556 return FALSE;
557 }
558
559 CALLBACK(pool_sn, bool,
560 request_data_t *request, vici_message_t *message,
561 vici_parse_context_t *ctx, char *name)
562 {
563 load_data_t data = {
564 .request = request,
565 .name = name,
566 };
567 bool merged;
568
569 INIT(data.pool);
570
571 if (!message->parse(message, ctx, NULL, pool_kv, pool_li, &data))
572 {
573 pool_destroy(data.pool);
574 return FALSE;
575 }
576
577 if (!data.pool->vips)
578 {
579 request->reply = create_reply("missing addrs for pool '%s'", name);
580 pool_destroy(data.pool);
581 return FALSE;
582 }
583
584 request->this->lock->write_lock(request->this->lock);
585 merged = merge_pool(request->this, data.pool);
586 request->this->lock->unlock(request->this->lock);
587
588 if (!merged)
589 {
590 request->reply = create_reply("vici pool %s has online leases, "
591 "unable to replace", name);
592 pool_destroy(data.pool);
593 }
594 return merged;
595 }
596
597 CALLBACK(load_pool, vici_message_t*,
598 private_vici_attribute_t *this, char *name, u_int id,
599 vici_message_t *message)
600 {
601 request_data_t request = {
602 .this = this,
603 };
604
605 if (!message->parse(message, NULL, pool_sn, NULL, NULL, &request))
606 {
607 if (request.reply)
608 {
609 return request.reply;
610 }
611 return create_reply("parsing request failed");
612 }
613 return create_reply(NULL);
614 }
615
616 CALLBACK(unload_pool, vici_message_t*,
617 private_vici_attribute_t *this, char *name, u_int id,
618 vici_message_t *message)
619 {
620 vici_message_t *reply;
621 u_int online;
622 pool_t *pool;
623
624 name = message->get_str(message, NULL, "name");
625 if (!name)
626 {
627 return create_reply("missing pool name to unload");
628 }
629
630 this->lock->write_lock(this->lock);
631
632 pool = this->pools->remove(this->pools, name);
633 if (pool)
634 {
635 online = pool->vips->get_online(pool->vips);
636 if (online)
637 {
638 DBG1(DBG_CFG, "vici pool %s has %u online leases, unable to unload",
639 name, online);
640 reply = create_reply("%s has online leases, unable to unload", name);
641 this->pools->put(this->pools, pool->vips->get_name(pool->vips), pool);
642 }
643 else
644 {
645 DBG1(DBG_CFG, "unloaded vici pool %s", name);
646 reply = create_reply(NULL);
647 pool_destroy(pool);
648 }
649 }
650 else
651 {
652 reply = create_reply("%s not found", name);
653 }
654
655 this->lock->unlock(this->lock);
656
657 return reply;
658 }
659
660 CALLBACK(get_pools, vici_message_t*,
661 private_vici_attribute_t *this, char *name, u_int id,
662 vici_message_t *message)
663 {
664 vici_builder_t *builder;
665 enumerator_t *enumerator, *leases;
666 mem_pool_t *vips;
667 pool_t *pool;
668 identification_t *uid;
669 host_t *lease;
670 bool list_leases, on;
671 char buf[32];
672 int i;
673
674 list_leases = message->get_bool(message, FALSE, "leases");
675
676 builder = vici_builder_create();
677
678 this->lock->read_lock(this->lock);
679 enumerator = this->pools->create_enumerator(this->pools);
680 while (enumerator->enumerate(enumerator, &name, &pool))
681 {
682 vips = pool->vips;
683
684 builder->begin_section(builder, name);
685
686 builder->add_kv(builder, "base", "%H", vips->get_base(vips));
687 builder->add_kv(builder, "size", "%u", vips->get_size(vips));
688 builder->add_kv(builder, "online", "%u", vips->get_online(vips));
689 builder->add_kv(builder, "offline", "%u", vips->get_offline(vips));
690
691 if (list_leases)
692 {
693 i = 0;
694 builder->begin_section(builder, "leases");
695 leases = vips->create_lease_enumerator(vips);
696 while (leases && leases->enumerate(leases, &uid, &lease, &on))
697 {
698 snprintf(buf, sizeof(buf), "%d", i++);
699 builder->begin_section(builder, buf);
700 builder->add_kv(builder, "address", "%H", lease);
701 builder->add_kv(builder, "identity", "%Y", uid);
702 builder->add_kv(builder, "status", on ? "online" : "offline");
703 builder->end_section(builder);
704 }
705 leases->destroy(leases);
706 builder->end_section(builder);
707 }
708 builder->end_section(builder);
709 }
710 enumerator->destroy(enumerator);
711 this->lock->unlock(this->lock);
712
713 return builder->finalize(builder);
714 }
715
716 static void manage_command(private_vici_attribute_t *this,
717 char *name, vici_command_cb_t cb, bool reg)
718 {
719 this->dispatcher->manage_command(this->dispatcher, name,
720 reg ? cb : NULL, this);
721 }
722
723 /**
724 * (Un-)register dispatcher functions
725 */
726 static void manage_commands(private_vici_attribute_t *this, bool reg)
727 {
728 manage_command(this, "load-pool", load_pool, reg);
729 manage_command(this, "unload-pool", unload_pool, reg);
730 manage_command(this, "get-pools", get_pools, reg);
731 }
732
733 METHOD(vici_attribute_t, destroy, void,
734 private_vici_attribute_t *this)
735 {
736 enumerator_t *enumerator;
737 pool_t *pool;
738
739 manage_commands(this, FALSE);
740
741 enumerator = this->pools->create_enumerator(this->pools);
742 while (enumerator->enumerate(enumerator, NULL, &pool))
743 {
744 pool_destroy(pool);
745 }
746 enumerator->destroy(enumerator);
747 this->pools->destroy(this->pools);
748 this->lock->destroy(this->lock);
749 free(this);
750 }
751
752 /*
753 * see header file
754 */
755 vici_attribute_t *vici_attribute_create(vici_dispatcher_t *dispatcher)
756 {
757 private_vici_attribute_t *this;
758
759 INIT(this,
760 .public = {
761 .provider = {
762 .acquire_address = _acquire_address,
763 .release_address = _release_address,
764 .create_attribute_enumerator = _create_attribute_enumerator,
765 },
766 .destroy = _destroy,
767 },
768 .dispatcher = dispatcher,
769 .pools = hashtable_create(hashtable_hash_str, hashtable_equals_str, 4),
770 .lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
771 );
772
773 manage_commands(this, TRUE);
774
775 return &this->public;
776 }