]> git.ipfire.org Git - thirdparty/strongswan.git/blobdiff - src/libcharon/sa/ike_sa.c
ike-sa: Add property for interface ID
[thirdparty/strongswan.git] / src / libcharon / sa / ike_sa.c
index 3384c02787577ac41c3facc8e29ab2d32b941c59..e75aa755cda3327ea60911adaa78e266de4070a3 100644 (file)
@@ -1,9 +1,9 @@
 /*
- * Copyright (C) 2006-2015 Tobias Brunner
+ * Copyright (C) 2006-2019 Tobias Brunner
  * Copyright (C) 2006 Daniel Roethlisberger
  * Copyright (C) 2005-2009 Martin Willi
  * Copyright (C) 2005 Jan Hutter
- * Hochschule fuer Technik Rapperswil
+ * HSR Hochschule fuer Technik Rapperswil
  *
  * This program is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License as published by the
@@ -71,6 +71,7 @@ ENUM(ike_sa_state_names, IKE_CREATED, IKE_DESTROYING,
        "ESTABLISHED",
        "PASSIVE",
        "REKEYING",
+       "REKEYED",
        "DELETING",
        "DESTROYING",
 );
@@ -101,7 +102,7 @@ struct private_ike_sa_t {
        /**
         * unique numerical ID for this IKE_SA.
         */
-       u_int32_t unique_id;
+       uint32_t unique_id;
 
        /**
         * Current state of the IKE_SA
@@ -230,15 +231,10 @@ struct private_ike_sa_t {
         */
        chunk_t nat_detection_dest;
 
-       /**
-        * number pending UPDATE_SA_ADDRESS (MOBIKE)
-        */
-       u_int32_t pending_updates;
-
        /**
         * NAT keep alive interval
         */
-       u_int32_t keepalive_interval;
+       uint32_t keepalive_interval;
 
        /**
         * The schedueld keep alive job, if any
@@ -249,7 +245,7 @@ struct private_ike_sa_t {
         * interval for retries during initiation (e.g. if DNS resolution failed),
         * 0 to disable (default)
         */
-       u_int32_t retry_initiate_interval;
+       uint32_t retry_initiate_interval;
 
        /**
         * TRUE if a retry_initiate_job has been queued
@@ -259,12 +255,12 @@ struct private_ike_sa_t {
        /**
         * Timestamps for this IKE_SA
         */
-       u_int32_t stats[STAT_MAX];
+       uint32_t stats[STAT_MAX];
 
        /**
         * how many times we have retried so far (keyingtries)
         */
-       u_int32_t keyingtry;
+       uint32_t keyingtry;
 
        /**
         * local host address to be used for IKE, set via MIGRATE kernel message
@@ -300,6 +296,16 @@ struct private_ike_sa_t {
         * Timestamps of redirect attempts to handle loops
         */
        array_t *redirected_at;
+
+       /**
+        * Inbound interface ID
+        */
+       uint32_t if_id_in;
+
+       /**
+        * Outbound interface ID
+        */
+       uint32_t if_id_out;
 };
 
 /**
@@ -343,7 +349,7 @@ static time_t get_use_time(private_ike_sa_t* this, bool inbound)
        return use_time;
 }
 
-METHOD(ike_sa_t, get_unique_id, u_int32_t,
+METHOD(ike_sa_t, get_unique_id, uint32_t,
        private_ike_sa_t *this)
 {
        return this->unique_id;
@@ -359,7 +365,7 @@ METHOD(ike_sa_t, get_name, char*,
        return "(unnamed)";
 }
 
-METHOD(ike_sa_t, get_statistic, u_int32_t,
+METHOD(ike_sa_t, get_statistic, uint32_t,
        private_ike_sa_t *this, statistic_t kind)
 {
        if (kind < STAT_MAX)
@@ -370,7 +376,7 @@ METHOD(ike_sa_t, get_statistic, u_int32_t,
 }
 
 METHOD(ike_sa_t, set_statistic, void,
-       private_ike_sa_t *this, statistic_t kind, u_int32_t value)
+       private_ike_sa_t *this, statistic_t kind, uint32_t value)
 {
        if (kind < STAT_MAX)
        {
@@ -423,11 +429,15 @@ METHOD(ike_sa_t, set_peer_cfg, void,
        DESTROY_IF(this->peer_cfg);
        this->peer_cfg = peer_cfg;
 
-       if (this->ike_cfg == NULL)
+       if (!this->ike_cfg)
        {
                this->ike_cfg = peer_cfg->get_ike_cfg(peer_cfg);
                this->ike_cfg->get_ref(this->ike_cfg);
        }
+
+       this->if_id_in = peer_cfg->get_if_id(peer_cfg, TRUE);
+       this->if_id_out = peer_cfg->get_if_id(peer_cfg, FALSE);
+       allocate_unique_if_ids(&this->if_id_in, &this->if_id_out);
 }
 
 METHOD(ike_sa_t, get_auth_cfg, auth_cfg_t*,
@@ -463,6 +473,26 @@ METHOD(ike_sa_t, create_auth_cfg_enumerator, enumerator_t*,
        return array_create_enumerator(this->other_auths);
 }
 
+/**
+ * Flush the stored authentication round information
+ */
+static void flush_auth_cfgs(private_ike_sa_t *this)
+{
+       auth_cfg_t *cfg;
+
+       this->my_auth->purge(this->my_auth, FALSE);
+       this->other_auth->purge(this->other_auth, FALSE);
+
+       while (array_remove(this->my_auths, ARRAY_TAIL, &cfg))
+       {
+               cfg->destroy(cfg);
+       }
+       while (array_remove(this->other_auths, ARRAY_TAIL, &cfg))
+       {
+               cfg->destroy(cfg);
+       }
+}
+
 METHOD(ike_sa_t, verify_peer_certificate, bool,
        private_ike_sa_t *this)
 {
@@ -482,13 +512,16 @@ METHOD(ike_sa_t, verify_peer_certificate, bool,
                return FALSE;
        }
 
-       if (lib->settings->get_bool(lib->settings,
+       if (!this->flush_auth_cfg &&
+               lib->settings->get_bool(lib->settings,
                                                                "%s.flush_auth_cfg", FALSE, lib->ns))
-       {
+       {       /* we can do this check only once if auth configs are flushed */
                DBG1(DBG_IKE, "unable to verify peer certificate as authentication "
                         "information has been flushed");
                return FALSE;
        }
+       this->public.set_condition(&this->public, COND_ONLINE_VALIDATION_SUSPENDED,
+                                                          FALSE);
 
        e1 = this->peer_cfg->create_auth_cfg_enumerator(this->peer_cfg, FALSE);
        e2 = array_create_enumerator(this->other_auths);
@@ -514,9 +547,6 @@ METHOD(ike_sa_t, verify_peer_certificate, bool,
                }
                if (!peer->get_validity(peer, NULL, &not_before, &not_after))
                {
-                       /* FIXME: theoretically we could find a newer cert with the same
-                        * identity and public key below...but it's not the cert used by
-                        * the peer during the original authentication so... */
                        DBG1(DBG_IKE, "peer certificate invalid (valid from %T to %T)",
                                 &not_before, FALSE, &not_after, FALSE);
                        valid = FALSE;
@@ -561,27 +591,13 @@ METHOD(ike_sa_t, verify_peer_certificate, bool,
        }
        e1->destroy(e1);
        e2->destroy(e2);
-       return valid;
-}
 
-/**
- * Flush the stored authentication round information
- */
-static void flush_auth_cfgs(private_ike_sa_t *this)
-{
-       auth_cfg_t *cfg;
-
-       this->my_auth->purge(this->my_auth, FALSE);
-       this->other_auth->purge(this->other_auth, FALSE);
-
-       while (array_remove(this->my_auths, ARRAY_TAIL, &cfg))
+       if (this->flush_auth_cfg)
        {
-               cfg->destroy(cfg);
-       }
-       while (array_remove(this->other_auths, ARRAY_TAIL, &cfg))
-       {
-               cfg->destroy(cfg);
+               this->flush_auth_cfg = FALSE;
+               flush_auth_cfgs(this);
        }
+       return valid;
 }
 
 METHOD(ike_sa_t, get_proposal, proposal_t*,
@@ -598,7 +614,7 @@ METHOD(ike_sa_t, set_proposal, void,
 }
 
 METHOD(ike_sa_t, set_message_id, void,
-       private_ike_sa_t *this, bool initiate, u_int32_t mid)
+       private_ike_sa_t *this, bool initiate, uint32_t mid)
 {
        if (initiate)
        {
@@ -610,6 +626,12 @@ METHOD(ike_sa_t, set_message_id, void,
        }
 }
 
+METHOD(ike_sa_t, get_message_id, uint32_t,
+       private_ike_sa_t *this, bool initiate)
+{
+       return this->task_manager->get_mid(this->task_manager, initiate);
+}
+
 METHOD(ike_sa_t, send_keepalive, void,
        private_ike_sa_t *this, bool scheduled)
 {
@@ -666,6 +688,7 @@ METHOD(ike_sa_t, get_ike_cfg, ike_cfg_t*,
 METHOD(ike_sa_t, set_ike_cfg, void,
        private_ike_sa_t *this, ike_cfg_t *ike_cfg)
 {
+       DESTROY_IF(this->ike_cfg);
        ike_cfg->get_ref(ike_cfg);
        this->ike_cfg = ike_cfg;
 }
@@ -721,8 +744,11 @@ METHOD(ike_sa_t, set_condition, void,
                        switch (condition)
                        {
                                case COND_NAT_HERE:
-                               case COND_NAT_FAKE:
                                case COND_NAT_THERE:
+                                       DBG1(DBG_IKE, "%s host is not behind NAT anymore",
+                                                condition == COND_NAT_HERE ? "local" : "remote");
+                                       /* fall-through */
+                               case COND_NAT_FAKE:
                                        set_condition(this, COND_NAT_ANY,
                                                                  has_condition(this, COND_NAT_HERE) ||
                                                                  has_condition(this, COND_NAT_THERE) ||
@@ -749,6 +775,10 @@ METHOD(ike_sa_t, send_dpd, status_t,
        {
                return INVALID_STATE;
        }
+       if (this->version == IKEV1 && this->state == IKE_REKEYING)
+       {       /* don't send DPDs for rekeyed IKEv1 SAs */
+               return SUCCESS;
+       }
        delay = this->peer_cfg->get_dpd(this->peer_cfg);
        if (this->task_manager->busy(this->task_manager))
        {
@@ -808,7 +838,7 @@ METHOD(ike_sa_t, set_state, void,
                                this->state == IKE_PASSIVE)
                        {
                                job_t *job;
-                               u_int32_t t;
+                               uint32_t t;
 
                                /* calculate rekey, reauth and lifetime */
                                this->stats[STAT_ESTABLISHED] = time_monotonic(NULL);
@@ -897,9 +927,15 @@ METHOD(ike_sa_t, set_state, void,
 }
 
 METHOD(ike_sa_t, reset, void,
-       private_ike_sa_t *this)
+       private_ike_sa_t *this, bool new_spi)
 {
-       /*  the responder ID is reset, as peer may choose another one */
+       /* reset the initiator SPI if requested */
+       if (new_spi)
+       {
+               charon->ike_sa_manager->new_initiator_spi(charon->ike_sa_manager,
+                                                                                                 &this->public);
+       }
+       /* the responder ID is reset, as peer may choose another one */
        if (this->ike_sa_id->is_initiator(this->ike_sa_id))
        {
                this->ike_sa_id->set_responder_spi(this->ike_sa_id, 0);
@@ -914,6 +950,7 @@ METHOD(ike_sa_t, reset, void,
                                                        this->ike_sa_id->is_initiator(this->ike_sa_id));
 
        this->task_manager->reset(this->task_manager, 0, 0);
+       this->task_manager->queue_ike(this->task_manager);
 }
 
 METHOD(ike_sa_t, get_keymat, keymat_t*,
@@ -1028,32 +1065,22 @@ METHOD(ike_sa_t, has_mapping_changed, bool,
        return TRUE;
 }
 
-METHOD(ike_sa_t, set_pending_updates, void,
-       private_ike_sa_t *this, u_int32_t updates)
-{
-       this->pending_updates = updates;
-}
-
-METHOD(ike_sa_t, get_pending_updates, u_int32_t,
-       private_ike_sa_t *this)
-{
-       return this->pending_updates;
-}
-
 METHOD(ike_sa_t, float_ports, void,
           private_ike_sa_t *this)
 {
-       /* do not switch if we have a custom port from MOBIKE/NAT */
+       /* even if the remote port is not 500 (e.g. because the response was natted)
+        * we switch the remote port if we used port 500 */
+       if (this->other_host->get_port(this->other_host) == IKEV2_UDP_PORT ||
+               this->my_host->get_port(this->my_host) == IKEV2_UDP_PORT)
+       {
+               this->other_host->set_port(this->other_host, IKEV2_NATT_PORT);
+       }
        if (this->my_host->get_port(this->my_host) ==
                        charon->socket->get_port(charon->socket, FALSE))
        {
                this->my_host->set_port(this->my_host,
                                                                charon->socket->get_port(charon->socket, TRUE));
        }
-       if (this->other_host->get_port(this->other_host) == IKEV2_UDP_PORT)
-       {
-               this->other_host->set_port(this->other_host, IKEV2_NATT_PORT);
-       }
 }
 
 METHOD(ike_sa_t, update_hosts, void,
@@ -1182,12 +1209,20 @@ METHOD(ike_sa_t, generate_message, status_t,
        return status;
 }
 
-static bool filter_fragments(private_ike_sa_t *this, packet_t **fragment,
-                                                        packet_t **packet)
+CALLBACK(filter_fragments, bool,
+       private_ike_sa_t *this, enumerator_t *orig, va_list args)
 {
-       *packet = (*fragment)->clone(*fragment);
-       set_dscp(this, *packet);
-       return TRUE;
+       packet_t *fragment, **packet;
+
+       VA_ARGS_VGET(args, packet);
+
+       if (orig->enumerate(orig, &fragment))
+       {
+               *packet = fragment->clone(fragment);
+               set_dscp(this, *packet);
+               return TRUE;
+       }
+       return FALSE;
 }
 
 METHOD(ike_sa_t, generate_message_fragmented, status_t,
@@ -1197,6 +1232,7 @@ METHOD(ike_sa_t, generate_message_fragmented, status_t,
        packet_t *packet;
        status_t status;
        bool use_frags = FALSE;
+       bool pre_generated = FALSE;
 
        if (this->ike_cfg)
        {
@@ -1231,15 +1267,22 @@ METHOD(ike_sa_t, generate_message_fragmented, status_t,
                return SUCCESS;
        }
 
+       pre_generated = message->is_encoded(message);
        this->stats[STAT_OUTBOUND] = time_monotonic(NULL);
        message->set_ike_sa_id(message, this->ike_sa_id);
-       charon->bus->message(charon->bus, message, FALSE, TRUE);
+       if (!pre_generated)
+       {
+               charon->bus->message(charon->bus, message, FALSE, TRUE);
+       }
        status = message->fragment(message, this->keymat, this->fragment_size,
                                                           &fragments);
        if (status == SUCCESS)
        {
-               charon->bus->message(charon->bus, message, FALSE, FALSE);
-               *packets = enumerator_create_filter(fragments, (void*)filter_fragments,
+               if (!pre_generated)
+               {
+                       charon->bus->message(charon->bus, message, FALSE, FALSE);
+               }
+               *packets = enumerator_create_filter(fragments, filter_fragments,
                                                                                        this, NULL);
        }
        return status;
@@ -1426,7 +1469,7 @@ static void resolve_hosts(private_ike_sa_t *this)
 }
 
 METHOD(ike_sa_t, initiate, status_t,
-       private_ike_sa_t *this, child_cfg_t *child_cfg, u_int32_t reqid,
+       private_ike_sa_t *this, child_cfg_t *child_cfg, uint32_t reqid,
        traffic_selector_t *tsi, traffic_selector_t *tsr)
 {
        bool defer_initiate = FALSE;
@@ -1543,9 +1586,14 @@ METHOD(ike_sa_t, process_message, status_t,
        status = this->task_manager->process_message(this->task_manager, message);
        if (this->flush_auth_cfg && this->state == IKE_ESTABLISHED)
        {
-               /* authentication completed */
-               this->flush_auth_cfg = FALSE;
-               flush_auth_cfgs(this);
+               /* authentication completed but if the online validation is suspended we
+                * need the auth cfgs until we did the delayed verification, we flush
+                * them afterwards */
+               if (!has_condition(this, COND_ONLINE_VALIDATION_SUSPENDED))
+               {
+                       this->flush_auth_cfg = FALSE;
+                       flush_auth_cfgs(this);
+               }
        }
        return status;
 }
@@ -1622,6 +1670,12 @@ METHOD(ike_sa_t, set_other_id, void,
        this->other_id = other;
 }
 
+METHOD(ike_sa_t, get_if_id, uint32_t,
+       private_ike_sa_t *this, bool inbound)
+{
+       return inbound ? this->if_id_in : this->if_id_out;
+}
+
 METHOD(ike_sa_t, add_child_sa, void,
        private_ike_sa_t *this, child_sa_t *child_sa)
 {
@@ -1631,7 +1685,7 @@ METHOD(ike_sa_t, add_child_sa, void,
 }
 
 METHOD(ike_sa_t, get_child_sa, child_sa_t*,
-       private_ike_sa_t *this, protocol_id_t protocol, u_int32_t spi, bool inbound)
+       private_ike_sa_t *this, protocol_id_t protocol, uint32_t spi, bool inbound)
 {
        enumerator_t *enumerator;
        child_sa_t *current, *found = NULL;
@@ -1668,8 +1722,11 @@ typedef struct {
 } child_enumerator_t;
 
 METHOD(enumerator_t, child_enumerate, bool,
-       child_enumerator_t *this, child_sa_t **child_sa)
+       child_enumerator_t *this, va_list args)
 {
+       child_sa_t **child_sa;
+
+       VA_ARGS_VGET(args, child_sa);
        if (this->inner->enumerate(this->inner, &this->current))
        {
                *child_sa = this->current;
@@ -1692,7 +1749,8 @@ METHOD(ike_sa_t, create_child_sa_enumerator, enumerator_t*,
 
        INIT(enumerator,
                .public = {
-                       .enumerate = (void*)_child_enumerate,
+                       .enumerate = enumerator_enumerate_default,
+                       .venumerate = _child_enumerate,
                        .destroy = _child_enumerator_destroy,
                },
                .inner = array_create_enumerator(this->child_sas),
@@ -1710,7 +1768,7 @@ METHOD(ike_sa_t, remove_child_sa, void,
 }
 
 METHOD(ike_sa_t, rekey_child_sa, status_t,
-       private_ike_sa_t *this, protocol_id_t protocol, u_int32_t spi)
+       private_ike_sa_t *this, protocol_id_t protocol, uint32_t spi)
 {
        if (this->state == IKE_PASSIVE)
        {
@@ -1721,7 +1779,7 @@ METHOD(ike_sa_t, rekey_child_sa, status_t,
 }
 
 METHOD(ike_sa_t, delete_child_sa, status_t,
-       private_ike_sa_t *this, protocol_id_t protocol, u_int32_t spi, bool expired)
+       private_ike_sa_t *this, protocol_id_t protocol, uint32_t spi, bool expired)
 {
        if (this->state == IKE_PASSIVE)
        {
@@ -1733,7 +1791,7 @@ METHOD(ike_sa_t, delete_child_sa, status_t,
 }
 
 METHOD(ike_sa_t, destroy_child_sa, status_t,
-       private_ike_sa_t *this, protocol_id_t protocol, u_int32_t spi)
+       private_ike_sa_t *this, protocol_id_t protocol, uint32_t spi)
 {
        enumerator_t *enumerator;
        child_sa_t *child_sa;
@@ -1756,36 +1814,53 @@ METHOD(ike_sa_t, destroy_child_sa, status_t,
 }
 
 METHOD(ike_sa_t, delete_, status_t,
-       private_ike_sa_t *this)
+       private_ike_sa_t *this, bool force)
 {
+       status_t status = DESTROY_ME;
+
        switch (this->state)
        {
-               case IKE_REKEYING:
-                       if (this->version == IKEV1)
-                       {       /* SA has been reauthenticated, delete */
-                               charon->bus->ike_updown(charon->bus, &this->public, FALSE);
-                               break;
-                       }
-                       /* FALL */
                case IKE_ESTABLISHED:
-                       if (time_monotonic(NULL) >= this->stats[STAT_DELETE])
-                       {       /* IKE_SA hard lifetime hit */
+               case IKE_REKEYING:
+                       if (time_monotonic(NULL) >= this->stats[STAT_DELETE] &&
+                               !(this->version == IKEV1 && this->state == IKE_REKEYING))
+                       {       /* IKE_SA hard lifetime hit, ignored for reauthenticated
+                                * IKEv1 SAs */
                                charon->bus->alert(charon->bus, ALERT_IKE_SA_EXPIRED);
                        }
                        this->task_manager->queue_ike_delete(this->task_manager);
-                       return this->task_manager->initiate(this->task_manager);
+                       status = this->task_manager->initiate(this->task_manager);
+                       break;
                case IKE_CREATED:
                        DBG1(DBG_IKE, "deleting unestablished IKE_SA");
                        break;
                case IKE_PASSIVE:
                        break;
                default:
-                       DBG1(DBG_IKE, "destroying IKE_SA in state %N "
-                               "without notification", ike_sa_state_names, this->state);
-                       charon->bus->ike_updown(charon->bus, &this->public, FALSE);
+                       DBG1(DBG_IKE, "destroying IKE_SA in state %N without notification",
+                                ike_sa_state_names, this->state);
+                       force = TRUE;
                        break;
        }
-       return DESTROY_ME;
+
+       if (force)
+       {
+               status = DESTROY_ME;
+
+               if (this->version == IKEV2)
+               {       /* for IKEv1 we trigger this in the ISAKMP delete task */
+                       switch (this->state)
+                       {
+                               case IKE_ESTABLISHED:
+                               case IKE_REKEYING:
+                               case IKE_DELETING:
+                                       charon->bus->ike_updown(charon->bus, &this->public, FALSE);
+                               default:
+                                       break;
+                       }
+               }
+       }
+       return status;
 }
 
 METHOD(ike_sa_t, rekey, status_t,
@@ -1810,8 +1885,7 @@ METHOD(ike_sa_t, reauth, status_t,
        {
                DBG0(DBG_IKE, "reinitiating IKE_SA %s[%d]",
                         get_name(this), this->unique_id);
-               reset(this);
-               this->task_manager->queue_ike(this->task_manager);
+               reset(this, TRUE);
                return this->task_manager->initiate(this->task_manager);
        }
        /* we can't reauthenticate as responder when we use EAP or virtual IPs.
@@ -1896,23 +1970,18 @@ static status_t reestablish_children(private_ike_sa_t *this, ike_sa_t *new,
        enumerator = create_child_sa_enumerator(this);
        while (enumerator->enumerate(enumerator, (void**)&child_sa))
        {
+               switch (child_sa->get_state(child_sa))
+               {
+                       case CHILD_REKEYED:
+                       case CHILD_DELETED:
+                               /* ignore CHILD_SAs in these states */
+                               continue;
+                       default:
+                               break;
+               }
                if (force)
                {
-                       switch (child_sa->get_state(child_sa))
-                       {
-                               case CHILD_ROUTED:
-                               {       /* move routed child directly */
-                                       remove_child_sa(this, enumerator);
-                                       new->add_child_sa(new, child_sa);
-                                       action = ACTION_NONE;
-                                       break;
-                               }
-                               default:
-                               {       /* initiate/queue all other CHILD_SAs */
-                                       action = ACTION_RESTART;
-                                       break;
-                               }
-                       }
+                       action = ACTION_RESTART;
                }
                else
                {       /* only restart CHILD_SAs that are configured accordingly */
@@ -1947,8 +2016,7 @@ static status_t reestablish_children(private_ike_sa_t *this, ike_sa_t *new,
        /* adopt any active or queued CHILD-creating tasks */
        if (status != DESTROY_ME)
        {
-               task_manager_t *other_tasks = ((private_ike_sa_t*)new)->task_manager;
-               other_tasks->adopt_child_tasks(other_tasks, this->task_manager);
+               new->adopt_child_tasks(new, &this->public);
                if (new->get_state(new) == IKE_CREATED)
                {
                        status = new->initiate(new, NULL, 0, NULL, NULL);
@@ -1990,6 +2058,15 @@ METHOD(ike_sa_t, reestablish, status_t,
                enumerator = array_create_enumerator(this->child_sas);
                while (enumerator->enumerate(enumerator, (void**)&child_sa))
                {
+                       switch (child_sa->get_state(child_sa))
+                       {
+                               case CHILD_REKEYED:
+                               case CHILD_DELETED:
+                                       /* ignore CHILD_SAs in these states */
+                                       continue;
+                               default:
+                                       break;
+                       }
                        if (this->state == IKE_DELETING)
                        {
                                action = child_sa->get_close_action(child_sa);
@@ -2005,8 +2082,7 @@ METHOD(ike_sa_t, reestablish, status_t,
                                        break;
                                case ACTION_ROUTE:
                                        charon->traps->install(charon->traps, this->peer_cfg,
-                                                                                  child_sa->get_config(child_sa),
-                                                                                  child_sa->get_reqid(child_sa));
+                                                                                  child_sa->get_config(child_sa));
                                        break;
                                default:
                                        break;
@@ -2184,7 +2260,7 @@ static bool redirect_connecting(private_ike_sa_t *this, identification_t *to)
        {
                return FALSE;
        }
-       reset(this);
+       reset(this, TRUE);
        DESTROY_IF(this->redirected_from);
        this->redirected_from = this->other_host->clone(this->other_host);
        DESTROY_IF(this->remote_host);
@@ -2290,7 +2366,7 @@ METHOD(ike_sa_t, redirect, status_t,
 }
 
 METHOD(ike_sa_t, retransmit, status_t,
-       private_ike_sa_t *this, u_int32_t message_id)
+       private_ike_sa_t *this, uint32_t message_id)
 {
        if (this->state == IKE_PASSIVE)
        {
@@ -2305,7 +2381,7 @@ METHOD(ike_sa_t, retransmit, status_t,
                        case IKE_CONNECTING:
                        {
                                /* retry IKE_SA_INIT/Main Mode if we have multiple keyingtries */
-                               u_int32_t tries = this->peer_cfg->get_keyingtries(this->peer_cfg);
+                               uint32_t tries = this->peer_cfg->get_keyingtries(this->peer_cfg);
                                charon->bus->alert(charon->bus, ALERT_PEER_INIT_UNREACHABLE,
                                                                   this->keyingtry);
                                this->keyingtry++;
@@ -2313,17 +2389,43 @@ METHOD(ike_sa_t, retransmit, status_t,
                                {
                                        DBG1(DBG_IKE, "peer not responding, trying again (%d/%d)",
                                                 this->keyingtry + 1, tries);
-                                       reset(this);
+                                       reset(this, TRUE);
                                        resolve_hosts(this);
-                                       this->task_manager->queue_ike(this->task_manager);
                                        return this->task_manager->initiate(this->task_manager);
                                }
                                DBG1(DBG_IKE, "establishing IKE_SA failed, peer not responding");
+
+                               if (this->version == IKEV1 && array_count(this->child_sas))
+                               {
+                                       enumerator_t *enumerator;
+                                       child_sa_t *child_sa;
+
+                                       /* if reauthenticating an IKEv1 SA failed (assumed for an SA
+                                        * in this state with CHILD_SAs), try again from scratch */
+                                       DBG1(DBG_IKE, "reauthentication failed, trying to "
+                                                "reestablish IKE_SA");
+                                       reestablish(this);
+                                       /* trigger down events for the CHILD_SAs, as no down event
+                                        * is triggered below for IKE SAs in this state */
+                                       enumerator = array_create_enumerator(this->child_sas);
+                                       while (enumerator->enumerate(enumerator, &child_sa))
+                                       {
+                                               if (child_sa->get_state(child_sa) != CHILD_REKEYED &&
+                                                       child_sa->get_state(child_sa) != CHILD_DELETED)
+                                               {
+                                                       charon->bus->child_updown(charon->bus, child_sa,
+                                                                                                         FALSE);
+                                               }
+                                       }
+                                       enumerator->destroy(enumerator);
+                               }
                                break;
                        }
                        case IKE_DELETING:
                                DBG1(DBG_IKE, "proper IKE_SA delete failed, peer not responding");
-                               if (has_condition(this, COND_REAUTHENTICATING))
+                               if (has_condition(this, COND_REAUTHENTICATING) &&
+                                       !lib->settings->get_bool(lib->settings,
+                                                                               "%s.make_before_break", FALSE, lib->ns))
                                {
                                        DBG1(DBG_IKE, "delete during reauthentication failed, "
                                                 "trying to reestablish IKE_SA anyway");
@@ -2337,7 +2439,8 @@ METHOD(ike_sa_t, retransmit, status_t,
                                reestablish(this);
                                break;
                }
-               if (this->state != IKE_CONNECTING)
+               if (this->state != IKE_CONNECTING &&
+                       this->state != IKE_REKEYED)
                {
                        charon->bus->ike_updown(charon->bus, &this->public, FALSE);
                }
@@ -2347,9 +2450,9 @@ METHOD(ike_sa_t, retransmit, status_t,
 }
 
 METHOD(ike_sa_t, set_auth_lifetime, status_t,
-       private_ike_sa_t *this, u_int32_t lifetime)
+       private_ike_sa_t *this, uint32_t lifetime)
 {
-       u_int32_t diff, hard, soft, now;
+       uint32_t diff, hard, soft, now;
        bool send_update;
 
        diff = this->peer_cfg->get_over_time(this->peer_cfg);
@@ -2420,6 +2523,25 @@ static bool is_current_path_valid(private_ike_sa_t *this)
 {
        bool valid = FALSE;
        host_t *src;
+
+       if (supports_extension(this, EXT_MOBIKE) &&
+               lib->settings->get_bool(lib->settings,
+                                                               "%s.prefer_best_path", FALSE, lib->ns))
+       {
+               /* check if the current path is the best path; migrate otherwise */
+               src = charon->kernel->get_source_addr(charon->kernel, this->other_host,
+                                                                                         NULL);
+               if (src)
+               {
+                       valid = src->ip_equals(src, this->my_host);
+                       src->destroy(src);
+               }
+               if (!valid)
+               {
+                       DBG1(DBG_IKE, "old path is not preferred anymore");
+               }
+               return valid;
+       }
        src = charon->kernel->get_source_addr(charon->kernel, this->other_host,
                                                                                  this->my_host);
        if (src)
@@ -2430,6 +2552,10 @@ static bool is_current_path_valid(private_ike_sa_t *this)
                }
                src->destroy(src);
        }
+       if (!valid)
+       {
+               DBG1(DBG_IKE, "old path is not available anymore, try to find another");
+       }
        return valid;
 }
 
@@ -2456,7 +2582,6 @@ static bool is_any_path_valid(private_ike_sa_t *this)
                        break;
        }
 
-       DBG1(DBG_IKE, "old path is not available anymore, try to find another");
        enumerator = create_peer_address_enumerator(this);
        while (enumerator->enumerate(enumerator, &addr))
        {
@@ -2489,11 +2614,33 @@ METHOD(ike_sa_t, roam, status_t,
                case IKE_DELETING:
                case IKE_DESTROYING:
                case IKE_PASSIVE:
+               case IKE_REKEYED:
                        return SUCCESS;
                default:
                        break;
        }
 
+       if (!this->ike_cfg)
+       {       /* this is the case for new HA SAs not yet in state IKE_PASSIVE and
+                * without config assigned */
+               return SUCCESS;
+       }
+       if (this->version == IKEV1)
+       {       /* ignore roam events for IKEv1 where we don't have MOBIKE and would
+                * have to reestablish from scratch (reauth is not enough) */
+               return SUCCESS;
+       }
+
+       /* ignore roam events if MOBIKE is not supported/enabled and the local
+        * address is statically configured */
+       if (!supports_extension(this, EXT_MOBIKE) &&
+               ike_cfg_has_address(this->ike_cfg, this->my_host, TRUE))
+       {
+               DBG2(DBG_IKE, "keeping statically configured path %H - %H",
+                        this->my_host, this->other_host);
+               return SUCCESS;
+       }
+
        /* keep existing path if possible */
        if (is_current_path_valid(this))
        {
@@ -2560,24 +2707,31 @@ METHOD(ike_sa_t, add_configuration_attribute, void,
        array_insert(this->attributes, ARRAY_TAIL, &entry);
 }
 
-/**
- * Enumerator filter for attributes
- */
-static bool filter_attribute(void *null, attribute_entry_t **in,
-                                                        configuration_attribute_type_t *type, void *in2,
-                                                        chunk_t *data, void *in3, bool *handled)
+CALLBACK(filter_attribute, bool,
+       void *null, enumerator_t *orig, va_list args)
 {
-       *type = (*in)->type;
-       *data = (*in)->data;
-       *handled = (*in)->handler != NULL;
-       return TRUE;
+       attribute_entry_t *entry;
+       configuration_attribute_type_t *type;
+       chunk_t *data;
+       bool *handled;
+
+       VA_ARGS_VGET(args, type, data, handled);
+
+       if (orig->enumerate(orig, &entry))
+       {
+               *type = entry->type;
+               *data = entry->data;
+               *handled = entry->handler != NULL;
+               return TRUE;
+       }
+       return FALSE;
 }
 
 METHOD(ike_sa_t, create_attribute_enumerator, enumerator_t*,
        private_ike_sa_t *this)
 {
        return enumerator_create_filter(array_create_enumerator(this->attributes),
-                                                                       (void*)filter_attribute, NULL, NULL);
+                                                                       filter_attribute, NULL, NULL);
 }
 
 METHOD(ike_sa_t, create_task_enumerator, enumerator_t*,
@@ -2586,6 +2740,12 @@ METHOD(ike_sa_t, create_task_enumerator, enumerator_t*,
        return this->task_manager->create_task_enumerator(this->task_manager, queue);
 }
 
+METHOD(ike_sa_t, remove_task, void,
+       private_ike_sa_t *this, enumerator_t *enumerator)
+{
+       return this->task_manager->remove_task(this->task_manager, enumerator);
+}
+
 METHOD(ike_sa_t, flush_queue, void,
        private_ike_sa_t *this, task_queue_t queue)
 {
@@ -2598,6 +2758,42 @@ METHOD(ike_sa_t, queue_task, void,
        this->task_manager->queue_task(this->task_manager, task);
 }
 
+METHOD(ike_sa_t, queue_task_delayed, void,
+       private_ike_sa_t *this, task_t *task, uint32_t delay)
+{
+       this->task_manager->queue_task_delayed(this->task_manager, task, delay);
+}
+
+/**
+ * Migrate and queue child-creating tasks from another IKE_SA
+ */
+static void migrate_child_tasks(private_ike_sa_t *this, ike_sa_t *other,
+                                                               task_queue_t queue)
+{
+       enumerator_t *enumerator;
+       task_t *task;
+
+       enumerator = other->create_task_enumerator(other, queue);
+       while (enumerator->enumerate(enumerator, &task))
+       {
+               if (task->get_type(task) == TASK_CHILD_CREATE ||
+                       task->get_type(task) == TASK_QUICK_MODE)
+               {
+                       other->remove_task(other, enumerator);
+                       task->migrate(task, &this->public);
+                       queue_task(this, task);
+               }
+       }
+       enumerator->destroy(enumerator);
+}
+
+METHOD(ike_sa_t, adopt_child_tasks, void,
+       private_ike_sa_t *this, ike_sa_t *other)
+{
+       migrate_child_tasks(this, other, TASK_QUEUE_ACTIVE);
+       migrate_child_tasks(this, other, TASK_QUEUE_QUEUED);
+}
+
 METHOD(ike_sa_t, inherit_pre, void,
        private_ike_sa_t *this, ike_sa_t *other_public)
 {
@@ -2634,6 +2830,8 @@ METHOD(ike_sa_t, inherit_post, void,
        this->other_host = other->other_host->clone(other->other_host);
        this->my_id = other->my_id->clone(other->my_id);
        this->other_id = other->other_id->clone(other->other_id);
+       this->if_id_in = other->if_id_in;
+       this->if_id_out = other->if_id_out;
 
        /* apply assigned virtual IPs... */
        while (array_remove(other->my_vips, ARRAY_HEAD, &vip))
@@ -2862,6 +3060,7 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id, bool initiator,
                        .get_other_host = _get_other_host,
                        .set_other_host = _set_other_host,
                        .set_message_id = _set_message_id,
+                       .get_message_id = _get_message_id,
                        .float_ports = _float_ports,
                        .update_hosts = _update_hosts,
                        .get_my_id = _get_my_id,
@@ -2873,8 +3072,6 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id, bool initiator,
                        .supports_extension = _supports_extension,
                        .set_condition = _set_condition,
                        .has_condition = _has_condition,
-                       .set_pending_updates = _set_pending_updates,
-                       .get_pending_updates = _get_pending_updates,
                        .create_peer_address_enumerator = _create_peer_address_enumerator,
                        .add_peer_address = _add_peer_address,
                        .clear_peer_addresses = _clear_peer_addresses,
@@ -2912,10 +3109,14 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id, bool initiator,
                        .create_virtual_ip_enumerator = _create_virtual_ip_enumerator,
                        .add_configuration_attribute = _add_configuration_attribute,
                        .create_attribute_enumerator = _create_attribute_enumerator,
+                       .get_if_id = _get_if_id,
                        .set_kmaddress = _set_kmaddress,
                        .create_task_enumerator = _create_task_enumerator,
+                       .remove_task = _remove_task,
                        .flush_queue = _flush_queue,
                        .queue_task = _queue_task,
+                       .queue_task_delayed = _queue_task_delayed,
+                       .adopt_child_tasks = _adopt_child_tasks,
 #ifdef ME
                        .act_as_mediation_server = _act_as_mediation_server,
                        .get_server_reflexive_host = _get_server_reflexive_host,
@@ -2951,7 +3152,7 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id, bool initiator,
                .flush_auth_cfg = lib->settings->get_bool(lib->settings,
                                                                "%s.flush_auth_cfg", FALSE, lib->ns),
                .fragment_size = lib->settings->get_int(lib->settings,
-                                                               "%s.fragment_size", 0, lib->ns),
+                                                               "%s.fragment_size", 1280, lib->ns),
                .follow_redirects = lib->settings->get_bool(lib->settings,
                                                                "%s.follow_redirects", TRUE, lib->ns),
        );