libipsec: Support usage statistics and query_sa() on IPsec SAs
[strongswan.git] / src / libipsec / ipsec_sa_mgr.c
1 /*
2 * Copyright (C) 2012 Tobias Brunner
3 * Copyright (C) 2012 Giuliano Grassi
4 * Copyright (C) 2012 Ralf Sager
5 * Hochschule fuer Technik Rapperswil
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation; either version 2 of the License, or (at your
10 * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 * for more details.
16 */
17
18 #include "ipsec.h"
19 #include "ipsec_sa_mgr.h"
20
21 #include <utils/debug.h>
22 #include <library.h>
23 #include <processing/jobs/callback_job.h>
24 #include <threading/condvar.h>
25 #include <threading/mutex.h>
26 #include <collections/hashtable.h>
27 #include <collections/linked_list.h>
28
29 typedef struct private_ipsec_sa_mgr_t private_ipsec_sa_mgr_t;
30
31 /**
32 * Private additions to ipsec_sa_mgr_t.
33 */
34 struct private_ipsec_sa_mgr_t {
35
36 /**
37 * Public members of ipsec_sa_mgr_t.
38 */
39 ipsec_sa_mgr_t public;
40
41 /**
42 * Installed SAs
43 */
44 linked_list_t *sas;
45
46 /**
47 * SPIs allocated using get_spi()
48 */
49 hashtable_t *allocated_spis;
50
51 /**
52 * Mutex used to synchronize access to the SA manager
53 */
54 mutex_t *mutex;
55
56 /**
57 * RNG used to generate SPIs
58 */
59 rng_t *rng;
60 };
61
62 /**
63 * Struct to keep track of locked IPsec SAs
64 */
65 typedef struct {
66
67 /**
68 * IPsec SA
69 */
70 ipsec_sa_t *sa;
71
72 /**
73 * Set if this SA is currently in use by a thread
74 */
75 bool locked;
76
77 /**
78 * Condvar used by threads to wait for this entry
79 */
80 condvar_t *condvar;
81
82 /**
83 * Number of threads waiting for this entry
84 */
85 u_int waiting_threads;
86
87 /**
88 * Set if this entry is awaiting deletion
89 */
90 bool awaits_deletion;
91
92 } ipsec_sa_entry_t;
93
94 /**
95 * Helper struct for expiration events
96 */
97 typedef struct {
98
99 /**
100 * IPsec SA manager
101 */
102 private_ipsec_sa_mgr_t *manager;
103
104 /**
105 * Entry that expired
106 */
107 ipsec_sa_entry_t *entry;
108
109 /**
110 * 0 if this is a hard expire, otherwise the offset in s (soft->hard)
111 */
112 u_int32_t hard_offset;
113
114 } ipsec_sa_expired_t;
115
116 /*
117 * Used for the hash table of allocated SPIs
118 */
119 static bool spi_equals(u_int32_t *spi, u_int32_t *other_spi)
120 {
121 return *spi == *other_spi;
122 }
123
124 static u_int spi_hash(u_int32_t *spi)
125 {
126 return chunk_hash(chunk_from_thing(*spi));
127 }
128
129 /**
130 * Create an SA entry
131 */
132 static ipsec_sa_entry_t *create_entry(ipsec_sa_t *sa)
133 {
134 ipsec_sa_entry_t *this;
135
136 INIT(this,
137 .condvar = condvar_create(CONDVAR_TYPE_DEFAULT),
138 .sa = sa,
139 );
140 return this;
141 }
142
143 /**
144 * Destroy an SA entry
145 */
146 static void destroy_entry(ipsec_sa_entry_t *entry)
147 {
148 entry->condvar->destroy(entry->condvar);
149 entry->sa->destroy(entry->sa);
150 free(entry);
151 }
152
153 /**
154 * Makes sure an entry is safe to remove
155 * Must be called with this->mutex held.
156 *
157 * @return TRUE if entry can be removed, FALSE if entry is already
158 * being removed by another thread
159 */
160 static bool wait_remove_entry(private_ipsec_sa_mgr_t *this,
161 ipsec_sa_entry_t *entry)
162 {
163 if (entry->awaits_deletion)
164 {
165 /* this will be deleted by another thread already */
166 return FALSE;
167 }
168 entry->awaits_deletion = TRUE;
169 while (entry->locked)
170 {
171 entry->condvar->wait(entry->condvar, this->mutex);
172 }
173 while (entry->waiting_threads > 0)
174 {
175 entry->condvar->broadcast(entry->condvar);
176 entry->condvar->wait(entry->condvar, this->mutex);
177 }
178 return TRUE;
179 }
180
181 /**
182 * Waits until an is available and then locks it.
183 * Must only be called with this->mutex held
184 */
185 static bool wait_for_entry(private_ipsec_sa_mgr_t *this,
186 ipsec_sa_entry_t *entry)
187 {
188 while (entry->locked && !entry->awaits_deletion)
189 {
190 entry->waiting_threads++;
191 entry->condvar->wait(entry->condvar, this->mutex);
192 entry->waiting_threads--;
193 }
194 if (entry->awaits_deletion)
195 {
196 /* others may still be waiting, */
197 entry->condvar->signal(entry->condvar);
198 return FALSE;
199 }
200 entry->locked = TRUE;
201 return TRUE;
202 }
203
204 /**
205 * Flushes all entries
206 * Must be called with this->mutex held.
207 */
208 static void flush_entries(private_ipsec_sa_mgr_t *this)
209 {
210 ipsec_sa_entry_t *current;
211 enumerator_t *enumerator;
212
213 DBG2(DBG_ESP, "flushing SAD");
214
215 enumerator = this->sas->create_enumerator(this->sas);
216 while (enumerator->enumerate(enumerator, (void**)&current))
217 {
218 if (wait_remove_entry(this, current))
219 {
220 this->sas->remove_at(this->sas, enumerator);
221 destroy_entry(current);
222 }
223 }
224 enumerator->destroy(enumerator);
225 }
226
227 /*
228 * Different match functions to find SAs in the linked list
229 */
230 static bool match_entry_by_ptr(ipsec_sa_entry_t *item, ipsec_sa_entry_t *entry)
231 {
232 return item == entry;
233 }
234
235 static bool match_entry_by_sa_ptr(ipsec_sa_entry_t *item, ipsec_sa_t *sa)
236 {
237 return item->sa == sa;
238 }
239
240 static bool match_entry_by_spi_inbound(ipsec_sa_entry_t *item, u_int32_t *spi,
241 bool *inbound)
242 {
243 return item->sa->get_spi(item->sa) == *spi &&
244 item->sa->is_inbound(item->sa) == *inbound;
245 }
246
247 static bool match_entry_by_spi_src_dst(ipsec_sa_entry_t *item, u_int32_t *spi,
248 host_t *src, host_t *dst)
249 {
250 return item->sa->match_by_spi_src_dst(item->sa, *spi, src, dst);
251 }
252
253 static bool match_entry_by_reqid_inbound(ipsec_sa_entry_t *item,
254 u_int32_t *reqid, bool *inbound)
255 {
256 return item->sa->match_by_reqid(item->sa, *reqid, *inbound);
257 }
258
259 static bool match_entry_by_spi_dst(ipsec_sa_entry_t *item, u_int32_t *spi,
260 host_t *dst)
261 {
262 return item->sa->match_by_spi_dst(item->sa, *spi, dst);
263 }
264
265 /**
266 * Remove an entry
267 */
268 static bool remove_entry(private_ipsec_sa_mgr_t *this, ipsec_sa_entry_t *entry)
269 {
270 ipsec_sa_entry_t *current;
271 enumerator_t *enumerator;
272 bool removed = FALSE;
273
274 enumerator = this->sas->create_enumerator(this->sas);
275 while (enumerator->enumerate(enumerator, (void**)&current))
276 {
277 if (current == entry)
278 {
279 if (wait_remove_entry(this, current))
280 {
281 this->sas->remove_at(this->sas, enumerator);
282 removed = TRUE;
283 }
284 break;
285 }
286 }
287 enumerator->destroy(enumerator);
288 return removed;
289 }
290
291 /**
292 * Callback for expiration events
293 */
294 static job_requeue_t sa_expired(ipsec_sa_expired_t *expired)
295 {
296 private_ipsec_sa_mgr_t *this = expired->manager;
297
298 this->mutex->lock(this->mutex);
299 if (this->sas->find_first(this->sas, (void*)match_entry_by_ptr,
300 NULL, expired->entry) == SUCCESS)
301 {
302 u_int32_t hard_offset = expired->hard_offset;
303 ipsec_sa_t *sa = expired->entry->sa;
304
305 ipsec->events->expire(ipsec->events, sa->get_reqid(sa),
306 sa->get_protocol(sa), sa->get_spi(sa),
307 hard_offset == 0);
308 if (hard_offset)
309 { /* soft limit reached, schedule hard expire */
310 expired->hard_offset = 0;
311 this->mutex->unlock(this->mutex);
312 return JOB_RESCHEDULE(hard_offset);
313 }
314 /* hard limit reached */
315 if (remove_entry(this, expired->entry))
316 {
317 destroy_entry(expired->entry);
318 }
319 }
320 this->mutex->unlock(this->mutex);
321 return JOB_REQUEUE_NONE;
322 }
323
324 /**
325 * Schedule a job to handle IPsec SA expiration
326 */
327 static void schedule_expiration(private_ipsec_sa_mgr_t *this,
328 ipsec_sa_entry_t *entry)
329 {
330 lifetime_cfg_t *lifetime = entry->sa->get_lifetime(entry->sa);
331 ipsec_sa_expired_t *expired;
332 callback_job_t *job;
333 u_int32_t timeout;
334
335 if (!lifetime->time.life)
336 { /* no expiration at all */
337 return;
338 }
339
340 INIT(expired,
341 .manager = this,
342 .entry = entry,
343 );
344
345 /* schedule a rekey first, a hard timeout will be scheduled then, if any */
346 expired->hard_offset = lifetime->time.life - lifetime->time.rekey;
347 timeout = lifetime->time.rekey;
348
349 if (lifetime->time.life <= lifetime->time.rekey ||
350 lifetime->time.rekey == 0)
351 { /* no rekey, schedule hard timeout */
352 expired->hard_offset = 0;
353 timeout = lifetime->time.life;
354 }
355
356 job = callback_job_create((callback_job_cb_t)sa_expired, expired,
357 (callback_job_cleanup_t)free, NULL);
358 lib->scheduler->schedule_job(lib->scheduler, (job_t*)job, timeout);
359 }
360
361 /**
362 * Remove all allocated SPIs
363 */
364 static void flush_allocated_spis(private_ipsec_sa_mgr_t *this)
365 {
366 enumerator_t *enumerator;
367 u_int32_t *current;
368
369 DBG2(DBG_ESP, "flushing allocated SPIs");
370 enumerator = this->allocated_spis->create_enumerator(this->allocated_spis);
371 while (enumerator->enumerate(enumerator, NULL, (void**)&current))
372 {
373 this->allocated_spis->remove_at(this->allocated_spis, enumerator);
374 DBG2(DBG_ESP, " removed allocated SPI %.8x", ntohl(*current));
375 free(current);
376 }
377 enumerator->destroy(enumerator);
378 }
379
380 /**
381 * Pre-allocate an SPI for an inbound SA
382 */
383 static bool allocate_spi(private_ipsec_sa_mgr_t *this, u_int32_t spi)
384 {
385 u_int32_t *spi_alloc;
386
387 if (this->allocated_spis->get(this->allocated_spis, &spi) ||
388 this->sas->find_first(this->sas, (void*)match_entry_by_spi_inbound,
389 NULL, &spi, TRUE) == SUCCESS)
390 {
391 return FALSE;
392 }
393 spi_alloc = malloc_thing(u_int32_t);
394 *spi_alloc = spi;
395 this->allocated_spis->put(this->allocated_spis, spi_alloc, spi_alloc);
396 return TRUE;
397 }
398
399 METHOD(ipsec_sa_mgr_t, get_spi, status_t,
400 private_ipsec_sa_mgr_t *this, host_t *src, host_t *dst, u_int8_t protocol,
401 u_int32_t reqid, u_int32_t *spi)
402 {
403 u_int32_t spi_new;
404
405 DBG2(DBG_ESP, "allocating SPI for reqid {%u}", reqid);
406
407 this->mutex->lock(this->mutex);
408 if (!this->rng)
409 {
410 this->rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK);
411 if (!this->rng)
412 {
413 this->mutex->unlock(this->mutex);
414 DBG1(DBG_ESP, "failed to create RNG for SPI generation");
415 return FAILED;
416 }
417 }
418
419 do
420 {
421 if (!this->rng->get_bytes(this->rng, sizeof(spi_new),
422 (u_int8_t*)&spi_new))
423 {
424 this->mutex->unlock(this->mutex);
425 DBG1(DBG_ESP, "failed to allocate SPI for reqid {%u}", reqid);
426 return FAILED;
427 }
428 /* make sure the SPI is valid (not in range 0-255) */
429 spi_new |= 0x00000100;
430 spi_new = htonl(spi_new);
431 }
432 while (!allocate_spi(this, spi_new));
433 this->mutex->unlock(this->mutex);
434
435 *spi = spi_new;
436
437 DBG2(DBG_ESP, "allocated SPI %.8x for reqid {%u}", ntohl(*spi), reqid);
438 return SUCCESS;
439 }
440
441 METHOD(ipsec_sa_mgr_t, add_sa, status_t,
442 private_ipsec_sa_mgr_t *this, host_t *src, host_t *dst, u_int32_t spi,
443 u_int8_t protocol, u_int32_t reqid, mark_t mark, u_int32_t tfc,
444 lifetime_cfg_t *lifetime, u_int16_t enc_alg, chunk_t enc_key,
445 u_int16_t int_alg, chunk_t int_key, ipsec_mode_t mode, u_int16_t ipcomp,
446 u_int16_t cpi, bool initiator, bool encap, bool esn, bool inbound,
447 traffic_selector_t *src_ts, traffic_selector_t *dst_ts)
448 {
449 ipsec_sa_entry_t *entry;
450 ipsec_sa_t *sa_new;
451
452 DBG2(DBG_ESP, "adding SAD entry with SPI %.8x and reqid {%u}",
453 ntohl(spi), reqid);
454 DBG2(DBG_ESP, " using encryption algorithm %N with key size %d",
455 encryption_algorithm_names, enc_alg, enc_key.len * 8);
456 DBG2(DBG_ESP, " using integrity algorithm %N with key size %d",
457 integrity_algorithm_names, int_alg, int_key.len * 8);
458
459 sa_new = ipsec_sa_create(spi, src, dst, protocol, reqid, mark, tfc,
460 lifetime, enc_alg, enc_key, int_alg, int_key, mode,
461 ipcomp, cpi, encap, esn, inbound, src_ts, dst_ts);
462 if (!sa_new)
463 {
464 DBG1(DBG_ESP, "failed to create SAD entry");
465 return FAILED;
466 }
467
468 this->mutex->lock(this->mutex);
469
470 if (inbound)
471 { /* remove any pre-allocated SPIs */
472 u_int32_t *spi_alloc;
473
474 spi_alloc = this->allocated_spis->remove(this->allocated_spis, &spi);
475 free(spi_alloc);
476 }
477
478 if (this->sas->find_first(this->sas, (void*)match_entry_by_spi_src_dst,
479 NULL, &spi, src, dst) == SUCCESS)
480 {
481 this->mutex->unlock(this->mutex);
482 DBG1(DBG_ESP, "failed to install SAD entry: already installed");
483 sa_new->destroy(sa_new);
484 return FAILED;
485 }
486
487 entry = create_entry(sa_new);
488 schedule_expiration(this, entry);
489 this->sas->insert_last(this->sas, entry);
490
491 this->mutex->unlock(this->mutex);
492 return SUCCESS;
493 }
494
495 METHOD(ipsec_sa_mgr_t, update_sa, status_t,
496 private_ipsec_sa_mgr_t *this, u_int32_t spi, u_int8_t protocol,
497 u_int16_t cpi, host_t *src, host_t *dst, host_t *new_src, host_t *new_dst,
498 bool encap, bool new_encap, mark_t mark)
499 {
500 ipsec_sa_entry_t *entry = NULL;
501
502 DBG2(DBG_ESP, "updating SAD entry with SPI %.8x from %#H..%#H to %#H..%#H",
503 ntohl(spi), src, dst, new_src, new_dst);
504
505 if (!new_encap)
506 {
507 DBG1(DBG_ESP, "failed to update SAD entry: can't deactivate UDP "
508 "encapsulation");
509 return NOT_SUPPORTED;
510 }
511
512 this->mutex->lock(this->mutex);
513 if (this->sas->find_first(this->sas, (void*)match_entry_by_spi_src_dst,
514 (void**)&entry, &spi, src, dst) == SUCCESS &&
515 wait_for_entry(this, entry))
516 {
517 entry->sa->set_source(entry->sa, new_src);
518 entry->sa->set_destination(entry->sa, new_dst);
519 /* checkin the entry */
520 entry->locked = FALSE;
521 entry->condvar->signal(entry->condvar);
522 }
523 this->mutex->unlock(this->mutex);
524
525 if (!entry)
526 {
527 DBG1(DBG_ESP, "failed to update SAD entry: not found");
528 return FAILED;
529 }
530 return SUCCESS;
531 }
532
533 METHOD(ipsec_sa_mgr_t, query_sa, status_t,
534 private_ipsec_sa_mgr_t *this, host_t *src, host_t *dst,
535 u_int32_t spi, u_int8_t protocol, mark_t mark,
536 u_int64_t *bytes, u_int64_t *packets, time_t *time)
537 {
538 ipsec_sa_entry_t *entry = NULL;
539
540 this->mutex->lock(this->mutex);
541 if (this->sas->find_first(this->sas, (void*)match_entry_by_spi_src_dst,
542 (void**)&entry, &spi, src, dst) == SUCCESS &&
543 wait_for_entry(this, entry))
544 {
545 entry->sa->get_usestats(entry->sa, bytes, packets, time);
546 /* checkin the entry */
547 entry->locked = FALSE;
548 entry->condvar->signal(entry->condvar);
549 }
550 this->mutex->unlock(this->mutex);
551
552 return entry ? SUCCESS : NOT_FOUND;
553 }
554
555 METHOD(ipsec_sa_mgr_t, del_sa, status_t,
556 private_ipsec_sa_mgr_t *this, host_t *src, host_t *dst, u_int32_t spi,
557 u_int8_t protocol, u_int16_t cpi, mark_t mark)
558 {
559 ipsec_sa_entry_t *current, *found = NULL;
560 enumerator_t *enumerator;
561
562 this->mutex->lock(this->mutex);
563 enumerator = this->sas->create_enumerator(this->sas);
564 while (enumerator->enumerate(enumerator, (void**)&current))
565 {
566 if (match_entry_by_spi_src_dst(current, &spi, src, dst))
567 {
568 if (wait_remove_entry(this, current))
569 {
570 this->sas->remove_at(this->sas, enumerator);
571 found = current;
572 }
573 break;
574 }
575 }
576 enumerator->destroy(enumerator);
577 this->mutex->unlock(this->mutex);
578
579 if (found)
580 {
581 DBG2(DBG_ESP, "deleted %sbound SAD entry with SPI %.8x",
582 found->sa->is_inbound(found->sa) ? "in" : "out", ntohl(spi));
583 destroy_entry(found);
584 return SUCCESS;
585 }
586 return FAILED;
587 }
588
589 METHOD(ipsec_sa_mgr_t, checkout_by_reqid, ipsec_sa_t*,
590 private_ipsec_sa_mgr_t *this, u_int32_t reqid, bool inbound)
591 {
592 ipsec_sa_entry_t *entry;
593 ipsec_sa_t *sa = NULL;
594
595 this->mutex->lock(this->mutex);
596 if (this->sas->find_first(this->sas, (void*)match_entry_by_reqid_inbound,
597 (void**)&entry, &reqid, &inbound) == SUCCESS &&
598 wait_for_entry(this, entry))
599 {
600 sa = entry->sa;
601 }
602 this->mutex->unlock(this->mutex);
603 return sa;
604 }
605
606 METHOD(ipsec_sa_mgr_t, checkout_by_spi, ipsec_sa_t*,
607 private_ipsec_sa_mgr_t *this, u_int32_t spi, host_t *dst)
608 {
609 ipsec_sa_entry_t *entry;
610 ipsec_sa_t *sa = NULL;
611
612 this->mutex->lock(this->mutex);
613 if (this->sas->find_first(this->sas, (void*)match_entry_by_spi_dst,
614 (void**)&entry, &spi, dst) == SUCCESS &&
615 wait_for_entry(this, entry))
616 {
617 sa = entry->sa;
618 }
619 this->mutex->unlock(this->mutex);
620 return sa;
621 }
622
623 METHOD(ipsec_sa_mgr_t, checkin, void,
624 private_ipsec_sa_mgr_t *this, ipsec_sa_t *sa)
625 {
626 ipsec_sa_entry_t *entry;
627
628 this->mutex->lock(this->mutex);
629 if (this->sas->find_first(this->sas, (void*)match_entry_by_sa_ptr,
630 (void**)&entry, sa) == SUCCESS)
631 {
632 if (entry->locked)
633 {
634 entry->locked = FALSE;
635 entry->condvar->signal(entry->condvar);
636 }
637 }
638 this->mutex->unlock(this->mutex);
639 }
640
641 METHOD(ipsec_sa_mgr_t, flush_sas, status_t,
642 private_ipsec_sa_mgr_t *this)
643 {
644 this->mutex->lock(this->mutex);
645 flush_entries(this);
646 this->mutex->unlock(this->mutex);
647 return SUCCESS;
648 }
649
650 METHOD(ipsec_sa_mgr_t, destroy, void,
651 private_ipsec_sa_mgr_t *this)
652 {
653 this->mutex->lock(this->mutex);
654 flush_entries(this);
655 flush_allocated_spis(this);
656 this->mutex->unlock(this->mutex);
657
658 this->allocated_spis->destroy(this->allocated_spis);
659 this->sas->destroy(this->sas);
660
661 this->mutex->destroy(this->mutex);
662 DESTROY_IF(this->rng);
663 free(this);
664 }
665
666 /**
667 * Described in header.
668 */
669 ipsec_sa_mgr_t *ipsec_sa_mgr_create()
670 {
671 private_ipsec_sa_mgr_t *this;
672
673 INIT(this,
674 .public = {
675 .get_spi = _get_spi,
676 .add_sa = _add_sa,
677 .update_sa = _update_sa,
678 .query_sa = _query_sa,
679 .del_sa = _del_sa,
680 .checkout_by_spi = _checkout_by_spi,
681 .checkout_by_reqid = _checkout_by_reqid,
682 .checkin = _checkin,
683 .flush_sas = _flush_sas,
684 .destroy = _destroy,
685 },
686 .sas = linked_list_create(),
687 .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
688 .allocated_spis = hashtable_create((hashtable_hash_t)spi_hash,
689 (hashtable_equals_t)spi_equals, 16),
690 );
691
692 return &this->public;
693 }