Don't unset IKE_SA on bus before we released virtual IPs and attributes
[strongswan.git] / src / libcharon / sa / ike_sa.c
index 78ffbe5..d7a9076 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2006-2011 Tobias Brunner
+ * Copyright (C) 2006-2012 Tobias Brunner
  * Copyright (C) 2006 Daniel Roethlisberger
  * Copyright (C) 2005-2009 Martin Willi
  * Copyright (C) 2005 Jan Hutter
 #include <library.h>
 #include <hydra.h>
 #include <daemon.h>
-#include <utils/linked_list.h>
+#include <collections/linked_list.h>
 #include <utils/lexparser.h>
 #include <processing/jobs/retransmit_job.h>
 #include <processing/jobs/delete_ike_sa_job.h>
 #include <processing/jobs/send_dpd_job.h>
 #include <processing/jobs/send_keepalive_job.h>
 #include <processing/jobs/rekey_ike_sa_job.h>
+#include <processing/jobs/retry_initiate_job.h>
+#include <sa/ikev2/tasks/ike_auth_lifetime.h>
 
 #ifdef ME
 #include <sa/ikev2/tasks/ike_me.h>
@@ -180,14 +182,14 @@ struct private_ike_sa_t {
        keymat_t *keymat;
 
        /**
-        * Virtual IP on local host, if any
+        * Virtual IPs on local host
         */
-       host_t *my_virtual_ip;
+       linked_list_t *my_vips;
 
        /**
-        * Virtual IP on remote host, if any
+        * Virtual IPs on remote host
         */
-       host_t *other_virtual_ip;
+       linked_list_t *other_vips;
 
        /**
         * List of configuration attributes (attribute_entry_t)
@@ -195,9 +197,9 @@ struct private_ike_sa_t {
        linked_list_t *attributes;
 
        /**
-        * list of peers additional addresses, transmitted via MOBIKE
+        * list of peer's addresses, additional ones transmitted via MOBIKE
         */
-       linked_list_t *additional_addresses;
+       linked_list_t *peer_addresses;
 
        /**
         * previously value of received DESTINATION_IP hash
@@ -215,6 +217,17 @@ struct private_ike_sa_t {
        u_int32_t keepalive_interval;
 
        /**
+        * interval for retries during initiation (e.g. if DNS resolution failed),
+        * 0 to disable (default)
+        */
+       u_int32_t retry_initiate_interval;
+
+       /**
+        * TRUE if a retry_initiate_job has been queued
+        */
+       bool retry_initiate_queued;
+
+       /**
         * Timestamps for this IKE_SA
         */
        u_int32_t stats[STAT_MAX];
@@ -272,7 +285,7 @@ static time_t get_use_time(private_ike_sa_t* this, bool inbound)
        enumerator = this->child_sas->create_enumerator(this->child_sas);
        while (enumerator->enumerate(enumerator, &child_sa))
        {
-               child_sa->get_usestats(child_sa, inbound, &current, NULL);
+               child_sa->get_usestats(child_sa, inbound, &current, NULL, NULL);
                use_time = max(use_time, current);
        }
        enumerator->destroy(enumerator);
@@ -470,8 +483,8 @@ METHOD(ike_sa_t, send_keepalive, void,
                data.ptr[0] = 0xFF;
                data.len = 1;
                packet->set_data(packet, data);
-               DBG1(DBG_IKE, "sending keep alive");
-               charon->sender->send(charon->sender, packet);
+               DBG1(DBG_IKE, "sending keep alive to %#H", this->other_host);
+               charon->sender->send_no_marker(charon->sender, packet);
                diff = 0;
        }
        job = send_keepalive_job_create(this->ike_sa_id);
@@ -562,7 +575,12 @@ METHOD(ike_sa_t, send_dpd, status_t,
 {
        job_t *job;
        time_t diff, delay;
+       bool task_queued = FALSE;
 
+       if (this->state == IKE_PASSIVE)
+       {
+               return INVALID_STATE;
+       }
        delay = this->peer_cfg->get_dpd(this->peer_cfg);
        if (this->task_manager->busy(this->task_manager))
        {
@@ -578,9 +596,10 @@ METHOD(ike_sa_t, send_dpd, status_t,
                diff = now - last_in;
                if (!delay || diff >= delay)
                {
-                       /* to long ago, initiate dead peer detection */
+                       /* too long ago, initiate dead peer detection */
                        DBG1(DBG_IKE, "sending DPD request");
                        this->task_manager->queue_dpd(this->task_manager);
+                       task_queued = TRUE;
                        diff = 0;
                }
        }
@@ -590,7 +609,11 @@ METHOD(ike_sa_t, send_dpd, status_t,
                job = (job_t*)send_dpd_job_create(this->ike_sa_id);
                lib->scheduler->schedule_job(lib->scheduler, job, delay - diff);
        }
-       return this->task_manager->initiate(this->task_manager);
+       if (task_queued)
+       {
+               return this->task_manager->initiate(this->task_manager);
+       }
+       return SUCCESS;
 }
 
 METHOD(ike_sa_t, get_state, ike_sa_state_t,
@@ -602,6 +625,8 @@ METHOD(ike_sa_t, get_state, ike_sa_state_t,
 METHOD(ike_sa_t, set_state, void,
        private_ike_sa_t *this, ike_sa_state_t state)
 {
+       bool trigger_dpd = FALSE;
+
        DBG2(DBG_IKE, "IKE_SA %s[%d] state change: %N => %N",
                 get_name(this), this->unique_id,
                 ike_sa_state_names, this->state,
@@ -662,28 +687,27 @@ METHOD(ike_sa_t, set_state, void,
                                        lib->scheduler->schedule_job(lib->scheduler, job, t);
                                        DBG1(DBG_IKE, "maximum IKE_SA lifetime %ds", t);
                                }
-
-                               /* start DPD checks */
-                               if (this->peer_cfg->get_dpd(this->peer_cfg))
-                               {
-                                       send_dpd(this);
-                               }
+                               trigger_dpd = this->peer_cfg->get_dpd(this->peer_cfg);
                        }
                        break;
                }
-               case IKE_DELETING:
-               {
-                       /* delete may fail if a packet gets lost, so set a timeout */
-                       job_t *job = (job_t*)delete_ike_sa_job_create(this->ike_sa_id, TRUE);
-                       lib->scheduler->schedule_job(lib->scheduler, job,
-                                                                                HALF_OPEN_IKE_SA_TIMEOUT);
-                       break;
-               }
                default:
                        break;
        }
        charon->bus->ike_state_change(charon->bus, &this->public, state);
        this->state = state;
+
+       if (trigger_dpd)
+       {
+               if (supports_extension(this, EXT_DPD))
+               {
+                       send_dpd(this);
+               }
+               else
+               {
+                       DBG1(DBG_IKE, "DPD not supported by peer, disabled");
+               }
+       }
 }
 
 METHOD(ike_sa_t, reset, void,
@@ -712,72 +736,99 @@ METHOD(ike_sa_t, get_keymat, keymat_t*,
        return this->keymat;
 }
 
-METHOD(ike_sa_t, set_virtual_ip, void,
+METHOD(ike_sa_t, add_virtual_ip, void,
        private_ike_sa_t *this, bool local, host_t *ip)
 {
        if (local)
        {
-               DBG1(DBG_IKE, "installing new virtual IP %H", ip);
-               if (hydra->kernel_interface->add_ip(hydra->kernel_interface, ip,
-                                                                                       this->my_host) == SUCCESS)
+               char *iface;
+
+               if (hydra->kernel_interface->get_interface(hydra->kernel_interface,
+                                                                                                  this->my_host, &iface))
                {
-                       if (this->my_virtual_ip)
+                       DBG1(DBG_IKE, "installing new virtual IP %H", ip);
+                       if (hydra->kernel_interface->add_ip(hydra->kernel_interface,
+                                                                                               ip, -1, iface) == SUCCESS)
+                       {
+                               this->my_vips->insert_last(this->my_vips, ip->clone(ip));
+                       }
+                       else
                        {
-                               DBG1(DBG_IKE, "removing old virtual IP %H", this->my_virtual_ip);
-                               hydra->kernel_interface->del_ip(hydra->kernel_interface,
-                                                                                               this->my_virtual_ip);
+                               DBG1(DBG_IKE, "installing virtual IP %H failed", ip);
                        }
-                       DESTROY_IF(this->my_virtual_ip);
-                       this->my_virtual_ip = ip->clone(ip);
+                       free(iface);
                }
                else
                {
-                       DBG1(DBG_IKE, "installing virtual IP %H failed", ip);
-                       this->my_virtual_ip = NULL;
+                       DBG1(DBG_IKE, "looking up interface for virtual IP %H failed", ip);
                }
        }
        else
        {
-               DESTROY_IF(this->other_virtual_ip);
-               this->other_virtual_ip = ip->clone(ip);
+               this->other_vips->insert_last(this->other_vips, ip->clone(ip));
        }
 }
 
-METHOD(ike_sa_t, get_virtual_ip, host_t*,
+
+METHOD(ike_sa_t, clear_virtual_ips, void,
        private_ike_sa_t *this, bool local)
 {
-       if (local)
+       linked_list_t *vips = local ? this->my_vips : this->other_vips;
+       host_t *vip;
+
+       if (!local && vips->get_count(vips))
        {
-               return this->my_virtual_ip;
+               charon->bus->assign_vips(charon->bus, &this->public, FALSE);
        }
-       else
+       while (vips->remove_first(vips, (void**)&vip) == SUCCESS)
        {
-               return this->other_virtual_ip;
+               if (local)
+               {
+                       hydra->kernel_interface->del_ip(hydra->kernel_interface,
+                                                                                       vip, -1, TRUE);
+               }
+               vip->destroy(vip);
        }
 }
 
-METHOD(ike_sa_t, add_additional_address, void,
+METHOD(ike_sa_t, create_virtual_ip_enumerator, enumerator_t*,
+       private_ike_sa_t *this, bool local)
+{
+       if (local)
+       {
+               return this->my_vips->create_enumerator(this->my_vips);
+       }
+       return this->other_vips->create_enumerator(this->other_vips);
+}
+
+METHOD(ike_sa_t, add_peer_address, void,
        private_ike_sa_t *this, host_t *host)
 {
-       this->additional_addresses->insert_last(this->additional_addresses, host);
+       this->peer_addresses->insert_last(this->peer_addresses, host);
 }
 
-METHOD(ike_sa_t, create_additional_address_enumerator, enumerator_t*,
+METHOD(ike_sa_t, create_peer_address_enumerator, enumerator_t*,
        private_ike_sa_t *this)
 {
-       return this->additional_addresses->create_enumerator(
-                                                                                               this->additional_addresses);
+       if (this->peer_addresses->get_count(this->peer_addresses))
+       {
+               return this->peer_addresses->create_enumerator(this->peer_addresses);
+       }
+       /* in case we don't have MOBIKE */
+       return enumerator_create_single(this->other_host, NULL);
 }
 
-METHOD(ike_sa_t, remove_additional_addresses, void,
+METHOD(ike_sa_t, clear_peer_addresses, void,
        private_ike_sa_t *this)
 {
-       enumerator_t *enumerator = create_additional_address_enumerator(this);
+       enumerator_t *enumerator;
        host_t *host;
+
+       enumerator = this->peer_addresses->create_enumerator(this->peer_addresses);
        while (enumerator->enumerate(enumerator, (void**)&host))
        {
-               this->additional_addresses->remove_at(this->additional_addresses,
-                                                                                         enumerator);
+               this->peer_addresses->remove_at(this->peer_addresses,
+                                                                               enumerator);
                host->destroy(host);
        }
        enumerator->destroy(enumerator);
@@ -816,9 +867,11 @@ METHOD(ike_sa_t, float_ports, void,
           private_ike_sa_t *this)
 {
        /* do not switch if we have a custom port from MOBIKE/NAT */
-       if (this->my_host->get_port(this->my_host) == IKEV2_UDP_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, IKEV2_NATT_PORT);
+               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)
        {
@@ -851,7 +904,7 @@ METHOD(ike_sa_t, update_hosts, void,
        else
        {
                /* update our address in any case */
-               if (!me->equals(me, this->my_host))
+               if (force && !me->equals(me, this->my_host))
                {
                        set_my_host(this, me->clone(me));
                        update = TRUE;
@@ -860,7 +913,8 @@ METHOD(ike_sa_t, update_hosts, void,
                if (!other->equals(other, this->other_host))
                {
                        /* update others address if we are NOT NATed */
-                       if (force || !has_condition(this, COND_NAT_HERE))
+                       if ((has_condition(this, COND_NAT_THERE) &&
+                                !has_condition(this, COND_NAT_HERE)) || force )
                        {
                                set_other_host(this, other->clone(other));
                                update = TRUE;
@@ -878,7 +932,7 @@ METHOD(ike_sa_t, update_hosts, void,
                while (enumerator->enumerate(enumerator, (void**)&child_sa))
                {
                        if (child_sa->update(child_sa, this->my_host,
-                                               this->other_host, this->my_virtual_ip,
+                                               this->other_host, this->my_vips,
                                                has_condition(this, COND_NAT_ANY)) == NOT_SUPPORTED)
                        {
                                this->public.rekey_child_sa(&this->public,
@@ -890,18 +944,50 @@ METHOD(ike_sa_t, update_hosts, void,
        }
 }
 
+/**
+ * Set configured DSCP value on packet
+ */
+static void set_dscp(private_ike_sa_t *this, packet_t *packet)
+{
+       ike_cfg_t *ike_cfg;
+
+       /* prefer IKE config on peer_cfg, as its selection is more accurate
+        * then the initial IKE config */
+       if (this->peer_cfg)
+       {
+               ike_cfg = this->peer_cfg->get_ike_cfg(this->peer_cfg);
+       }
+       else
+       {
+               ike_cfg = this->ike_cfg;
+       }
+       if (ike_cfg)
+       {
+               packet->set_dscp(packet, ike_cfg->get_dscp(ike_cfg));
+       }
+}
+
 METHOD(ike_sa_t, generate_message, status_t,
        private_ike_sa_t *this, message_t *message, packet_t **packet)
 {
+       status_t status;
+
        if (message->is_encoded(message))
-       {       /* already done */
+       {       /* already encoded in task, but set DSCP value */
                *packet = message->get_packet(message);
+               set_dscp(this, *packet);
                return SUCCESS;
        }
        this->stats[STAT_OUTBOUND] = time_monotonic(NULL);
        message->set_ike_sa_id(message, this->ike_sa_id);
-       charon->bus->message(charon->bus, message, FALSE);
-       return message->generate(message, this->keymat, packet);
+       charon->bus->message(charon->bus, message, FALSE, TRUE);
+       status = message->generate(message, this->keymat, packet);
+       if (status == SUCCESS)
+       {
+               set_dscp(this, *packet);
+               charon->bus->message(charon->bus, message, FALSE, FALSE);
+       }
+       return status;
 }
 
 METHOD(ike_sa_t, set_kmaddress, void,
@@ -1003,8 +1089,12 @@ static void resolve_hosts(private_ike_sa_t *this)
        }
        else
        {
-               host = host_create_from_dns(this->ike_cfg->get_other_addr(this->ike_cfg),
-                                                               0, this->ike_cfg->get_other_port(this->ike_cfg));
+               char *other_addr;
+               u_int16_t other_port;
+
+               other_addr = this->ike_cfg->get_other_addr(this->ike_cfg, NULL);
+               other_port = this->ike_cfg->get_other_port(this->ike_cfg);
+               host = host_create_from_dns(other_addr, 0, other_port);
        }
        if (host)
        {
@@ -1014,10 +1104,12 @@ static void resolve_hosts(private_ike_sa_t *this)
        if (this->local_host)
        {
                host = this->local_host->clone(this->local_host);
-               host->set_port(host, IKEV2_UDP_PORT);
+               host->set_port(host, charon->socket->get_port(charon->socket, FALSE));
        }
        else
        {
+               char *my_addr;
+               u_int16_t my_port;
                int family = 0;
 
                /* use same address family as for other */
@@ -1025,8 +1117,9 @@ static void resolve_hosts(private_ike_sa_t *this)
                {
                        family = this->other_host->get_family(this->other_host);
                }
-               host = host_create_from_dns(this->ike_cfg->get_my_addr(this->ike_cfg),
-                                                       family, this->ike_cfg->get_my_port(this->ike_cfg));
+               my_addr = this->ike_cfg->get_my_addr(this->ike_cfg, NULL);
+               my_port = this->ike_cfg->get_my_port(this->ike_cfg);
+               host = host_create_from_dns(my_addr, family, my_port);
 
                if (host && host->is_anyaddr(host) &&
                        !this->other_host->is_anyaddr(this->other_host))
@@ -1040,9 +1133,7 @@ static void resolve_hosts(private_ike_sa_t *this)
                        }
                        else
                        {       /* fallback to address family specific %any(6), if configured */
-                               host = host_create_from_dns(
-                                                               this->ike_cfg->get_my_addr(this->ike_cfg),
-                                                               0, this->ike_cfg->get_my_port(this->ike_cfg));
+                               host = host_create_from_dns(my_addr, 0, my_port);
                        }
                }
        }
@@ -1056,9 +1147,15 @@ METHOD(ike_sa_t, initiate, status_t,
        private_ike_sa_t *this, child_cfg_t *child_cfg, u_int32_t reqid,
        traffic_selector_t *tsi, traffic_selector_t *tsr)
 {
+       bool defer_initiate = FALSE;
+
        if (this->state == IKE_CREATED)
        {
-               resolve_hosts(this);
+               if (this->my_host->is_anyaddr(this->my_host) ||
+                       this->other_host->is_anyaddr(this->other_host))
+               {
+                       resolve_hosts(this);
+               }
 
                if (this->other_host->is_anyaddr(this->other_host)
 #ifdef ME
@@ -1066,10 +1163,27 @@ METHOD(ike_sa_t, initiate, status_t,
 #endif /* ME */
                        )
                {
-                       child_cfg->destroy(child_cfg);
-                       DBG1(DBG_IKE, "unable to initiate to %%any");
-                       charon->bus->alert(charon->bus, ALERT_PEER_ADDR_FAILED);
-                       return DESTROY_ME;
+                       char *addr = this->ike_cfg->get_other_addr(this->ike_cfg, NULL);
+                       bool is_anyaddr = streq(addr, "%any") || streq(addr, "%any6");
+
+                       if (is_anyaddr || !this->retry_initiate_interval)
+                       {
+                               if (is_anyaddr)
+                               {
+                                       DBG1(DBG_IKE, "unable to initiate to %s", addr);
+                               }
+                               else
+                               {
+                                       DBG1(DBG_IKE, "unable to resolve %s, initiate aborted",
+                                                addr);
+                               }
+                               DESTROY_IF(child_cfg);
+                               charon->bus->alert(charon->bus, ALERT_PEER_ADDR_FAILED);
+                               return DESTROY_ME;
+                       }
+                       DBG1(DBG_IKE, "unable to resolve %s, retrying in %ds",
+                                addr, this->retry_initiate_interval);
+                       defer_initiate = TRUE;
                }
 
                set_condition(this, COND_ORIGINAL_INITIATOR, TRUE);
@@ -1090,6 +1204,7 @@ METHOD(ike_sa_t, initiate, status_t,
        }
        else
 #endif /* ME */
+       if (child_cfg)
        {
                /* normal IKE_SA with CHILD_SA */
                this->task_manager->queue_child(this->task_manager, child_cfg, reqid,
@@ -1105,9 +1220,32 @@ METHOD(ike_sa_t, initiate, status_t,
 #endif /* ME */
        }
 
+       if (defer_initiate)
+       {
+               if (!this->retry_initiate_queued)
+               {
+                       job_t *job = (job_t*)retry_initiate_job_create(this->ike_sa_id);
+                       lib->scheduler->schedule_job(lib->scheduler, (job_t*)job,
+                                                                                this->retry_initiate_interval);
+                       this->retry_initiate_queued = TRUE;
+               }
+               return SUCCESS;
+       }
+       this->retry_initiate_queued = FALSE;
        return this->task_manager->initiate(this->task_manager);
 }
 
+METHOD(ike_sa_t, retry_initiate, status_t,
+       private_ike_sa_t *this)
+{
+       if (this->retry_initiate_queued)
+       {
+               this->retry_initiate_queued = FALSE;
+               return initiate(this, NULL, 0, NULL, NULL);
+       }
+       return SUCCESS;
+}
+
 METHOD(ike_sa_t, process_message, status_t,
        private_ike_sa_t *this, message_t *message)
 {
@@ -1182,6 +1320,10 @@ METHOD(ike_sa_t, get_other_eap_id, identification_t*,
                current = cfg->get(cfg, AUTH_RULE_EAP_IDENTITY);
                if (!current || current->get_type(current) == ID_ANY)
                {
+                       current = cfg->get(cfg, AUTH_RULE_XAUTH_IDENTITY);
+               }
+               if (!current || current->get_type(current) == ID_ANY)
+               {
                        current = cfg->get(cfg, AUTH_RULE_IDENTITY);
                }
                if (current && current->get_type(current) != ID_ANY)
@@ -1251,14 +1393,23 @@ 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)
 {
+       if (this->state == IKE_PASSIVE)
+       {
+               return INVALID_STATE;
+       }
        this->task_manager->queue_child_rekey(this->task_manager, protocol, spi);
        return this->task_manager->initiate(this->task_manager);
 }
 
 METHOD(ike_sa_t, delete_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, u_int32_t spi, bool expired)
 {
-       this->task_manager->queue_child_delete(this->task_manager, protocol, spi);
+       if (this->state == IKE_PASSIVE)
+       {
+               return INVALID_STATE;
+       }
+       this->task_manager->queue_child_delete(this->task_manager,
+                                                                                  protocol, spi, expired);
        return this->task_manager->initiate(this->task_manager);
 }
 
@@ -1290,8 +1441,18 @@ METHOD(ike_sa_t, delete_, status_t,
 {
        switch (this->state)
        {
-               case IKE_ESTABLISHED:
                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 */
+                               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);
                case IKE_CREATED:
@@ -1311,6 +1472,10 @@ METHOD(ike_sa_t, delete_, status_t,
 METHOD(ike_sa_t, rekey, status_t,
        private_ike_sa_t *this)
 {
+       if (this->state == IKE_PASSIVE)
+       {
+               return INVALID_STATE;
+       }
        this->task_manager->queue_ike_rekey(this->task_manager);
        return this->task_manager->initiate(this->task_manager);
 }
@@ -1318,13 +1483,18 @@ METHOD(ike_sa_t, rekey, status_t,
 METHOD(ike_sa_t, reauth, status_t,
        private_ike_sa_t *this)
 {
+       if (this->state == IKE_PASSIVE)
+       {
+               return INVALID_STATE;
+       }
        /* we can't reauthenticate as responder when we use EAP or virtual IPs.
         * If the peer does not support RFC4478, there is no way to keep the
         * IKE_SA up. */
        if (!has_condition(this, COND_ORIGINAL_INITIATOR))
        {
                DBG1(DBG_IKE, "initiator did not reauthenticate as requested");
-               if (this->other_virtual_ip != NULL ||
+               if (this->other_vips->get_count(this->other_vips) != 0 ||
+                       has_condition(this, COND_XAUTH_AUTHENTICATED) ||
                        has_condition(this, COND_EAP_AUTHENTICATED)
 #ifdef ME
                        /* as mediation server we too cannot reauth the IKE_SA */
@@ -1332,17 +1502,26 @@ METHOD(ike_sa_t, reauth, status_t,
 #endif /* ME */
                        )
                {
-                       time_t now = time_monotonic(NULL);
+                       time_t del, now;
 
-                       DBG1(DBG_IKE, "IKE_SA will timeout in %V",
-                                &now, &this->stats[STAT_DELETE]);
+                       del = this->stats[STAT_DELETE];
+                       now = time_monotonic(NULL);
+                       DBG1(DBG_IKE, "IKE_SA %s[%d] will timeout in %V",
+                                get_name(this), this->unique_id, &now, &del);
                        return FAILED;
                }
                else
                {
-                       DBG1(DBG_IKE, "reauthenticating actively");
+                       DBG0(DBG_IKE, "reauthenticating IKE_SA %s[%d] actively",
+                                get_name(this), this->unique_id);
                }
        }
+       else
+       {
+               DBG0(DBG_IKE, "reauthenticating IKE_SA %s[%d]",
+                        get_name(this), this->unique_id);
+       }
+       set_condition(this, COND_REAUTHENTICATING, TRUE);
        this->task_manager->queue_ike_reauth(this->task_manager);
        return this->task_manager->initiate(this->task_manager);
 }
@@ -1359,39 +1538,58 @@ METHOD(ike_sa_t, reestablish, status_t,
        bool restart = FALSE;
        status_t status = FAILED;
 
-       /* check if we have children to keep up at all */
-       enumerator = this->child_sas->create_enumerator(this->child_sas);
-       while (enumerator->enumerate(enumerator, (void**)&child_sa))
-       {
-               if (this->state == IKE_DELETING)
+       if (has_condition(this, COND_REAUTHENTICATING))
+       {       /* only reauthenticate if we have children */
+               if (this->child_sas->get_count(this->child_sas) == 0
+#ifdef ME
+                       /* allow reauth of mediation connections without CHILD_SAs */
+                       && !this->peer_cfg->is_mediation(this->peer_cfg)
+#endif /* ME */
+                       )
                {
-                       action = child_sa->get_close_action(child_sa);
+                       DBG1(DBG_IKE, "unable to reauthenticate IKE_SA, no CHILD_SA "
+                                "to recreate");
                }
                else
                {
-                       action = child_sa->get_dpd_action(child_sa);
+                       restart = TRUE;
                }
-               switch (action)
+       }
+       else
+       {       /* check if we have children to keep up at all */
+               enumerator = this->child_sas->create_enumerator(this->child_sas);
+               while (enumerator->enumerate(enumerator, (void**)&child_sa))
                {
-                       case ACTION_RESTART:
-                               restart = TRUE;
-                               break;
-                       case ACTION_ROUTE:
-                               charon->traps->install(charon->traps, this->peer_cfg,
-                                                                          child_sa->get_config(child_sa));
-                               break;
-                       default:
-                               break;
+                       if (this->state == IKE_DELETING)
+                       {
+                               action = child_sa->get_close_action(child_sa);
+                       }
+                       else
+                       {
+                               action = child_sa->get_dpd_action(child_sa);
+                       }
+                       switch (action)
+                       {
+                               case ACTION_RESTART:
+                                       restart = TRUE;
+                                       break;
+                               case ACTION_ROUTE:
+                                       charon->traps->install(charon->traps, this->peer_cfg,
+                                                                                  child_sa->get_config(child_sa));
+                                       break;
+                               default:
+                                       break;
+                       }
                }
-       }
-       enumerator->destroy(enumerator);
+               enumerator->destroy(enumerator);
 #ifdef ME
-       /* mediation connections have no children, keep them up anyway */
-       if (this->peer_cfg->is_mediation(this->peer_cfg))
-       {
-               restart = TRUE;
-       }
+               /* mediation connections have no children, keep them up anyway */
+               if (this->peer_cfg->is_mediation(this->peer_cfg))
+               {
+                       restart = TRUE;
+               }
 #endif /* ME */
+       }
        if (!restart)
        {
                return FAILED;
@@ -1399,7 +1597,7 @@ METHOD(ike_sa_t, reestablish, status_t,
 
        /* check if we are able to reestablish this IKE_SA */
        if (!has_condition(this, COND_ORIGINAL_INITIATOR) &&
-               (this->other_virtual_ip != NULL ||
+               (this->other_vips->get_count(this->other_vips) != 0 ||
                 has_condition(this, COND_EAP_AUTHENTICATED)
 #ifdef ME
                 || this->is_mediation_server
@@ -1412,17 +1610,22 @@ METHOD(ike_sa_t, reestablish, status_t,
 
        new = charon->ike_sa_manager->checkout_new(charon->ike_sa_manager,
                                                                                           this->version, TRUE);
+       if (!new)
+       {
+               return FAILED;
+       }
        new->set_peer_cfg(new, this->peer_cfg);
        host = this->other_host;
        new->set_other_host(new, host->clone(host));
        host = this->my_host;
        new->set_my_host(new, host->clone(host));
        /* if we already have a virtual IP, we reuse it */
-       host = this->my_virtual_ip;
-       if (host)
+       enumerator = this->my_vips->create_enumerator(this->my_vips);
+       while (enumerator->enumerate(enumerator, &host))
        {
-               new->set_virtual_ip(new, TRUE, host);
+               new->add_virtual_ip(new, TRUE, host);
        }
+       enumerator->destroy(enumerator);
 
 #ifdef ME
        if (this->peer_cfg->is_mediation(this->peer_cfg))
@@ -1435,13 +1638,34 @@ METHOD(ike_sa_t, reestablish, status_t,
                enumerator = this->child_sas->create_enumerator(this->child_sas);
                while (enumerator->enumerate(enumerator, (void**)&child_sa))
                {
-                       if (this->state == IKE_DELETING)
+                       if (has_condition(this, COND_REAUTHENTICATING))
                        {
-                               action = child_sa->get_close_action(child_sa);
+                               switch (child_sa->get_state(child_sa))
+                               {
+                                       case CHILD_ROUTED:
+                                       {       /* move routed child directly */
+                                               this->child_sas->remove_at(this->child_sas, enumerator);
+                                               new->add_child_sa(new, child_sa);
+                                               action = ACTION_NONE;
+                                               break;
+                                       }
+                                       default:
+                                       {       /* initiate/queue all other CHILD_SAs */
+                                               action = ACTION_RESTART;
+                                               break;
+                                       }
+                               }
                        }
                        else
-                       {
-                               action = child_sa->get_dpd_action(child_sa);
+                       {       /* only restart CHILD_SAs that are configured accordingly */
+                               if (this->state == IKE_DELETING)
+                               {
+                                       action = child_sa->get_close_action(child_sa);
+                               }
+                               else
+                               {
+                                       action = child_sa->get_dpd_action(child_sa);
+                               }
                        }
                        switch (action)
                        {
@@ -1470,6 +1694,7 @@ METHOD(ike_sa_t, reestablish, status_t,
        }
        else
        {
+               charon->bus->ike_reestablish(charon->bus, &this->public, new);
                charon->ike_sa_manager->checkin(charon->ike_sa_manager, new);
                status = SUCCESS;
        }
@@ -1480,6 +1705,10 @@ METHOD(ike_sa_t, reestablish, status_t,
 METHOD(ike_sa_t, retransmit, status_t,
        private_ike_sa_t *this, u_int32_t message_id)
 {
+       if (this->state == IKE_PASSIVE)
+       {
+               return INVALID_STATE;
+       }
        this->stats[STAT_OUTBOUND] = time_monotonic(NULL);
        if (this->task_manager->retransmit(this->task_manager, message_id) != SUCCESS)
        {
@@ -1490,12 +1719,15 @@ METHOD(ike_sa_t, retransmit, status_t,
                        {
                                /* retry IKE_SA_INIT/Main Mode if we have multiple keyingtries */
                                u_int32_t tries = this->peer_cfg->get_keyingtries(this->peer_cfg);
+                               charon->bus->alert(charon->bus, ALERT_PEER_INIT_UNREACHABLE,
+                                                                  this->keyingtry);
                                this->keyingtry++;
                                if (tries == 0 || tries > this->keyingtry)
                                {
                                        DBG1(DBG_IKE, "peer not responding, trying again (%d/%d)",
                                                 this->keyingtry + 1, tries);
                                        reset(this);
+                                       resolve_hosts(this);
                                        this->task_manager->queue_ike(this->task_manager);
                                        return this->task_manager->initiate(this->task_manager);
                                }
@@ -1504,6 +1736,12 @@ METHOD(ike_sa_t, retransmit, status_t,
                        }
                        case IKE_DELETING:
                                DBG1(DBG_IKE, "proper IKE_SA delete failed, peer not responding");
+                               if (has_condition(this, COND_REAUTHENTICATING))
+                               {
+                                       DBG1(DBG_IKE, "delete during reauthentication failed, "
+                                                "trying to reestablish IKE_SA anyway");
+                                       reestablish(this);
+                               }
                                break;
                        case IKE_REKEYING:
                                DBG1(DBG_IKE, "rekeying IKE_SA failed, peer not responding");
@@ -1512,40 +1750,79 @@ METHOD(ike_sa_t, retransmit, status_t,
                                reestablish(this);
                                break;
                }
+               if (this->state != IKE_CONNECTING)
+               {
+                       charon->bus->ike_updown(charon->bus, &this->public, FALSE);
+               }
                return DESTROY_ME;
        }
        return SUCCESS;
 }
 
-METHOD(ike_sa_t, set_auth_lifetime, void,
+METHOD(ike_sa_t, set_auth_lifetime, status_t,
        private_ike_sa_t *this, u_int32_t lifetime)
 {
-       u_int32_t reduction = this->peer_cfg->get_over_time(this->peer_cfg);
-       u_int32_t reauth_time = time_monotonic(NULL) + lifetime - reduction;
+       u_int32_t diff, hard, soft, now;
+       bool send_update;
+
+       diff = this->peer_cfg->get_over_time(this->peer_cfg);
+       now = time_monotonic(NULL);
+       hard = now + lifetime;
+       soft = hard - diff;
 
-       if (lifetime < reduction)
+       /* check if we have to send an AUTH_LIFETIME to enforce the new lifetime.
+        * We send the notify in IKE_AUTH if not yet ESTABLISHED. */
+       send_update = this->state == IKE_ESTABLISHED && this->version == IKEV2 &&
+                                 !has_condition(this, COND_ORIGINAL_INITIATOR) &&
+                                 (this->other_vips->get_count(this->other_vips) != 0 ||
+                                 has_condition(this, COND_EAP_AUTHENTICATED));
+
+       if (lifetime < diff)
        {
-               DBG1(DBG_IKE, "received AUTH_LIFETIME of %ds, starting reauthentication",
-                        lifetime);
-               lib->processor->queue_job(lib->processor,
+               this->stats[STAT_REAUTH] = now;
+
+               if (!send_update)
+               {
+                       DBG1(DBG_IKE, "received AUTH_LIFETIME of %ds, "
+                                "starting reauthentication", lifetime);
+                       lib->processor->queue_job(lib->processor,
                                        (job_t*)rekey_ike_sa_job_create(this->ike_sa_id, TRUE));
+               }
        }
        else if (this->stats[STAT_REAUTH] == 0 ||
-                        this->stats[STAT_REAUTH] > reauth_time)
+                        this->stats[STAT_REAUTH] > soft)
        {
-               this->stats[STAT_REAUTH] = reauth_time;
-               DBG1(DBG_IKE, "received AUTH_LIFETIME of %ds, scheduling reauthentication"
-                        " in %ds", lifetime, lifetime - reduction);
-               lib->scheduler->schedule_job(lib->scheduler,
+               this->stats[STAT_REAUTH] = soft;
+               if (!send_update)
+               {
+                       DBG1(DBG_IKE, "received AUTH_LIFETIME of %ds, scheduling "
+                                "reauthentication in %ds", lifetime, lifetime - diff);
+                       lib->scheduler->schedule_job(lib->scheduler,
                                                (job_t*)rekey_ike_sa_job_create(this->ike_sa_id, TRUE),
-                                               lifetime - reduction);
+                                               lifetime - diff);
+               }
        }
        else
        {
                DBG1(DBG_IKE, "received AUTH_LIFETIME of %ds, "
                         "reauthentication already scheduled in %ds", lifetime,
                         this->stats[STAT_REAUTH] - time_monotonic(NULL));
+               send_update = FALSE;
        }
+       /* give at least some seconds to reauthenticate */
+       this->stats[STAT_DELETE] = max(hard, now + 10);
+
+#ifdef USE_IKEV2
+       if (send_update)
+       {
+               ike_auth_lifetime_t *task;
+
+               task = ike_auth_lifetime_create(&this->public, TRUE);
+               this->task_manager->queue_task(this->task_manager, &task->task);
+               return this->task_manager->initiate(this->task_manager);
+       }
+#endif
+       return SUCCESS;
 }
 
 /**
@@ -1576,26 +1853,21 @@ static bool is_any_path_valid(private_ike_sa_t *this)
 {
        bool valid = FALSE;
        enumerator_t *enumerator;
-       host_t *src, *addr;
+       host_t *src = NULL, *addr;
+
        DBG1(DBG_IKE, "old path is not available anymore, try to find another");
-       src = hydra->kernel_interface->get_source_addr(hydra->kernel_interface,
-                                                                                                  this->other_host, NULL);
-       if (!src)
+       enumerator = create_peer_address_enumerator(this);
+       while (enumerator->enumerate(enumerator, &addr))
        {
-               enumerator = this->additional_addresses->create_enumerator(
-                                                                                               this->additional_addresses);
-               while (enumerator->enumerate(enumerator, &addr))
+               DBG1(DBG_IKE, "looking for a route to %H ...", addr);
+               src = hydra->kernel_interface->get_source_addr(
+                                                                               hydra->kernel_interface, addr, NULL);
+               if (src)
                {
-                       DBG1(DBG_IKE, "looking for a route to %H ...", addr);
-                       src = hydra->kernel_interface->get_source_addr(
-                                                                       hydra->kernel_interface, addr, NULL);
-                       if (src)
-                       {
-                               break;
-                       }
+                       break;
                }
-               enumerator->destroy(enumerator);
        }
+       enumerator->destroy(enumerator);
        if (src)
        {
                valid = TRUE;
@@ -1667,6 +1939,8 @@ METHOD(ike_sa_t, roam, status_t,
                return SUCCESS;
        }
        DBG1(DBG_IKE, "reauthenticating IKE_SA due to address change");
+       /* since our previous path is not valid anymore, try and find a new one */
+       resolve_hosts(this);
        return reauth(this);
 }
 
@@ -1689,6 +1963,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, flush_queue, void,
+       private_ike_sa_t *this, task_queue_t queue)
+{
+       this->task_manager->flush_queue(this->task_manager, queue);
+}
+
 METHOD(ike_sa_t, queue_task, void,
        private_ike_sa_t *this, task_t *task)
 {
@@ -1703,6 +1983,7 @@ METHOD(ike_sa_t, inherit, void,
        attribute_entry_t *entry;
        enumerator_t *enumerator;
        auth_cfg_t *cfg;
+       host_t *vip;
 
        /* apply hosts and ids */
        this->my_host->destroy(this->my_host);
@@ -1714,16 +1995,15 @@ METHOD(ike_sa_t, inherit, void,
        this->my_id = other->my_id->clone(other->my_id);
        this->other_id = other->other_id->clone(other->other_id);
 
-       /* apply virtual assigned IPs... */
-       if (other->my_virtual_ip)
+       /* apply assigned virtual IPs... */
+       while (other->my_vips->remove_last(other->my_vips, (void**)&vip) == SUCCESS)
        {
-               this->my_virtual_ip = other->my_virtual_ip;
-               other->my_virtual_ip = NULL;
+               this->my_vips->insert_first(this->my_vips, vip);
        }
-       if (other->other_virtual_ip)
+       while (other->other_vips->remove_last(other->other_vips,
+                                                                                 (void**)&vip) == SUCCESS)
        {
-               this->other_virtual_ip = other->other_virtual_ip;
-               other->other_virtual_ip = NULL;
+               this->other_vips->insert_first(this->other_vips, vip);
        }
 
        /* authentication information */
@@ -1798,11 +2078,12 @@ METHOD(ike_sa_t, destroy, void,
        private_ike_sa_t *this)
 {
        attribute_entry_t *entry;
+       host_t *vip;
 
        charon->bus->set_sa(charon->bus, &this->public);
 
        set_state(this, IKE_DESTROYING);
-       this->task_manager->destroy(this->task_manager);
+       DESTROY_IF(this->task_manager);
 
        /* remove attributes first, as we pass the IKE_SA to the handler */
        while (this->attributes->remove_last(this->attributes,
@@ -1813,33 +2094,42 @@ METHOD(ike_sa_t, destroy, void,
                free(entry->data.ptr);
                free(entry);
        }
-       this->attributes->destroy(this->attributes);
-
-       this->child_sas->destroy_offset(this->child_sas, offsetof(child_sa_t, destroy));
-
-       /* unset SA after here to avoid usage by the listeners */
-       charon->bus->set_sa(charon->bus, NULL);
-
-       this->keymat->destroy(this->keymat);
-
-       if (this->my_virtual_ip)
+       while (this->my_vips->remove_last(this->my_vips, (void**)&vip) == SUCCESS)
+       {
+               hydra->kernel_interface->del_ip(hydra->kernel_interface, vip, -1, TRUE);
+               vip->destroy(vip);
+       }
+       if (this->other_vips->get_count(this->other_vips))
        {
-               hydra->kernel_interface->del_ip(hydra->kernel_interface,
-                                                                               this->my_virtual_ip);
-               this->my_virtual_ip->destroy(this->my_virtual_ip);
+               charon->bus->assign_vips(charon->bus, &this->public, FALSE);
        }
-       if (this->other_virtual_ip)
+       while (this->other_vips->remove_last(this->other_vips,
+                                                                                (void**)&vip) == SUCCESS)
        {
-               if (this->peer_cfg && this->peer_cfg->get_pool(this->peer_cfg))
+               if (this->peer_cfg)
                {
-                       hydra->attributes->release_address(hydra->attributes,
-                                                       this->peer_cfg->get_pool(this->peer_cfg),
-                                                       this->other_virtual_ip, get_other_eap_id(this));
+                       linked_list_t *pools;
+                       identification_t *id;
+
+                       id = get_other_eap_id(this);
+                       pools = linked_list_create_from_enumerator(
+                                               this->peer_cfg->create_pool_enumerator(this->peer_cfg));
+                       hydra->attributes->release_address(hydra->attributes, pools, vip, id);
+                       pools->destroy(pools);
                }
-               this->other_virtual_ip->destroy(this->other_virtual_ip);
+               vip->destroy(vip);
        }
-       this->additional_addresses->destroy_offset(this->additional_addresses,
-                                                                                                       offsetof(host_t, destroy));
+
+       /* unset SA after here to avoid usage by the listeners */
+       charon->bus->set_sa(charon->bus, NULL);
+
+       this->child_sas->destroy_offset(this->child_sas, offsetof(child_sa_t, destroy));
+       DESTROY_IF(this->keymat);
+       this->attributes->destroy(this->attributes);
+       this->my_vips->destroy(this->my_vips);
+       this->other_vips->destroy(this->other_vips);
+       this->peer_addresses->destroy_offset(this->peer_addresses,
+                                                                                offsetof(host_t, destroy));
 #ifdef ME
        if (this->is_mediation_server)
        {
@@ -1881,6 +2171,15 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id, bool initiator,
        private_ike_sa_t *this;
        static u_int32_t unique_id = 0;
 
+       if (version == IKE_ANY)
+       {       /* prefer IKEv2 if protocol not specified */
+#ifdef USE_IKEV2
+               version = IKEV2;
+#else
+               version = IKEV1;
+#endif
+       }
+
        INIT(this,
                .public = {
                        .get_version = _get_version,
@@ -1891,6 +2190,7 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id, bool initiator,
                        .set_statistic = _set_statistic,
                        .process_message = _process_message,
                        .initiate = _initiate,
+                       .retry_initiate = _retry_initiate,
                        .get_ike_cfg = _get_ike_cfg,
                        .set_ike_cfg = _set_ike_cfg,
                        .get_peer_cfg = _get_peer_cfg,
@@ -1919,9 +2219,9 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id, bool initiator,
                        .has_condition = _has_condition,
                        .set_pending_updates = _set_pending_updates,
                        .get_pending_updates = _get_pending_updates,
-                       .create_additional_address_enumerator = _create_additional_address_enumerator,
-                       .add_additional_address = _add_additional_address,
-                       .remove_additional_addresses = _remove_additional_addresses,
+                       .create_peer_address_enumerator = _create_peer_address_enumerator,
+                       .add_peer_address = _add_peer_address,
+                       .clear_peer_addresses = _clear_peer_addresses,
                        .has_mapping_changed = _has_mapping_changed,
                        .retransmit = _retransmit,
                        .delete = _delete_,
@@ -1946,11 +2246,13 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id, bool initiator,
                        .generate_message = _generate_message,
                        .reset = _reset,
                        .get_unique_id = _get_unique_id,
-                       .set_virtual_ip = _set_virtual_ip,
-                       .get_virtual_ip = _get_virtual_ip,
+                       .add_virtual_ip = _add_virtual_ip,
+                       .clear_virtual_ips = _clear_virtual_ips,
+                       .create_virtual_ip_enumerator = _create_virtual_ip_enumerator,
                        .add_configuration_attribute = _add_configuration_attribute,
                        .set_kmaddress = _set_kmaddress,
                        .create_task_enumerator = _create_task_enumerator,
+                       .flush_queue = _flush_queue,
                        .queue_task = _queue_task,
 #ifdef ME
                        .act_as_mediation_server = _act_as_mediation_server,
@@ -1980,19 +2282,32 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id, bool initiator,
                .my_auths = linked_list_create(),
                .other_auths = linked_list_create(),
                .unique_id = ++unique_id,
-               .additional_addresses = linked_list_create(),
+               .peer_addresses = linked_list_create(),
+               .my_vips = linked_list_create(),
+               .other_vips = linked_list_create(),
                .attributes = linked_list_create(),
                .keepalive_interval = lib->settings->get_time(lib->settings,
-                                                                       "charon.keep_alive", KEEPALIVE_INTERVAL),
+                                                       "%s.keep_alive", KEEPALIVE_INTERVAL, charon->name),
+               .retry_initiate_interval = lib->settings->get_time(lib->settings,
+                                                       "%s.retry_initiate_interval", 0, charon->name),
                .flush_auth_cfg = lib->settings->get_bool(lib->settings,
-                                                                       "charon.flush_auth_cfg", FALSE),
+                                                       "%s.flush_auth_cfg", FALSE, charon->name),
        );
 
-       this->task_manager = task_manager_create(&this->public);
-       this->my_host->set_port(this->my_host, IKEV2_UDP_PORT);
+       if (version == IKEV2)
+       {       /* always supported with IKEv2 */
+               enable_extension(this, EXT_DPD);
+       }
 
-       /* TODO-IKEv1: check if keymat and task manager created successfully.
-        * Return NULL otherwise? */
+       this->task_manager = task_manager_create(&this->public);
+       this->my_host->set_port(this->my_host,
+                                                       charon->socket->get_port(charon->socket, FALSE));
 
+       if (!this->task_manager || !this->keymat)
+       {
+               DBG1(DBG_IKE, "IKE version %d not supported", this->version);
+               destroy(this);
+               return NULL;
+       }
        return &this->public;
 }