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