ike-sa-manager: Remove superfluous assignment
[strongswan.git] / src / libcharon / sa / ike_sa_manager.c
index 7366420..6bd49a0 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Copyright (C) 2005-2011 Martin Willi
  * Copyright (C) 2011 revosec AG
- * Copyright (C) 2008-2012 Tobias Brunner
+ * Copyright (C) 2008-2016 Tobias Brunner
  * Copyright (C) 2005 Jan Hutter
  * Hochschule fuer Technik Rapperswil
  *
  */
 
 #include <string.h>
+#include <inttypes.h>
 
 #include "ike_sa_manager.h"
 
 #include <daemon.h>
 #include <sa/ike_sa_id.h>
 #include <bus/bus.h>
+#include <threading/thread.h>
 #include <threading/condvar.h>
 #include <threading/mutex.h>
 #include <threading/rwlock.h>
@@ -57,9 +59,9 @@ struct entry_t {
        condvar_t *condvar;
 
        /**
-        * Is this ike_sa currently checked out?
+        * Thread by which this IKE_SA is currently checked out, if any
         */
-       bool checked_out;
+       thread_t *checked_out;
 
        /**
         * Does this SA drives out new threads?
@@ -111,7 +113,7 @@ struct entry_t {
        /**
         * message ID or hash of currently processing message, -1 if none
         */
-       u_int32_t processing;
+       uint32_t processing;
 };
 
 /**
@@ -157,6 +159,8 @@ static bool entry_match_by_id(entry_t *entry, ike_sa_id_t *id)
        }
        if ((id->get_responder_spi(id) == 0 ||
                 entry->ike_sa_id->get_responder_spi(entry->ike_sa_id) == 0) &&
+               (id->get_ike_version(id) == IKEV1_MAJOR_VERSION ||
+                id->is_initiator(id) == entry->ike_sa_id->is_initiator(entry->ike_sa_id)) &&
                id->get_initiator_spi(id) == entry->ike_sa_id->get_initiator_spi(entry->ike_sa_id))
        {
                /* this is TRUE for IKE_SAs that we initiated but have not yet received a response */
@@ -204,6 +208,9 @@ struct half_open_t {
 
        /** the number of half-open IKE_SAs with that host */
        u_int count;
+
+       /** the number of half-open IKE_SAs we responded to with that host */
+       u_int count_responder;
 };
 
 /**
@@ -258,7 +265,7 @@ struct init_hash_t {
        chunk_t hash;
 
        /** our SPI allocated for the IKE_SA based on this message */
-       u_int64_t our_spi;
+       uint64_t our_spi;
 };
 
 typedef struct segment_t segment_t;
@@ -354,6 +361,16 @@ struct private_ike_sa_manager_t {
        shareable_segment_t *half_open_segments;
 
        /**
+        * Total number of half-open IKE_SAs.
+        */
+       refcount_t half_open_count;
+
+       /**
+        * Total number of half-open IKE_SAs as responder.
+        */
+       refcount_t half_open_count_responder;
+
+       /**
         * Hash table with connected_peers_t objects.
         */
        table_item_t **connected_peers_table;
@@ -379,9 +396,17 @@ struct private_ike_sa_manager_t {
        rng_t *rng;
 
        /**
-        * SHA1 hasher for IKE_SA_INIT retransmit detection
+        * Registered callback for IKE SPIs
+        */
+       struct {
+               spi_cb_t cb;
+               void *data;
+       } spi_cb;
+
+       /**
+        * Lock to access the RNG instance and the callback
         */
-       hasher_t *hasher;
+       rwlock_t *spi_lock;
 
        /**
         * reuse existing IKE_SAs in checkout_by_config
@@ -730,9 +755,11 @@ static void put_half_open(private_ike_sa_manager_t *this, entry_t *entry)
        table_item_t *item;
        u_int row, segment;
        rwlock_t *lock;
+       ike_sa_id_t *ike_id;
        half_open_t *half_open;
        chunk_t addr;
 
+       ike_id = entry->ike_sa_id;
        addr = entry->other->get_address(entry->other);
        row = chunk_hash(addr) & this->table_mask;
        segment = row & this->segment_mask;
@@ -745,7 +772,6 @@ static void put_half_open(private_ike_sa_manager_t *this, entry_t *entry)
 
                if (chunk_equals(addr, half_open->other))
                {
-                       half_open->count++;
                        break;
                }
                item = item->next;
@@ -755,7 +781,6 @@ static void put_half_open(private_ike_sa_manager_t *this, entry_t *entry)
        {
                INIT(half_open,
                        .other = chunk_clone(addr),
-                       .count = 1,
                );
                INIT(item,
                        .value = half_open,
@@ -763,6 +788,13 @@ static void put_half_open(private_ike_sa_manager_t *this, entry_t *entry)
                );
                this->half_open_table[row] = item;
        }
+       half_open->count++;
+       ref_get(&this->half_open_count);
+       if (!ike_id->is_initiator(ike_id))
+       {
+               half_open->count_responder++;
+               ref_get(&this->half_open_count_responder);
+       }
        this->half_open_segments[segment].count++;
        lock->unlock(lock);
 }
@@ -775,8 +807,10 @@ static void remove_half_open(private_ike_sa_manager_t *this, entry_t *entry)
        table_item_t *item, *prev = NULL;
        u_int row, segment;
        rwlock_t *lock;
+       ike_sa_id_t *ike_id;
        chunk_t addr;
 
+       ike_id = entry->ike_sa_id;
        addr = entry->other->get_address(entry->other);
        row = chunk_hash(addr) & this->table_mask;
        segment = row & this->segment_mask;
@@ -789,6 +823,12 @@ static void remove_half_open(private_ike_sa_manager_t *this, entry_t *entry)
 
                if (chunk_equals(addr, half_open->other))
                {
+                       if (!ike_id->is_initiator(ike_id))
+                       {
+                               half_open->count_responder--;
+                               ignore_result(ref_put(&this->half_open_count_responder));
+                       }
+                       ignore_result(ref_put(&this->half_open_count));
                        if (--half_open->count == 0)
                        {
                                if (prev)
@@ -937,16 +977,22 @@ static void remove_connected_peers(private_ike_sa_manager_t *this, entry_t *entr
 /**
  * Get a random SPI for new IKE_SAs
  */
-static u_int64_t get_spi(private_ike_sa_manager_t *this)
+static uint64_t get_spi(private_ike_sa_manager_t *this)
 {
-       u_int64_t spi;
+       uint64_t spi;
 
-       if (this->rng &&
-               this->rng->get_bytes(this->rng, sizeof(spi), (u_int8_t*)&spi))
+       this->spi_lock->read_lock(this->spi_lock);
+       if (this->spi_cb.cb)
+       {
+               spi = this->spi_cb.cb(this->spi_cb.data);
+       }
+       else if (!this->rng ||
+                        !this->rng->get_bytes(this->rng, sizeof(spi), (uint8_t*)&spi))
        {
-               return spi;
+               spi = 0;
        }
-       return 0;
+       this->spi_lock->unlock(this->spi_lock);
+       return spi;
 }
 
 /**
@@ -955,49 +1001,39 @@ static u_int64_t get_spi(private_ike_sa_manager_t *this)
  *
  * @returns TRUE on success
  */
-static bool get_init_hash(private_ike_sa_manager_t *this, message_t *message,
-                                                 chunk_t *hash)
+static bool get_init_hash(hasher_t *hasher, message_t *message, chunk_t *hash)
 {
        host_t *src;
 
-       if (!this->hasher)
-       {       /* this might be the case when flush() has been called */
-               return FALSE;
-       }
-       if (message->get_first_payload_type(message) == FRAGMENT_V1)
+       if (message->get_first_payload_type(message) == PLV1_FRAGMENT)
        {       /* only hash the source IP, port and SPI for fragmented init messages */
-               u_int16_t port;
-               u_int64_t spi;
+               uint16_t port;
+               uint64_t spi;
 
                src = message->get_source(message);
-               if (!this->hasher->allocate_hash(this->hasher,
-                                                                                src->get_address(src), NULL))
+               if (!hasher->allocate_hash(hasher, src->get_address(src), NULL))
                {
                        return FALSE;
                }
                port = src->get_port(src);
-               if (!this->hasher->allocate_hash(this->hasher,
-                                                                                chunk_from_thing(port), NULL))
+               if (!hasher->allocate_hash(hasher, chunk_from_thing(port), NULL))
                {
                        return FALSE;
                }
                spi = message->get_initiator_spi(message);
-               return this->hasher->allocate_hash(this->hasher,
-                                                                                  chunk_from_thing(spi), hash);
+               return hasher->allocate_hash(hasher, chunk_from_thing(spi), hash);
        }
        if (message->get_exchange_type(message) == ID_PROT)
        {       /* include the source for Main Mode as the hash will be the same if
                 * SPIs are reused by two initiators that use the same proposal */
                src = message->get_source(message);
 
-               if (!this->hasher->allocate_hash(this->hasher,
-                                                                                src->get_address(src), NULL))
+               if (!hasher->allocate_hash(hasher, src->get_address(src), NULL))
                {
                        return FALSE;
                }
        }
-       return this->hasher->allocate_hash(this->hasher,
-                                                                          message->get_packet_data(message), hash);
+       return hasher->allocate_hash(hasher, message->get_packet_data(message), hash);
 }
 
 /**
@@ -1014,13 +1050,13 @@ static bool get_init_hash(private_ike_sa_manager_t *this, message_t *message,
  *                     FAILED if the SPI allocation failed
  */
 static status_t check_and_put_init_hash(private_ike_sa_manager_t *this,
-                                                                               chunk_t init_hash, u_int64_t *our_spi)
+                                                                               chunk_t init_hash, uint64_t *our_spi)
 {
        table_item_t *item;
        u_int row, segment;
        mutex_t *mutex;
        init_hash_t *init;
-       u_int64_t spi;
+       uint64_t spi;
 
        row = chunk_hash(init_hash) & this->table_mask;
        segment = row & this->segment_mask;
@@ -1108,13 +1144,16 @@ METHOD(ike_sa_manager_t, checkout, ike_sa_t*,
        entry_t *entry;
        u_int segment;
 
-       DBG2(DBG_MGR, "checkout IKE_SA");
+       DBG2(DBG_MGR, "checkout %N SA with SPIs %.16"PRIx64"_i %.16"PRIx64"_r",
+                ike_version_names, ike_sa_id->get_ike_version(ike_sa_id),
+                be64toh(ike_sa_id->get_initiator_spi(ike_sa_id)),
+                be64toh(ike_sa_id->get_responder_spi(ike_sa_id)));
 
        if (get_entry_by_id(this, ike_sa_id, &entry, &segment) == SUCCESS)
        {
                if (wait_for_entry(this, entry, segment))
                {
-                       entry->checked_out = TRUE;
+                       entry->checked_out = thread_current();
                        ike_sa = entry->ike_sa;
                        DBG2(DBG_MGR, "IKE_SA %s[%u] successfully checked out",
                                        ike_sa->get_name(ike_sa), ike_sa->get_unique_id(ike_sa));
@@ -1122,6 +1161,11 @@ METHOD(ike_sa_manager_t, checkout, ike_sa_t*,
                unlock_single_segment(this, segment);
        }
        charon->bus->set_sa(charon->bus, ike_sa);
+
+       if (!ike_sa)
+       {
+               DBG2(DBG_MGR, "IKE_SA checkout not successful");
+       }
        return ike_sa;
 }
 
@@ -1130,8 +1174,8 @@ METHOD(ike_sa_manager_t, checkout_new, ike_sa_t*,
 {
        ike_sa_id_t *ike_sa_id;
        ike_sa_t *ike_sa;
-       u_int8_t ike_version;
-       u_int64_t spi;
+       uint8_t ike_version;
+       uint64_t spi;
 
        ike_version = version == IKEV1 ? IKEV1_MAJOR_VERSION : IKEV2_MAJOR_VERSION;
 
@@ -1164,13 +1208,17 @@ METHOD(ike_sa_manager_t, checkout_new, ike_sa_t*,
 /**
  * Get the message ID or message hash to detect early retransmissions
  */
-static u_int32_t get_message_id_or_hash(message_t *message)
+static uint32_t get_message_id_or_hash(message_t *message)
 {
-       /* Use the message ID, or the message hash in IKEv1 Main/Aggressive mode */
-       if (message->get_major_version(message) == IKEV1_MAJOR_VERSION &&
-               message->get_message_id(message) == 0)
+       if (message->get_major_version(message) == IKEV1_MAJOR_VERSION)
        {
-               return chunk_hash(message->get_packet_data(message));
+               /* Use a hash for IKEv1 Phase 1, where we don't have a MID, and Quick
+                * Mode, where all three messages use the same message ID */
+               if (message->get_message_id(message) == 0 ||
+                       message->get_exchange_type(message) == QUICK_MODE)
+               {
+                       return chunk_hash(message->get_packet_data(message));
+               }
        }
        return message->get_message_id(message);
 }
@@ -1190,9 +1238,13 @@ METHOD(ike_sa_manager_t, checkout_by_message, ike_sa_t*,
        id = id->clone(id);
        id->switch_initiator(id);
 
-       DBG2(DBG_MGR, "checkout IKE_SA by message");
+       DBG2(DBG_MGR, "checkout %N SA by message with SPIs %.16"PRIx64"_i "
+                "%.16"PRIx64"_r", ike_version_names, id->get_ike_version(id),
+                be64toh(id->get_initiator_spi(id)),
+                be64toh(id->get_responder_spi(id)));
 
-       if (id->get_responder_spi(id) == 0)
+       if (id->get_responder_spi(id) == 0 &&
+               message->get_message_id(message) == 0)
        {
                if (message->get_major_version(message) == IKEV2_MAJOR_VERSION)
                {
@@ -1220,15 +1272,19 @@ METHOD(ike_sa_manager_t, checkout_by_message, ike_sa_t*,
 
        if (is_init)
        {
-               u_int64_t our_spi;
+               hasher_t *hasher;
+               uint64_t our_spi;
                chunk_t hash;
 
-               if (!get_init_hash(this, message, &hash))
+               hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA1);
+               if (!hasher || !get_init_hash(hasher, message, &hash))
                {
                        DBG1(DBG_MGR, "ignoring message, failed to hash message");
+                       DESTROY_IF(hasher);
                        id->destroy(id);
-                       return NULL;
+                       goto out;
                }
+               hasher->destroy(hasher);
 
                /* ensure this is not a retransmit of an already handled init message */
                switch (check_and_put_init_hash(this, hash, &our_spi))
@@ -1245,20 +1301,17 @@ METHOD(ike_sa_manager_t, checkout_by_message, ike_sa_t*,
                                                entry = entry_create();
                                                entry->ike_sa = ike_sa;
                                                entry->ike_sa_id = id;
+                                               entry->processing = get_message_id_or_hash(message);
+                                               entry->init_hash = hash;
 
                                                segment = put_entry(this, entry);
-                                               entry->checked_out = TRUE;
+                                               entry->checked_out = thread_current();
                                                unlock_single_segment(this, segment);
 
-                                               entry->processing = get_message_id_or_hash(message);
-                                               entry->init_hash = hash;
-
                                                DBG2(DBG_MGR, "created IKE_SA %s[%u]",
                                                         ike_sa->get_name(ike_sa),
                                                         ike_sa->get_unique_id(ike_sa));
-
-                                               charon->bus->set_sa(charon->bus, ike_sa);
-                                               return ike_sa;
+                                               goto out;
                                        }
                                        else
                                        {
@@ -1274,14 +1327,14 @@ METHOD(ike_sa_manager_t, checkout_by_message, ike_sa_t*,
                                remove_init_hash(this, hash);
                                chunk_free(&hash);
                                id->destroy(id);
-                               return NULL;
+                               goto out;
                        }
                        case FAILED:
                        {       /* we failed to allocate an SPI */
                                chunk_free(&hash);
                                id->destroy(id);
                                DBG1(DBG_MGR, "ignoring message, failed to allocate SPI");
-                               return NULL;
+                               goto out;
                        }
                        case ALREADY_DONE:
                        default:
@@ -1305,9 +1358,10 @@ METHOD(ike_sa_manager_t, checkout_by_message, ike_sa_t*,
                        ike_sa_id_t *ike_id;
 
                        ike_id = entry->ike_sa->get_id(entry->ike_sa);
-                       entry->checked_out = TRUE;
-                       if (message->get_first_payload_type(message) != FRAGMENT_V1)
-                       {
+                       entry->checked_out = thread_current();
+                       if (message->get_first_payload_type(message) != PLV1_FRAGMENT &&
+                               message->get_first_payload_type(message) != PLV2_FRAGMENT)
+                       {       /* TODO-FRAG: this fails if there are unencrypted payloads */
                                entry->processing = get_message_id_or_hash(message);
                        }
                        if (ike_id->get_responder_spi(ike_id) == 0)
@@ -1325,7 +1379,13 @@ METHOD(ike_sa_manager_t, checkout_by_message, ike_sa_t*,
                charon->bus->alert(charon->bus, ALERT_INVALID_IKE_SPI, message);
        }
        id->destroy(id);
+
+out:
        charon->bus->set_sa(charon->bus, ike_sa);
+       if (!ike_sa)
+       {
+               DBG2(DBG_MGR, "IKE_SA checkout not successful");
+       }
        return ike_sa;
 }
 
@@ -1341,11 +1401,11 @@ METHOD(ike_sa_manager_t, checkout_by_config, ike_sa_t*,
 
        DBG2(DBG_MGR, "checkout IKE_SA by config");
 
-       if (!this->reuse_ikesa)
-       {       /* IKE_SA reuse disable by config */
+       if (!this->reuse_ikesa && peer_cfg->get_ike_version(peer_cfg) != IKEV1)
+       {       /* IKE_SA reuse disabled by config (not possible for IKEv1) */
                ike_sa = checkout_new(this, peer_cfg->get_ike_version(peer_cfg), TRUE);
                charon->bus->set_sa(charon->bus, ike_sa);
-               return ike_sa;
+               goto out;
        }
 
        enumerator = create_table_enumerator(this);
@@ -1355,8 +1415,10 @@ METHOD(ike_sa_manager_t, checkout_by_config, ike_sa_t*,
                {
                        continue;
                }
-               if (entry->ike_sa->get_state(entry->ike_sa) == IKE_DELETING)
-               {       /* skip IKE_SAs which are not usable */
+               if (entry->ike_sa->get_state(entry->ike_sa) == IKE_DELETING ||
+                       entry->ike_sa->get_state(entry->ike_sa) == IKE_REKEYED)
+               {       /* skip IKE_SAs which are not usable, wake other waiting threads */
+                       entry->condvar->signal(entry->condvar);
                        continue;
                }
 
@@ -1366,7 +1428,7 @@ METHOD(ike_sa_manager_t, checkout_by_config, ike_sa_t*,
                        current_ike = current_peer->get_ike_cfg(current_peer);
                        if (current_ike->equals(current_ike, peer_cfg->get_ike_cfg(peer_cfg)))
                        {
-                               entry->checked_out = TRUE;
+                               entry->checked_out = thread_current();
                                ike_sa = entry->ike_sa;
                                DBG2(DBG_MGR, "found existing IKE_SA %u with a '%s' config",
                                                ike_sa->get_unique_id(ike_sa),
@@ -1374,6 +1436,8 @@ METHOD(ike_sa_manager_t, checkout_by_config, ike_sa_t*,
                                break;
                        }
                }
+               /* other threads might be waiting for this entry */
+               entry->condvar->signal(entry->condvar);
        }
        enumerator->destroy(enumerator);
 
@@ -1382,58 +1446,51 @@ METHOD(ike_sa_manager_t, checkout_by_config, ike_sa_t*,
                ike_sa = checkout_new(this, peer_cfg->get_ike_version(peer_cfg), TRUE);
        }
        charon->bus->set_sa(charon->bus, ike_sa);
+
+out:
+       if (!ike_sa)
+       {
+               DBG2(DBG_MGR, "IKE_SA checkout not successful");
+       }
        return ike_sa;
 }
 
 METHOD(ike_sa_manager_t, checkout_by_id, ike_sa_t*,
-       private_ike_sa_manager_t *this, u_int32_t id, bool child)
+       private_ike_sa_manager_t *this, uint32_t id)
 {
-       enumerator_t *enumerator, *children;
+       enumerator_t *enumerator;
        entry_t *entry;
        ike_sa_t *ike_sa = NULL;
-       child_sa_t *child_sa;
        u_int segment;
 
-       DBG2(DBG_MGR, "checkout IKE_SA by ID");
+       DBG2(DBG_MGR, "checkout IKE_SA by unique ID %u", id);
 
        enumerator = create_table_enumerator(this);
        while (enumerator->enumerate(enumerator, &entry, &segment))
        {
                if (wait_for_entry(this, entry, segment))
                {
-                       /* look for a child with such a reqid ... */
-                       if (child)
-                       {
-                               children = entry->ike_sa->create_child_sa_enumerator(entry->ike_sa);
-                               while (children->enumerate(children, (void**)&child_sa))
-                               {
-                                       if (child_sa->get_reqid(child_sa) == id)
-                                       {
-                                               ike_sa = entry->ike_sa;
-                                               break;
-                                       }
-                               }
-                               children->destroy(children);
-                       }
-                       else /* ... or for a IKE_SA with such a unique id */
+                       if (entry->ike_sa->get_unique_id(entry->ike_sa) == id)
                        {
-                               if (entry->ike_sa->get_unique_id(entry->ike_sa) == id)
-                               {
-                                       ike_sa = entry->ike_sa;
-                               }
-                       }
-                       /* got one, return */
-                       if (ike_sa)
-                       {
-                               entry->checked_out = TRUE;
-                               DBG2(DBG_MGR, "IKE_SA %s[%u] successfully checked out",
-                                               ike_sa->get_name(ike_sa), ike_sa->get_unique_id(ike_sa));
+                               ike_sa = entry->ike_sa;
+                               entry->checked_out = thread_current();
                                break;
                        }
+                       /* other threads might be waiting for this entry */
+                       entry->condvar->signal(entry->condvar);
                }
        }
        enumerator->destroy(enumerator);
 
+       if (ike_sa)
+       {
+               DBG2(DBG_MGR, "IKE_SA %s[%u] successfully checked out",
+                        ike_sa->get_name(ike_sa), ike_sa->get_unique_id(ike_sa));
+       }
+       else
+       {
+               DBG2(DBG_MGR, "IKE_SA checkout not successful");
+       }
        charon->bus->set_sa(charon->bus, ike_sa);
        return ike_sa;
 }
@@ -1447,6 +1504,8 @@ METHOD(ike_sa_manager_t, checkout_by_name, ike_sa_t*,
        child_sa_t *child_sa;
        u_int segment;
 
+       DBG2(DBG_MGR, "checkout IKE_SA by%s name '%s'", child ? " child" : "", name);
+
        enumerator = create_table_enumerator(this);
        while (enumerator->enumerate(enumerator, &entry, &segment))
        {
@@ -1476,16 +1535,23 @@ METHOD(ike_sa_manager_t, checkout_by_name, ike_sa_t*,
                        /* got one, return */
                        if (ike_sa)
                        {
-                               entry->checked_out = TRUE;
+                               entry->checked_out = thread_current();
                                DBG2(DBG_MGR, "IKE_SA %s[%u] successfully checked out",
                                                ike_sa->get_name(ike_sa), ike_sa->get_unique_id(ike_sa));
                                break;
                        }
+                       /* other threads might be waiting for this entry */
+                       entry->condvar->signal(entry->condvar);
                }
        }
        enumerator->destroy(enumerator);
 
        charon->bus->set_sa(charon->bus, ike_sa);
+
+       if (!ike_sa)
+       {
+               DBG2(DBG_MGR, "IKE_SA checkout not successful");
+       }
        return ike_sa;
 }
 
@@ -1566,7 +1632,7 @@ METHOD(ike_sa_manager_t, checkin, void,
                /* ike_sa_id must be updated */
                entry->ike_sa_id->replace_values(entry->ike_sa_id, ike_sa->get_id(ike_sa));
                /* signal waiting threads */
-               entry->checked_out = FALSE;
+               entry->checked_out = NULL;
                entry->processing = -1;
                /* check if this SA is half-open */
                if (entry->half_open && ike_sa->get_state(ike_sa) != IKE_CONNECTING)
@@ -1584,7 +1650,6 @@ METHOD(ike_sa_manager_t, checkin, void,
                        put_half_open(this, entry);
                }
                else if (!entry->half_open &&
-                                !entry->ike_sa_id->is_initiator(entry->ike_sa_id) &&
                                 ike_sa->get_state(ike_sa) == IKE_CONNECTING)
                {
                        /* this is a new half-open SA */
@@ -1592,7 +1657,6 @@ METHOD(ike_sa_manager_t, checkin, void,
                        entry->other = other->clone(other);
                        put_half_open(this, entry);
                }
-               DBG2(DBG_MGR, "check-in of IKE_SA successful.");
                entry->condvar->signal(entry->condvar);
        }
        else
@@ -1600,8 +1664,15 @@ METHOD(ike_sa_manager_t, checkin, void,
                entry = entry_create();
                entry->ike_sa_id = ike_sa_id->clone(ike_sa_id);
                entry->ike_sa = ike_sa;
+               if (ike_sa->get_state(ike_sa) == IKE_CONNECTING)
+               {
+                       entry->half_open = TRUE;
+                       entry->other = other->clone(other);
+                       put_half_open(this, entry);
+               }
                segment = put_entry(this, entry);
        }
+       DBG2(DBG_MGR, "checkin of IKE_SA successful");
 
        /* apply identities for duplicate test */
        if ((ike_sa->get_state(ike_sa) == IKE_ESTABLISHED ||
@@ -1614,8 +1685,27 @@ METHOD(ike_sa_manager_t, checkin, void,
                         * delete any existing IKE_SAs with that peer. */
                        if (ike_sa->has_condition(ike_sa, COND_INIT_CONTACT_SEEN))
                        {
+                               /* We can't hold the segment locked while checking the
+                                * uniqueness as this could lead to deadlocks.  We mark the
+                                * entry as checked out while we release the lock so no other
+                                * thread can acquire it.  Since it is not yet in the list of
+                                * connected peers that will not cause a deadlock as no other
+                                * caller of check_unqiueness() will try to check out this SA */
+                               entry->checked_out = thread_current();
+                               unlock_single_segment(this, segment);
+
                                this->public.check_uniqueness(&this->public, ike_sa, TRUE);
                                ike_sa->set_condition(ike_sa, COND_INIT_CONTACT_SEEN, FALSE);
+
+                               /* The entry could have been modified in the mean time, e.g.
+                                * because another SA was added/removed next to it or another
+                                * thread is waiting, but it should still exist, so there is no
+                                * need for a lookup via get_entry_by... */
+                               lock_single_segment(this, segment);
+                               entry->checked_out = NULL;
+                               /* We already signaled waiting threads above, we have to do that
+                                * again after checking the SA out and back in again. */
+                               entry->condvar->signal(entry->condvar);
                        }
                }
 
@@ -1655,8 +1745,8 @@ METHOD(ike_sa_manager_t, checkin_and_destroy, void,
                if (entry->driveout_waiting_threads && entry->driveout_new_threads)
                {       /* it looks like flush() has been called and the SA is being deleted
                         * anyway, just check it in */
-                       DBG2(DBG_MGR, "ignored check-in and destroy of IKE_SA during shutdown");
-                       entry->checked_out = FALSE;
+                       DBG2(DBG_MGR, "ignored checkin and destroy of IKE_SA during shutdown");
+                       entry->checked_out = NULL;
                        entry->condvar->broadcast(entry->condvar);
                        unlock_single_segment(this, segment);
                        return;
@@ -1692,11 +1782,11 @@ METHOD(ike_sa_manager_t, checkin_and_destroy, void,
 
                entry_destroy(entry);
 
-               DBG2(DBG_MGR, "check-in and destroy of IKE_SA successful");
+               DBG2(DBG_MGR, "checkin and destroy of IKE_SA successful");
        }
        else
        {
-               DBG1(DBG_MGR, "tried to check-in and delete nonexisting IKE_SA");
+               DBG1(DBG_MGR, "tried to checkin and delete nonexisting IKE_SA");
                ike_sa->destroy(ike_sa);
        }
        charon->bus->set_sa(charon->bus, NULL);
@@ -1749,20 +1839,47 @@ METHOD(ike_sa_manager_t, create_id_enumerator, enumerator_t*,
 }
 
 /**
- * Move all CHILD_SAs from old to new
+ * Move all CHILD_SAs and virtual IPs from old to new
  */
-static void adopt_children(ike_sa_t *old, ike_sa_t *new)
+static void adopt_children_and_vips(ike_sa_t *old, ike_sa_t *new)
 {
        enumerator_t *enumerator;
        child_sa_t *child_sa;
+       host_t *vip;
+       int chcount = 0, vipcount = 0;
 
+       charon->bus->children_migrate(charon->bus, new->get_id(new),
+                                                                 new->get_unique_id(new));
        enumerator = old->create_child_sa_enumerator(old);
        while (enumerator->enumerate(enumerator, &child_sa))
        {
                old->remove_child_sa(old, enumerator);
                new->add_child_sa(new, child_sa);
+               chcount++;
+       }
+       enumerator->destroy(enumerator);
+
+       enumerator = old->create_virtual_ip_enumerator(old, FALSE);
+       while (enumerator->enumerate(enumerator, &vip))
+       {
+               new->add_virtual_ip(new, FALSE, vip);
+               vipcount++;
        }
        enumerator->destroy(enumerator);
+       /* this does not release the addresses, which is good, but it does trigger
+        * an assign_vips(FALSE) event... */
+       old->clear_virtual_ips(old, FALSE);
+       /* ...trigger the analogous event on the new SA */
+       charon->bus->set_sa(charon->bus, new);
+       charon->bus->assign_vips(charon->bus, new, TRUE);
+       charon->bus->children_migrate(charon->bus, NULL, 0);
+       charon->bus->set_sa(charon->bus, old);
+
+       if (chcount || vipcount)
+       {
+               DBG1(DBG_IKE, "detected reauth of existing IKE_SA, adopting %d "
+                        "children and %d virtual IPs", chcount, vipcount);
+       }
 }
 
 /**
@@ -1774,14 +1891,20 @@ static status_t enforce_replace(private_ike_sa_manager_t *this,
 {
        charon->bus->alert(charon->bus, ALERT_UNIQUE_REPLACE);
 
-       if (duplicate->get_version(duplicate) == IKEV1 &&
-               host->equals(host, duplicate->get_other_host(duplicate)))
+       if (host->equals(host, duplicate->get_other_host(duplicate)))
        {
                /* looks like a reauthentication attempt */
-               adopt_children(duplicate, new);
+               if (!new->has_condition(new, COND_INIT_CONTACT_SEEN) &&
+                       new->get_version(new) == IKEV1)
+               {
+                       /* IKEv1 implicitly takes over children, IKEv2 recreates them
+                        * explicitly. */
+                       adopt_children_and_vips(duplicate, new);
+               }
                /* For IKEv1 we have to delay the delete for the old IKE_SA. Some
                 * peers need to complete the new SA first, otherwise the quick modes
-                * might get lost. */
+                * might get lost. For IKEv2 we do the same, as we want overlapping
+                * CHILD_SAs to keep connectivity up. */
                lib->scheduler->schedule_job(lib->scheduler, (job_t*)
                        delete_ike_sa_job_create(duplicate->get_id(duplicate), TRUE), 10);
                return SUCCESS;
@@ -1846,10 +1969,15 @@ METHOD(ike_sa_manager_t, check_uniqueness, bool,
                                                                                                         other, other_host);
                                                        break;
                                                case UNIQUE_KEEP:
-                                                       cancel = TRUE;
-                                                       /* we keep the first IKE_SA and delete all
-                                                        * other duplicates that might exist */
-                                                       policy = UNIQUE_REPLACE;
+                                                       /* potential reauthentication? */
+                                                       if (!other_host->equals(other_host,
+                                                                               duplicate->get_other_host(duplicate)))
+                                                       {
+                                                               cancel = TRUE;
+                                                               /* we keep the first IKE_SA and delete all
+                                                                * other duplicates that might exist */
+                                                               policy = UNIQUE_REPLACE;
+                                                       }
                                                        break;
                                                default:
                                                        break;
@@ -1920,7 +2048,7 @@ METHOD(ike_sa_manager_t, get_count, u_int,
 }
 
 METHOD(ike_sa_manager_t, get_half_open_count, u_int,
-       private_ike_sa_manager_t *this, host_t *ip)
+       private_ike_sa_manager_t *this, host_t *ip, bool responder_only)
 {
        table_item_t *item;
        u_int row, segment;
@@ -1942,7 +2070,8 @@ METHOD(ike_sa_manager_t, get_half_open_count, u_int,
 
                        if (chunk_equals(addr, half_open->other))
                        {
-                               count = half_open->count;
+                               count = responder_only ? half_open->count_responder
+                                                                          : half_open->count;
                                break;
                        }
                        item = item->next;
@@ -1951,21 +2080,56 @@ METHOD(ike_sa_manager_t, get_half_open_count, u_int,
        }
        else
        {
-               for (segment = 0; segment < this->segment_count; segment++)
+               count = responder_only ? (u_int)ref_cur(&this->half_open_count_responder)
+                                                          : (u_int)ref_cur(&this->half_open_count);
+       }
+       return count;
+}
+
+METHOD(ike_sa_manager_t, set_spi_cb, void,
+       private_ike_sa_manager_t *this, spi_cb_t callback, void *data)
+{
+       this->spi_lock->write_lock(this->spi_lock);
+       this->spi_cb.cb = callback;
+       this->spi_cb.data = data;
+       this->spi_lock->unlock(this->spi_lock);
+}
+
+/**
+ * Destroy all entries
+ */
+static void destroy_all_entries(private_ike_sa_manager_t *this)
+{
+       enumerator_t *enumerator;
+       entry_t *entry;
+       u_int segment;
+
+       enumerator = create_table_enumerator(this);
+       while (enumerator->enumerate(enumerator, &entry, &segment))
+       {
+               charon->bus->set_sa(charon->bus, entry->ike_sa);
+               if (entry->half_open)
                {
-                       lock = this->half_open_segments[segment].lock;
-                       lock->read_lock(lock);
-                       count += this->half_open_segments[segment].count;
-                       lock->unlock(lock);
+                       remove_half_open(this, entry);
                }
+               if (entry->my_id && entry->other_id)
+               {
+                       remove_connected_peers(this, entry);
+               }
+               if (entry->init_hash.ptr)
+               {
+                       remove_init_hash(this, entry->init_hash);
+               }
+               remove_entry_at((private_enumerator_t*)enumerator);
+               entry_destroy(entry);
        }
-       return count;
+       enumerator->destroy(enumerator);
+       charon->bus->set_sa(charon->bus, NULL);
 }
 
 METHOD(ike_sa_manager_t, flush, void,
        private_ike_sa_manager_t *this)
 {
-       /* destroy all list entries */
        enumerator_t *enumerator;
        entry_t *entry;
        u_int segment;
@@ -2021,33 +2185,15 @@ METHOD(ike_sa_manager_t, flush, void,
 
        DBG2(DBG_MGR, "destroy all entries");
        /* Step 4: destroy all entries */
-       enumerator = create_table_enumerator(this);
-       while (enumerator->enumerate(enumerator, &entry, &segment))
-       {
-               charon->bus->set_sa(charon->bus, entry->ike_sa);
-               if (entry->half_open)
-               {
-                       remove_half_open(this, entry);
-               }
-               if (entry->my_id && entry->other_id)
-               {
-                       remove_connected_peers(this, entry);
-               }
-               if (entry->init_hash.ptr)
-               {
-                       remove_init_hash(this, entry->init_hash);
-               }
-               remove_entry_at((private_enumerator_t*)enumerator);
-               entry_destroy(entry);
-       }
-       enumerator->destroy(enumerator);
-       charon->bus->set_sa(charon->bus, NULL);
+       destroy_all_entries(this);
        unlock_all_segments(this);
 
-       this->rng->destroy(this->rng);
+       this->spi_lock->write_lock(this->spi_lock);
+       DESTROY_IF(this->rng);
        this->rng = NULL;
-       this->hasher->destroy(this->hasher);
-       this->hasher = NULL;
+       this->spi_cb.cb = NULL;
+       this->spi_cb.data = NULL;
+       this->spi_lock->unlock(this->spi_lock);
 }
 
 METHOD(ike_sa_manager_t, destroy, void,
@@ -2055,7 +2201,11 @@ METHOD(ike_sa_manager_t, destroy, void,
 {
        u_int i;
 
-       /* these are already cleared in flush() above */
+       /* in case new SAs were checked in after flush() was called */
+       lock_all_segments(this);
+       destroy_all_entries(this);
+       unlock_all_segments(this);
+
        free(this->ike_sa_table);
        free(this->half_open_table);
        free(this->connected_peers_table);
@@ -2072,6 +2222,7 @@ METHOD(ike_sa_manager_t, destroy, void,
        free(this->connected_peers_segments);
        free(this->init_hashes_segments);
 
+       this->spi_lock->destroy(this->spi_lock);
        free(this);
 }
 
@@ -2118,38 +2269,32 @@ ike_sa_manager_t *ike_sa_manager_create()
                        .get_count = _get_count,
                        .get_half_open_count = _get_half_open_count,
                        .flush = _flush,
+                       .set_spi_cb = _set_spi_cb,
                        .destroy = _destroy,
                },
        );
 
-       this->hasher = lib->crypto->create_hasher(lib->crypto, HASH_PREFERRED);
-       if (this->hasher == NULL)
-       {
-               DBG1(DBG_MGR, "manager initialization failed, no hasher supported");
-               free(this);
-               return NULL;
-       }
        this->rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK);
        if (this->rng == NULL)
        {
                DBG1(DBG_MGR, "manager initialization failed, no RNG supported");
-               this->hasher->destroy(this->hasher);
                free(this);
                return NULL;
        }
+       this->spi_lock = rwlock_create(RWLOCK_TYPE_DEFAULT);
 
        this->ikesa_limit = lib->settings->get_int(lib->settings,
-                                                                       "%s.ikesa_limit", 0, charon->name);
+                                                                                          "%s.ikesa_limit", 0, lib->ns);
 
        this->table_size = get_nearest_powerof2(lib->settings->get_int(
                                                                        lib->settings, "%s.ikesa_table_size",
-                                                                       DEFAULT_HASHTABLE_SIZE, charon->name));
+                                                                       DEFAULT_HASHTABLE_SIZE, lib->ns));
        this->table_size = max(1, min(this->table_size, MAX_HASHTABLE_SIZE));
        this->table_mask = this->table_size - 1;
 
        this->segment_count = get_nearest_powerof2(lib->settings->get_int(
                                                                        lib->settings, "%s.ikesa_table_segments",
-                                                                       DEFAULT_SEGMENT_COUNT, charon->name));
+                                                                       DEFAULT_SEGMENT_COUNT, lib->ns));
        this->segment_count = max(1, min(this->segment_count, this->table_size));
        this->segment_mask = this->segment_count - 1;
 
@@ -2158,7 +2303,6 @@ ike_sa_manager_t *ike_sa_manager_create()
        for (i = 0; i < this->segment_count; i++)
        {
                this->segments[i].mutex = mutex_create(MUTEX_TYPE_RECURSIVE);
-               this->segments[i].count = 0;
        }
 
        /* we use the same table parameters for the table to track half-open SAs */
@@ -2167,7 +2311,6 @@ ike_sa_manager_t *ike_sa_manager_create()
        for (i = 0; i < this->segment_count; i++)
        {
                this->half_open_segments[i].lock = rwlock_create(RWLOCK_TYPE_DEFAULT);
-               this->half_open_segments[i].count = 0;
        }
 
        /* also for the hash table used for duplicate tests */
@@ -2176,7 +2319,6 @@ ike_sa_manager_t *ike_sa_manager_create()
        for (i = 0; i < this->segment_count; i++)
        {
                this->connected_peers_segments[i].lock = rwlock_create(RWLOCK_TYPE_DEFAULT);
-               this->connected_peers_segments[i].count = 0;
        }
 
        /* and again for the table of hashes of seen initial IKE messages */
@@ -2185,10 +2327,9 @@ ike_sa_manager_t *ike_sa_manager_create()
        for (i = 0; i < this->segment_count; i++)
        {
                this->init_hashes_segments[i].mutex = mutex_create(MUTEX_TYPE_RECURSIVE);
-               this->init_hashes_segments[i].count = 0;
        }
 
        this->reuse_ikesa = lib->settings->get_bool(lib->settings,
-                                                                               "%s.reuse_ikesa", TRUE, charon->name);
+                                                                                       "%s.reuse_ikesa", TRUE, lib->ns);
        return &this->public;
 }