2 * Copyright (C) 2012 Tobias Brunner
3 * Copyright (C) 2012 Giuliano Grassi
4 * Copyright (C) 2012 Ralf Sager
5 * Hochschule fuer Technik Rapperswil
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>.
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
19 #include "ipsec_sa_mgr.h"
21 #include <utils/debug.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>
29 typedef struct private_ipsec_sa_mgr_t private_ipsec_sa_mgr_t
;
32 * Private additions to ipsec_sa_mgr_t.
34 struct private_ipsec_sa_mgr_t
{
37 * Public members of ipsec_sa_mgr_t.
39 ipsec_sa_mgr_t
public;
47 * SPIs allocated using get_spi()
49 hashtable_t
*allocated_spis
;
52 * Mutex used to synchronize access to the SA manager
57 * RNG used to generate SPIs
63 * Struct to keep track of locked IPsec SAs
73 * Set if this SA is currently in use by a thread
78 * Condvar used by threads to wait for this entry
83 * Number of threads waiting for this entry
85 u_int waiting_threads
;
88 * Set if this entry is awaiting deletion
95 * Helper struct for expiration events
102 private_ipsec_sa_mgr_t
*manager
;
107 ipsec_sa_entry_t
*entry
;
110 * 0 if this is a hard expire, otherwise the offset in s (soft->hard)
112 u_int32_t hard_offset
;
114 } ipsec_sa_expired_t
;
117 * Used for the hash table of allocated SPIs
119 static bool spi_equals(u_int32_t
*spi
, u_int32_t
*other_spi
)
121 return *spi
== *other_spi
;
124 static u_int
spi_hash(u_int32_t
*spi
)
126 return chunk_hash(chunk_from_thing(*spi
));
132 static ipsec_sa_entry_t
*create_entry(ipsec_sa_t
*sa
)
134 ipsec_sa_entry_t
*this;
137 .condvar
= condvar_create(CONDVAR_TYPE_DEFAULT
),
144 * Destroy an SA entry
146 static void destroy_entry(ipsec_sa_entry_t
*entry
)
148 entry
->condvar
->destroy(entry
->condvar
);
149 entry
->sa
->destroy(entry
->sa
);
154 * Makes sure an entry is safe to remove
155 * Must be called with this->mutex held.
157 * @return TRUE if entry can be removed, FALSE if entry is already
158 * being removed by another thread
160 static bool wait_remove_entry(private_ipsec_sa_mgr_t
*this,
161 ipsec_sa_entry_t
*entry
)
163 if (entry
->awaits_deletion
)
165 /* this will be deleted by another thread already */
168 entry
->awaits_deletion
= TRUE
;
169 while (entry
->locked
)
171 entry
->condvar
->wait(entry
->condvar
, this->mutex
);
173 while (entry
->waiting_threads
> 0)
175 entry
->condvar
->broadcast(entry
->condvar
);
176 entry
->condvar
->wait(entry
->condvar
, this->mutex
);
182 * Waits until an is available and then locks it.
183 * Must only be called with this->mutex held
185 static bool wait_for_entry(private_ipsec_sa_mgr_t
*this,
186 ipsec_sa_entry_t
*entry
)
188 while (entry
->locked
&& !entry
->awaits_deletion
)
190 entry
->waiting_threads
++;
191 entry
->condvar
->wait(entry
->condvar
, this->mutex
);
192 entry
->waiting_threads
--;
194 if (entry
->awaits_deletion
)
196 /* others may still be waiting, */
197 entry
->condvar
->signal(entry
->condvar
);
200 entry
->locked
= TRUE
;
205 * Flushes all entries
206 * Must be called with this->mutex held.
208 static void flush_entries(private_ipsec_sa_mgr_t
*this)
210 ipsec_sa_entry_t
*current
;
211 enumerator_t
*enumerator
;
213 DBG2(DBG_ESP
, "flushing SAD");
215 enumerator
= this->sas
->create_enumerator(this->sas
);
216 while (enumerator
->enumerate(enumerator
, (void**)¤t
))
218 if (wait_remove_entry(this, current
))
220 this->sas
->remove_at(this->sas
, enumerator
);
221 destroy_entry(current
);
224 enumerator
->destroy(enumerator
);
228 * Different match functions to find SAs in the linked list
230 static bool match_entry_by_ptr(ipsec_sa_entry_t
*item
, ipsec_sa_entry_t
*entry
)
232 return item
== entry
;
235 static bool match_entry_by_sa_ptr(ipsec_sa_entry_t
*item
, ipsec_sa_t
*sa
)
237 return item
->sa
== sa
;
240 static bool match_entry_by_spi_inbound(ipsec_sa_entry_t
*item
, u_int32_t
*spi
,
243 return item
->sa
->get_spi(item
->sa
) == *spi
&&
244 item
->sa
->is_inbound(item
->sa
) == *inbound
;
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
)
250 return item
->sa
->match_by_spi_src_dst(item
->sa
, *spi
, src
, dst
);
253 static bool match_entry_by_reqid_inbound(ipsec_sa_entry_t
*item
,
254 u_int32_t
*reqid
, bool *inbound
)
256 return item
->sa
->match_by_reqid(item
->sa
, *reqid
, *inbound
);
259 static bool match_entry_by_spi_dst(ipsec_sa_entry_t
*item
, u_int32_t
*spi
,
262 return item
->sa
->match_by_spi_dst(item
->sa
, *spi
, dst
);
268 static bool remove_entry(private_ipsec_sa_mgr_t
*this, ipsec_sa_entry_t
*entry
)
270 ipsec_sa_entry_t
*current
;
271 enumerator_t
*enumerator
;
272 bool removed
= FALSE
;
274 enumerator
= this->sas
->create_enumerator(this->sas
);
275 while (enumerator
->enumerate(enumerator
, (void**)¤t
))
277 if (current
== entry
)
279 if (wait_remove_entry(this, current
))
281 this->sas
->remove_at(this->sas
, enumerator
);
287 enumerator
->destroy(enumerator
);
292 * Callback for expiration events
294 static job_requeue_t
sa_expired(ipsec_sa_expired_t
*expired
)
296 private_ipsec_sa_mgr_t
*this = expired
->manager
;
298 this->mutex
->lock(this->mutex
);
299 if (this->sas
->find_first(this->sas
, (void*)match_entry_by_ptr
,
300 NULL
, expired
->entry
) == SUCCESS
)
302 u_int32_t hard_offset
= expired
->hard_offset
;
303 ipsec_sa_t
*sa
= expired
->entry
->sa
;
305 ipsec
->events
->expire(ipsec
->events
, sa
->get_reqid(sa
),
306 sa
->get_protocol(sa
), sa
->get_spi(sa
),
309 { /* soft limit reached, schedule hard expire */
310 expired
->hard_offset
= 0;
311 this->mutex
->unlock(this->mutex
);
312 return JOB_RESCHEDULE(hard_offset
);
314 /* hard limit reached */
315 if (remove_entry(this, expired
->entry
))
317 destroy_entry(expired
->entry
);
320 this->mutex
->unlock(this->mutex
);
321 return JOB_REQUEUE_NONE
;
325 * Schedule a job to handle IPsec SA expiration
327 static void schedule_expiration(private_ipsec_sa_mgr_t
*this,
328 ipsec_sa_entry_t
*entry
)
330 lifetime_cfg_t
*lifetime
= entry
->sa
->get_lifetime(entry
->sa
);
331 ipsec_sa_expired_t
*expired
;
340 /* schedule a rekey first, a hard timeout will be scheduled then, if any */
341 expired
->hard_offset
= lifetime
->time
.life
- lifetime
->time
.rekey
;
342 timeout
= lifetime
->time
.rekey
;
344 if (lifetime
->time
.life
<= lifetime
->time
.rekey
||
345 lifetime
->time
.rekey
== 0)
346 { /* no rekey, schedule hard timeout */
347 expired
->hard_offset
= 0;
348 timeout
= lifetime
->time
.life
;
351 job
= callback_job_create((callback_job_cb_t
)sa_expired
, expired
,
352 (callback_job_cleanup_t
)free
, NULL
);
353 lib
->scheduler
->schedule_job(lib
->scheduler
, (job_t
*)job
, timeout
);
357 * Remove all allocated SPIs
359 static void flush_allocated_spis(private_ipsec_sa_mgr_t
*this)
361 enumerator_t
*enumerator
;
364 DBG2(DBG_ESP
, "flushing allocated SPIs");
365 enumerator
= this->allocated_spis
->create_enumerator(this->allocated_spis
);
366 while (enumerator
->enumerate(enumerator
, NULL
, (void**)¤t
))
368 this->allocated_spis
->remove_at(this->allocated_spis
, enumerator
);
369 DBG2(DBG_ESP
, " removed allocated SPI %.8x", ntohl(*current
));
372 enumerator
->destroy(enumerator
);
376 * Pre-allocate an SPI for an inbound SA
378 static bool allocate_spi(private_ipsec_sa_mgr_t
*this, u_int32_t spi
)
380 u_int32_t
*spi_alloc
;
382 if (this->allocated_spis
->get(this->allocated_spis
, &spi
) ||
383 this->sas
->find_first(this->sas
, (void*)match_entry_by_spi_inbound
,
384 NULL
, &spi
, TRUE
) == SUCCESS
)
388 spi_alloc
= malloc_thing(u_int32_t
);
390 this->allocated_spis
->put(this->allocated_spis
, spi_alloc
, spi_alloc
);
394 METHOD(ipsec_sa_mgr_t
, get_spi
, status_t
,
395 private_ipsec_sa_mgr_t
*this, host_t
*src
, host_t
*dst
, u_int8_t protocol
,
396 u_int32_t reqid
, u_int32_t
*spi
)
400 DBG2(DBG_ESP
, "allocating SPI for reqid {%u}", reqid
);
402 this->mutex
->lock(this->mutex
);
405 this->rng
= lib
->crypto
->create_rng(lib
->crypto
, RNG_WEAK
);
408 this->mutex
->unlock(this->mutex
);
409 DBG1(DBG_ESP
, "failed to create RNG for SPI generation");
416 if (!this->rng
->get_bytes(this->rng
, sizeof(spi_new
),
417 (u_int8_t
*)&spi_new
))
419 this->mutex
->unlock(this->mutex
);
420 DBG1(DBG_ESP
, "failed to allocate SPI for reqid {%u}", reqid
);
423 /* make sure the SPI is valid (not in range 0-255) */
424 spi_new
|= 0x00000100;
425 spi_new
= htonl(spi_new
);
427 while (!allocate_spi(this, spi_new
));
428 this->mutex
->unlock(this->mutex
);
432 DBG2(DBG_ESP
, "allocated SPI %.8x for reqid {%u}", ntohl(*spi
), reqid
);
436 METHOD(ipsec_sa_mgr_t
, add_sa
, status_t
,
437 private_ipsec_sa_mgr_t
*this, host_t
*src
, host_t
*dst
, u_int32_t spi
,
438 u_int8_t protocol
, u_int32_t reqid
, mark_t mark
, u_int32_t tfc
,
439 lifetime_cfg_t
*lifetime
, u_int16_t enc_alg
, chunk_t enc_key
,
440 u_int16_t int_alg
, chunk_t int_key
, ipsec_mode_t mode
, u_int16_t ipcomp
,
441 u_int16_t cpi
, bool initiator
, bool encap
, bool esn
, bool inbound
,
442 traffic_selector_t
*src_ts
, traffic_selector_t
*dst_ts
)
444 ipsec_sa_entry_t
*entry
;
447 DBG2(DBG_ESP
, "adding SAD entry with SPI %.8x and reqid {%u}",
449 DBG2(DBG_ESP
, " using encryption algorithm %N with key size %d",
450 encryption_algorithm_names
, enc_alg
, enc_key
.len
* 8);
451 DBG2(DBG_ESP
, " using integrity algorithm %N with key size %d",
452 integrity_algorithm_names
, int_alg
, int_key
.len
* 8);
454 sa_new
= ipsec_sa_create(spi
, src
, dst
, protocol
, reqid
, mark
, tfc
,
455 lifetime
, enc_alg
, enc_key
, int_alg
, int_key
, mode
,
456 ipcomp
, cpi
, encap
, esn
, inbound
, src_ts
, dst_ts
);
459 DBG1(DBG_ESP
, "failed to create SAD entry");
463 this->mutex
->lock(this->mutex
);
466 { /* remove any pre-allocated SPIs */
467 u_int32_t
*spi_alloc
;
469 spi_alloc
= this->allocated_spis
->remove(this->allocated_spis
, &spi
);
473 if (this->sas
->find_first(this->sas
, (void*)match_entry_by_spi_src_dst
,
474 NULL
, &spi
, src
, dst
) == SUCCESS
)
476 this->mutex
->unlock(this->mutex
);
477 DBG1(DBG_ESP
, "failed to install SAD entry: already installed");
478 sa_new
->destroy(sa_new
);
482 entry
= create_entry(sa_new
);
483 schedule_expiration(this, entry
);
484 this->sas
->insert_last(this->sas
, entry
);
486 this->mutex
->unlock(this->mutex
);
490 METHOD(ipsec_sa_mgr_t
, update_sa
, status_t
,
491 private_ipsec_sa_mgr_t
*this, u_int32_t spi
, u_int8_t protocol
,
492 u_int16_t cpi
, host_t
*src
, host_t
*dst
, host_t
*new_src
, host_t
*new_dst
,
493 bool encap
, bool new_encap
, mark_t mark
)
495 ipsec_sa_entry_t
*entry
= NULL
;
497 DBG2(DBG_ESP
, "updating SAD entry with SPI %.8x from %#H..%#H to %#H..%#H",
498 ntohl(spi
), src
, dst
, new_src
, new_dst
);
502 DBG1(DBG_ESP
, "failed to update SAD entry: can't deactivate UDP "
504 return NOT_SUPPORTED
;
507 this->mutex
->lock(this->mutex
);
508 if (this->sas
->find_first(this->sas
, (void*)match_entry_by_spi_src_dst
,
509 (void**)&entry
, &spi
, src
, dst
) == SUCCESS
&&
510 wait_for_entry(this, entry
))
512 entry
->sa
->set_source(entry
->sa
, new_src
);
513 entry
->sa
->set_destination(entry
->sa
, new_dst
);
514 /* checkin the entry */
515 entry
->locked
= FALSE
;
516 entry
->condvar
->signal(entry
->condvar
);
518 this->mutex
->unlock(this->mutex
);
522 DBG1(DBG_ESP
, "failed to update SAD entry: not found");
528 METHOD(ipsec_sa_mgr_t
, del_sa
, status_t
,
529 private_ipsec_sa_mgr_t
*this, host_t
*src
, host_t
*dst
, u_int32_t spi
,
530 u_int8_t protocol
, u_int16_t cpi
, mark_t mark
)
532 ipsec_sa_entry_t
*current
, *found
= NULL
;
533 enumerator_t
*enumerator
;
535 this->mutex
->lock(this->mutex
);
536 enumerator
= this->sas
->create_enumerator(this->sas
);
537 while (enumerator
->enumerate(enumerator
, (void**)¤t
))
539 if (match_entry_by_spi_src_dst(current
, &spi
, src
, dst
))
541 if (wait_remove_entry(this, current
))
543 this->sas
->remove_at(this->sas
, enumerator
);
549 enumerator
->destroy(enumerator
);
550 this->mutex
->unlock(this->mutex
);
554 DBG2(DBG_ESP
, "deleted %sbound SAD entry with SPI %.8x",
555 found
->sa
->is_inbound(found
->sa
) ?
"in" : "out", ntohl(spi
));
556 destroy_entry(found
);
562 METHOD(ipsec_sa_mgr_t
, checkout_by_reqid
, ipsec_sa_t
*,
563 private_ipsec_sa_mgr_t
*this, u_int32_t reqid
, bool inbound
)
565 ipsec_sa_entry_t
*entry
;
566 ipsec_sa_t
*sa
= NULL
;
568 this->mutex
->lock(this->mutex
);
569 if (this->sas
->find_first(this->sas
, (void*)match_entry_by_reqid_inbound
,
570 (void**)&entry
, &reqid
, &inbound
) == SUCCESS
&&
571 wait_for_entry(this, entry
))
575 this->mutex
->unlock(this->mutex
);
579 METHOD(ipsec_sa_mgr_t
, checkout_by_spi
, ipsec_sa_t
*,
580 private_ipsec_sa_mgr_t
*this, u_int32_t spi
, host_t
*dst
)
582 ipsec_sa_entry_t
*entry
;
583 ipsec_sa_t
*sa
= NULL
;
585 this->mutex
->lock(this->mutex
);
586 if (this->sas
->find_first(this->sas
, (void*)match_entry_by_spi_dst
,
587 (void**)&entry
, &spi
, dst
) == SUCCESS
&&
588 wait_for_entry(this, entry
))
592 this->mutex
->unlock(this->mutex
);
596 METHOD(ipsec_sa_mgr_t
, checkin
, void,
597 private_ipsec_sa_mgr_t
*this, ipsec_sa_t
*sa
)
599 ipsec_sa_entry_t
*entry
;
601 this->mutex
->lock(this->mutex
);
602 if (this->sas
->find_first(this->sas
, (void*)match_entry_by_sa_ptr
,
603 (void**)&entry
, sa
) == SUCCESS
)
607 entry
->locked
= FALSE
;
608 entry
->condvar
->signal(entry
->condvar
);
611 this->mutex
->unlock(this->mutex
);
614 METHOD(ipsec_sa_mgr_t
, flush_sas
, status_t
,
615 private_ipsec_sa_mgr_t
*this)
617 this->mutex
->lock(this->mutex
);
619 this->mutex
->unlock(this->mutex
);
623 METHOD(ipsec_sa_mgr_t
, destroy
, void,
624 private_ipsec_sa_mgr_t
*this)
626 this->mutex
->lock(this->mutex
);
628 flush_allocated_spis(this);
629 this->mutex
->unlock(this->mutex
);
631 this->allocated_spis
->destroy(this->allocated_spis
);
632 this->sas
->destroy(this->sas
);
634 this->mutex
->destroy(this->mutex
);
635 DESTROY_IF(this->rng
);
640 * Described in header.
642 ipsec_sa_mgr_t
*ipsec_sa_mgr_create()
644 private_ipsec_sa_mgr_t
*this;
650 .update_sa
= _update_sa
,
652 .checkout_by_spi
= _checkout_by_spi
,
653 .checkout_by_reqid
= _checkout_by_reqid
,
655 .flush_sas
= _flush_sas
,
658 .sas
= linked_list_create(),
659 .mutex
= mutex_create(MUTEX_TYPE_DEFAULT
),
660 .allocated_spis
= hashtable_create((hashtable_hash_t
)spi_hash
,
661 (hashtable_equals_t
)spi_equals
, 16),
664 return &this->public;