payload: Use common prefixes for all payload type identifiers
[strongswan.git] / src / libcharon / sa / ikev1 / tasks / quick_mode.c
index 63b59e0..e627368 100644 (file)
  * for more details.
  */
 
+/*
+ * Copyright (C) 2012 Volker RĂ¼melin
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
 #include "quick_mode.h"
 
 #include <string.h>
@@ -143,6 +165,11 @@ struct private_quick_mode_t {
         */
        ipsec_mode_t mode;
 
+       /*
+        * SA protocol (ESP|AH) negotiated
+        */
+       protocol_id_t proto;
+
        /**
         * Use UDP encapsulation
         */
@@ -167,7 +194,7 @@ static void schedule_inactivity_timeout(private_quick_mode_t *this)
        if (timeout)
        {
                close_ike = lib->settings->get_bool(lib->settings,
-                                                               "%s.inactivity_close_ike", FALSE, charon->name);
+                                                                       "%s.inactivity_close_ike", FALSE, lib->ns);
                lib->scheduler->schedule_job(lib->scheduler, (job_t*)
                                inactivity_job_create(this->child_sa->get_reqid(this->child_sa),
                                                                          timeout, close_ike), timeout);
@@ -175,13 +202,69 @@ static void schedule_inactivity_timeout(private_quick_mode_t *this)
 }
 
 /**
+ * Check if we have a an address pool configured
+ */
+static bool have_pool(ike_sa_t *ike_sa)
+{
+       enumerator_t *enumerator;
+       peer_cfg_t *peer_cfg;
+       char *pool;
+       bool found = FALSE;
+
+       peer_cfg = ike_sa->get_peer_cfg(ike_sa);
+       if (peer_cfg)
+       {
+               enumerator = peer_cfg->create_pool_enumerator(peer_cfg);
+               if (enumerator->enumerate(enumerator, &pool))
+               {
+                       found = TRUE;
+               }
+               enumerator->destroy(enumerator);
+       }
+       return found;
+}
+
+/**
+ * Get hosts to use for dynamic traffic selectors
+ */
+static linked_list_t *get_dynamic_hosts(ike_sa_t *ike_sa, bool local)
+{
+       enumerator_t *enumerator;
+       linked_list_t *list;
+       host_t *host;
+
+       list = linked_list_create();
+       enumerator = ike_sa->create_virtual_ip_enumerator(ike_sa, local);
+       while (enumerator->enumerate(enumerator, &host))
+       {
+               list->insert_last(list, host);
+       }
+       enumerator->destroy(enumerator);
+
+       if (list->get_count(list) == 0)
+       {       /* no virtual IPs assigned */
+               if (local)
+               {
+                       host = ike_sa->get_my_host(ike_sa);
+                       list->insert_last(list, host);
+               }
+               else if (!have_pool(ike_sa))
+               {       /* use host only if we don't have a pool configured */
+                       host = ike_sa->get_other_host(ike_sa);
+                       list->insert_last(list, host);
+               }
+       }
+       return list;
+}
+
+/**
  * Install negotiated CHILD_SA
  */
 static bool install(private_quick_mode_t *this)
 {
        status_t status, status_i, status_o;
        chunk_t encr_i, encr_r, integ_i, integ_r;
-       linked_list_t *tsi, *tsr;
+       linked_list_t *tsi, *tsr, *my_ts, *other_ts;
        child_sa_t *old = NULL;
 
        this->child_sa->set_proposal(this->child_sa, this->proposal);
@@ -202,10 +285,8 @@ static bool install(private_quick_mode_t *this)
 
        status_i = status_o = FAILED;
        encr_i = encr_r = integ_i = integ_r = chunk_empty;
-       tsi = linked_list_create();
-       tsr = linked_list_create();
-       tsi->insert_last(tsi, this->tsi->clone(this->tsi));
-       tsr->insert_last(tsr, this->tsr->clone(this->tsr));
+       tsi = linked_list_create_with_items(this->tsi->clone(this->tsi), NULL);
+       tsr = linked_list_create_with_items(this->tsr->clone(this->tsr), NULL);
        if (this->initiator)
        {
                charon->bus->narrow(charon->bus, this->child_sa,
@@ -214,7 +295,7 @@ static bool install(private_quick_mode_t *this)
        else
        {
                charon->bus->narrow(charon->bus, this->child_sa,
-                                                       NARROW_RESPONDER, tsr, tsi);
+                                                       NARROW_RESPONDER_POST, tsr, tsi);
        }
        if (tsi->get_count(tsi) == 0 || tsr->get_count(tsr) == 0)
        {
@@ -230,17 +311,21 @@ static bool install(private_quick_mode_t *this)
        {
                if (this->initiator)
                {
-                       status_i = this->child_sa->install(this->child_sa, encr_r, integ_r,
-                                                       this->spi_i, this->cpi_i, TRUE, FALSE, tsi, tsr);
-                       status_o = this->child_sa->install(this->child_sa, encr_i, integ_i,
-                                                       this->spi_r, this->cpi_r, FALSE, FALSE, tsi, tsr);
+                       status_i = this->child_sa->install(this->child_sa,
+                                                                       encr_r, integ_r, this->spi_i, this->cpi_i,
+                                                                       this->initiator, TRUE, FALSE, tsi, tsr);
+                       status_o = this->child_sa->install(this->child_sa,
+                                                                       encr_i, integ_i, this->spi_r, this->cpi_r,
+                                                                       this->initiator, FALSE, FALSE, tsi, tsr);
                }
                else
                {
-                       status_i = this->child_sa->install(this->child_sa, encr_i, integ_i,
-                                                       this->spi_r, this->cpi_r, TRUE, FALSE, tsr, tsi);
-                       status_o = this->child_sa->install(this->child_sa, encr_r, integ_r,
-                                                       this->spi_i, this->cpi_i, FALSE, FALSE, tsr, tsi);
+                       status_i = this->child_sa->install(this->child_sa,
+                                                                       encr_i, integ_i, this->spi_r, this->cpi_r,
+                                                                       this->initiator, TRUE, FALSE, tsr, tsi);
+                       status_o = this->child_sa->install(this->child_sa,
+                                                                       encr_r, integ_r, this->spi_i, this->cpi_i,
+                                                                       this->initiator, FALSE, FALSE, tsr, tsi);
                }
        }
        chunk_clear(&integ_i);
@@ -282,14 +367,20 @@ static bool install(private_quick_mode_t *this)
        this->child_sa->set_state(this->child_sa, CHILD_INSTALLED);
        this->ike_sa->add_child_sa(this->ike_sa, this->child_sa);
 
+       my_ts = linked_list_create_from_enumerator(
+                               this->child_sa->create_ts_enumerator(this->child_sa, TRUE));
+       other_ts = linked_list_create_from_enumerator(
+                               this->child_sa->create_ts_enumerator(this->child_sa, FALSE));
+
        DBG0(DBG_IKE, "CHILD_SA %s{%d} established "
                 "with SPIs %.8x_i %.8x_o and TS %#R=== %#R",
                 this->child_sa->get_name(this->child_sa),
                 this->child_sa->get_reqid(this->child_sa),
                 ntohl(this->child_sa->get_spi(this->child_sa, TRUE)),
-                ntohl(this->child_sa->get_spi(this->child_sa, FALSE)),
-                this->child_sa->get_traffic_selectors(this->child_sa, TRUE),
-                this->child_sa->get_traffic_selectors(this->child_sa, FALSE));
+                ntohl(this->child_sa->get_spi(this->child_sa, FALSE)), my_ts, other_ts);
+
+       my_ts->destroy(my_ts);
+       other_ts->destroy(other_ts);
 
        if (this->rekey)
        {
@@ -328,10 +419,15 @@ static bool add_nonce(private_quick_mode_t *this, chunk_t *nonce,
                DBG1(DBG_IKE, "no nonce generator found to create nonce");
                return FALSE;
        }
-       nonceg->allocate_nonce(nonceg, NONCE_SIZE, nonce);
+       if (!nonceg->allocate_nonce(nonceg, NONCE_SIZE, nonce))
+       {
+               DBG1(DBG_IKE, "nonce allocation failed");
+               nonceg->destroy(nonceg);
+               return FALSE;
+       }
        nonceg->destroy(nonceg);
 
-       nonce_payload = nonce_payload_create(NONCE_V1);
+       nonce_payload = nonce_payload_create(PLV1_NONCE);
        nonce_payload->set_nonce(nonce_payload, *nonce);
        message->add_payload(message, &nonce_payload->payload_interface);
 
@@ -346,7 +442,7 @@ static bool get_nonce(private_quick_mode_t *this, chunk_t *nonce,
 {
        nonce_payload_t *nonce_payload;
 
-       nonce_payload = (nonce_payload_t*)message->get_payload(message, NONCE_V1);
+       nonce_payload = (nonce_payload_t*)message->get_payload(message, PLV1_NONCE);
        if (!nonce_payload)
        {
                DBG1(DBG_IKE, "NONCE payload missing in message");
@@ -364,7 +460,7 @@ static void add_ke(private_quick_mode_t *this, message_t *message)
 {
        ke_payload_t *ke_payload;
 
-       ke_payload = ke_payload_create_from_diffie_hellman(KEY_EXCHANGE_V1, this->dh);
+       ke_payload = ke_payload_create_from_diffie_hellman(PLV1_KEY_EXCHANGE, this->dh);
        message->add_payload(message, &ke_payload->payload_interface);
 }
 
@@ -375,7 +471,7 @@ static bool get_ke(private_quick_mode_t *this, message_t *message)
 {
        ke_payload_t *ke_payload;
 
-       ke_payload = (ke_payload_t*)message->get_payload(message, KEY_EXCHANGE_V1);
+       ke_payload = (ke_payload_t*)message->get_payload(message, PLV1_KEY_EXCHANGE);
        if (!ke_payload)
        {
                DBG1(DBG_IKE, "KE payload missing");
@@ -393,36 +489,20 @@ static traffic_selector_t* select_ts(private_quick_mode_t *this, bool local,
                                                                         linked_list_t *supplied)
 {
        traffic_selector_t *ts;
-       linked_list_t *list;
-       host_t *host;
+       linked_list_t *list, *hosts;
 
-       host = this->ike_sa->get_virtual_ip(this->ike_sa, local);
-       if (!host)
-       {
-               if (local)
-               {
-                       host = this->ike_sa->get_my_host(this->ike_sa);
-               }
-               else
-               {
-                       host = this->ike_sa->get_other_host(this->ike_sa);
-               }
-       }
-       list = this->config->get_traffic_selectors(this->config, local,
-                                                                                          supplied, host);
+       hosts = get_dynamic_hosts(this->ike_sa, local);
+       list = this->config->get_traffic_selectors(this->config,
+                                                                                          local, supplied, hosts);
+       hosts->destroy(hosts);
        if (list->get_first(list, (void**)&ts) == SUCCESS)
        {
-               if (list->get_count(list) > 1)
-               {
-                       DBG1(DBG_IKE, "configuration has more than one %s traffic selector,"
-                                " using first only", local ? "local" : "remote");
-               }
                ts = ts->clone(ts);
        }
        else
        {
                DBG1(DBG_IKE, "%s traffic selector missing in configuration",
-                        local ? "local" : "local");
+                        local ? "local" : "remote");
                ts = NULL;
        }
        list->destroy_offset(list, offsetof(traffic_selector_t, destroy));
@@ -435,33 +515,11 @@ static traffic_selector_t* select_ts(private_quick_mode_t *this, bool local,
 static void add_ts(private_quick_mode_t *this, message_t *message)
 {
        id_payload_t *id_payload;
-       host_t *hsi, *hsr;
 
-       if (this->initiator)
-       {
-               hsi = this->ike_sa->get_my_host(this->ike_sa);
-               hsr = this->ike_sa->get_other_host(this->ike_sa);
-       }
-       else
-       {
-               hsr = this->ike_sa->get_my_host(this->ike_sa);
-               hsi = this->ike_sa->get_other_host(this->ike_sa);
-       }
-       /* add ID payload only if negotiating non host2host tunnels */
-       if (!this->tsi->is_host(this->tsi, hsi) ||
-               !this->tsr->is_host(this->tsr, hsr) ||
-               this->tsi->get_protocol(this->tsi) ||
-               this->tsr->get_protocol(this->tsr) ||
-               this->tsi->get_from_port(this->tsi) ||
-               this->tsr->get_from_port(this->tsr) ||
-               this->tsi->get_to_port(this->tsi) != 65535 ||
-               this->tsr->get_to_port(this->tsr) != 65535)
-       {
-               id_payload = id_payload_create_from_ts(this->tsi);
-               message->add_payload(message, &id_payload->payload_interface);
-               id_payload = id_payload_create_from_ts(this->tsr);
-               message->add_payload(message, &id_payload->payload_interface);
-       }
+       id_payload = id_payload_create_from_ts(this->tsi);
+       message->add_payload(message, &id_payload->payload_interface);
+       id_payload = id_payload_create_from_ts(this->tsr);
+       message->add_payload(message, &id_payload->payload_interface);
 }
 
 /**
@@ -479,7 +537,7 @@ static bool get_ts(private_quick_mode_t *this, message_t *message)
        enumerator = message->create_payload_enumerator(message);
        while (enumerator->enumerate(enumerator, &payload))
        {
-               if (payload->get_type(payload) == ID_V1)
+               if (payload->get_type(payload) == PLV1_ID)
                {
                        id_payload = (id_payload_t*)payload;
 
@@ -511,29 +569,45 @@ static bool get_ts(private_quick_mode_t *this, message_t *message)
        if (!tsi)
        {
                tsi = traffic_selector_create_from_subnet(hsi->clone(hsi),
-                                                       hsi->get_family(hsi) == AF_INET ? 32 : 128, 0, 0);
+                                       hsi->get_family(hsi) == AF_INET ? 32 : 128, 0, 0, 65535);
        }
        if (!tsr)
        {
                tsr = traffic_selector_create_from_subnet(hsr->clone(hsr),
-                                                       hsr->get_family(hsr) == AF_INET ? 32 : 128, 0, 0);
+                                       hsr->get_family(hsr) == AF_INET ? 32 : 128, 0, 0, 65535);
+       }
+       if (this->mode == MODE_TRANSPORT && this->udp &&
+          (!tsi->is_host(tsi, hsi) || !tsr->is_host(tsr, hsr)))
+       {       /* change TS in case of a NAT in transport mode */
+               DBG2(DBG_IKE, "changing received traffic selectors %R=== %R due to NAT",
+                        tsi, tsr);
+               tsi->set_address(tsi, hsi);
+               tsr->set_address(tsr, hsr);
        }
+
        if (this->initiator)
        {
-               /* check if peer selection valid */
-               if (!tsr->is_contained_in(tsr, this->tsr) ||
-                       !tsi->is_contained_in(tsi, this->tsi))
+               traffic_selector_t *tsisub, *tsrsub;
+
+               /* check if peer selection is valid */
+               tsisub = this->tsi->get_subset(this->tsi, tsi);
+               tsrsub = this->tsr->get_subset(this->tsr, tsr);
+               if (!tsisub || !tsrsub)
                {
-                       DBG1(DBG_IKE, "peer selected invalid traffic selectors: ",
+                       DBG1(DBG_IKE, "peer selected invalid traffic selectors: "
                                 "%R for %R, %R for %R", tsi, this->tsi, tsr, this->tsr);
+                       DESTROY_IF(tsisub);
+                       DESTROY_IF(tsrsub);
                        tsi->destroy(tsi);
                        tsr->destroy(tsr);
                        return FALSE;
                }
+               tsi->destroy(tsi);
+               tsr->destroy(tsr);
                this->tsi->destroy(this->tsi);
                this->tsr->destroy(this->tsr);
-               this->tsi = tsi;
-               this->tsr = tsr;
+               this->tsi = tsisub;
+               this->tsr = tsrsub;
        }
        else
        {
@@ -544,6 +618,34 @@ static bool get_ts(private_quick_mode_t *this, message_t *message)
 }
 
 /**
+ * Get encap
+ */
+static encap_t get_encap(ike_sa_t* ike_sa, bool udp)
+{
+       if (!udp)
+       {
+               return ENCAP_NONE;
+       }
+       if (ike_sa->supports_extension(ike_sa, EXT_NATT_DRAFT_02_03))
+       {
+               return ENCAP_UDP_DRAFT_00_03;
+       }
+       return ENCAP_UDP;
+}
+
+/**
+ * Get NAT-OA payload type (RFC 3947 or RFC 3947 drafts).
+ */
+static payload_type_t get_nat_oa_payload_type(ike_sa_t *ike_sa)
+{
+       if (ike_sa->supports_extension(ike_sa, EXT_NATT_DRAFT_02_03))
+       {
+               return PLV1_NAT_OA_DRAFT_00_03;
+       }
+       return PLV1_NAT_OA;
+}
+
+/**
  * Add NAT-OA payloads
  */
 static void add_nat_oa_payloads(private_quick_mode_t *this, message_t *message)
@@ -551,6 +653,7 @@ static void add_nat_oa_payloads(private_quick_mode_t *this, message_t *message)
        identification_t *id;
        id_payload_t *nat_oa;
        host_t *src, *dst;
+       payload_type_t nat_oa_payload_type;
 
        src = message->get_source(message);
        dst = message->get_destination(message);
@@ -558,15 +661,17 @@ static void add_nat_oa_payloads(private_quick_mode_t *this, message_t *message)
        src = this->initiator ? src : dst;
        dst = this->initiator ? dst : src;
 
+       nat_oa_payload_type = get_nat_oa_payload_type(this->ike_sa);
+
        /* first NAT-OA is the initiator's address */
        id = identification_create_from_sockaddr(src->get_sockaddr(src));
-       nat_oa = id_payload_create_from_identification(NAT_OA_V1, id);
+       nat_oa = id_payload_create_from_identification(nat_oa_payload_type, id);
        message->add_payload(message, (payload_t*)nat_oa);
        id->destroy(id);
 
        /* second NAT-OA is that of the responder */
        id = identification_create_from_sockaddr(dst->get_sockaddr(dst));
-       nat_oa = id_payload_create_from_identification(NAT_OA_V1, id);
+       nat_oa = id_payload_create_from_identification(nat_oa_payload_type, id);
        message->add_payload(message, (payload_t*)nat_oa);
        id->destroy(id);
 }
@@ -621,8 +726,8 @@ static status_t send_notify(private_quick_mode_t *this, notify_type_t type)
 {
        notify_payload_t *notify;
 
-       notify = notify_payload_create_from_protocol_and_type(NOTIFY_V1,
-                                                                                                                 PROTO_ESP, type);
+       notify = notify_payload_create_from_protocol_and_type(PLV1_NOTIFY,
+                                                                                                                 this->proto, type);
        notify->set_spi(notify, this->spi_i);
 
        this->ike_sa->queue_task(this->ike_sa,
@@ -633,6 +738,38 @@ static status_t send_notify(private_quick_mode_t *this, notify_type_t type)
        return ALREADY_DONE;
 }
 
+/**
+ * Prepare a list of proposals from child_config containing only the specified
+ * DH group, unless it is set to MODP_NONE.
+ */
+static linked_list_t *get_proposals(private_quick_mode_t *this,
+                                                                       diffie_hellman_group_t group)
+{
+       linked_list_t *list;
+       proposal_t *proposal;
+       enumerator_t *enumerator;
+
+       list = this->config->get_proposals(this->config, FALSE);
+       enumerator = list->create_enumerator(list);
+       while (enumerator->enumerate(enumerator, &proposal))
+       {
+               if (group != MODP_NONE)
+               {
+                       if (!proposal->has_dh_group(proposal, group))
+                       {
+                               list->remove_at(list, enumerator);
+                               proposal->destroy(proposal);
+                               continue;
+                       }
+                       proposal->strip_dh(proposal, group);
+               }
+               proposal->set_spi(proposal, this->spi_i);
+       }
+       enumerator->destroy(enumerator);
+
+       return list;
+}
+
 METHOD(task_t, build_i, status_t,
        private_quick_mode_t *this, message_t *message)
 {
@@ -640,11 +777,11 @@ METHOD(task_t, build_i, status_t,
        {
                case QM_INIT:
                {
-                       enumerator_t *enumerator;
                        sa_payload_t *sa_payload;
                        linked_list_t *list, *tsi, *tsr;
                        proposal_t *proposal;
                        diffie_hellman_group_t group;
+                       encap_t encap;
 
                        this->udp = this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY);
                        this->mode = this->config->get_mode(this->config);
@@ -661,41 +798,69 @@ METHOD(task_t, build_i, status_t,
 
                        if (this->config->use_ipcomp(this->config))
                        {
-                               if (this->udp)
-                               {
-                                       DBG1(DBG_IKE, "IPComp is not supported if either peer is "
-                                                "natted, IPComp disabled");
-                               }
-                               else
+                               this->cpi_i = this->child_sa->alloc_cpi(this->child_sa);
+                               if (!this->cpi_i)
                                {
-                                       this->cpi_i = this->child_sa->alloc_cpi(this->child_sa);
-                                       if (!this->cpi_i)
-                                       {
-                                               DBG1(DBG_IKE, "unable to allocate a CPI from kernel, "
-                                                        "IPComp disabled");
-                                       }
+                                       DBG1(DBG_IKE, "unable to allocate a CPI from kernel, "
+                                                "IPComp disabled");
                                }
                        }
 
-                       this->spi_i = this->child_sa->alloc_spi(this->child_sa, PROTO_ESP);
+                       list = this->config->get_proposals(this->config, MODP_NONE);
+                       if (list->get_first(list, (void**)&proposal) == SUCCESS)
+                       {
+                               this->proto = proposal->get_protocol(proposal);
+                       }
+                       list->destroy_offset(list, offsetof(proposal_t, destroy));
+                       this->spi_i = this->child_sa->alloc_spi(this->child_sa, this->proto);
                        if (!this->spi_i)
                        {
                                DBG1(DBG_IKE, "allocating SPI from kernel failed");
                                return FAILED;
                        }
 
-                       list = this->config->get_proposals(this->config, FALSE);
-                       enumerator = list->create_enumerator(list);
-                       while (enumerator->enumerate(enumerator, &proposal))
+                       group = this->config->get_dh_group(this->config);
+                       if (group != MODP_NONE)
+                       {
+                               proposal_t *proposal;
+                               u_int16_t preferred_group;
+
+                               proposal = this->ike_sa->get_proposal(this->ike_sa);
+                               proposal->get_algorithm(proposal, DIFFIE_HELLMAN_GROUP,
+                                                                               &preferred_group, NULL);
+                               /* try the negotiated DH group from IKE_SA */
+                               list = get_proposals(this, preferred_group);
+                               if (list->get_count(list))
+                               {
+                                       group = preferred_group;
+                               }
+                               else
+                               {
+                                       /* fall back to the first configured DH group */
+                                       list->destroy(list);
+                                       list = get_proposals(this, group);
+                               }
+
+                               this->dh = this->keymat->keymat.create_dh(&this->keymat->keymat,
+                                                                                                                 group);
+                               if (!this->dh)
+                               {
+                                       DBG1(DBG_IKE, "configured DH group %N not supported",
+                                                diffie_hellman_group_names, group);
+                                       list->destroy_offset(list, offsetof(proposal_t, destroy));
+                                       return FAILED;
+                               }
+                       }
+                       else
                        {
-                               proposal->set_spi(proposal, this->spi_i);
+                               list = get_proposals(this, MODP_NONE);
                        }
-                       enumerator->destroy(enumerator);
 
                        get_lifetimes(this);
+                       encap = get_encap(this->ike_sa, this->udp);
                        sa_payload = sa_payload_create_from_proposals_v1(list,
                                                                this->lifetime, this->lifebytes, AUTH_NONE,
-                                                               this->mode, this->udp, this->cpi_i);
+                                                               this->mode, encap, this->cpi_i);
                        list->destroy_offset(list, offsetof(proposal_t, destroy));
                        message->add_payload(message, &sa_payload->payload_interface);
 
@@ -703,26 +868,20 @@ METHOD(task_t, build_i, status_t,
                        {
                                return FAILED;
                        }
-
-                       group = this->config->get_dh_group(this->config);
                        if (group != MODP_NONE)
                        {
-                               this->dh = this->keymat->keymat.create_dh(&this->keymat->keymat,
-                                                                                                                 group);
-                               if (!this->dh)
-                               {
-                                       DBG1(DBG_IKE, "configured DH group %N not supported",
-                                                diffie_hellman_group_names, group);
-                                       return FAILED;
-                               }
                                add_ke(this, message);
                        }
-                       this->tsi = select_ts(this, TRUE, NULL);
-                       this->tsr = select_ts(this, FALSE, NULL);
-                       tsi = linked_list_create();
-                       tsr = linked_list_create();
-                       tsi->insert_last(tsi, this->tsi);
-                       tsr->insert_last(tsr, this->tsr);
+                       if (!this->tsi)
+                       {
+                               this->tsi = select_ts(this, TRUE, NULL);
+                       }
+                       if (!this->tsr)
+                       {
+                               this->tsr = select_ts(this, FALSE, NULL);
+                       }
+                       tsi = linked_list_create_with_items(this->tsi, NULL);
+                       tsr = linked_list_create_with_items(this->tsr, NULL);
                        this->tsi = this->tsr = NULL;
                        charon->bus->narrow(charon->bus, this->child_sa,
                                                                NARROW_INITIATOR_PRE_AUTH, tsi, tsr);
@@ -758,7 +917,7 @@ static bool has_notify_errors(private_quick_mode_t *this, message_t *message)
        enumerator = message->create_payload_enumerator(message);
        while (enumerator->enumerate(enumerator, &payload))
        {
-               if (payload->get_type(payload) == NOTIFY_V1)
+               if (payload->get_type(payload) == PLV1_NOTIFY)
                {
                        notify_payload_t *notify;
                        notify_type_t type;
@@ -791,30 +950,37 @@ static void check_for_rekeyed_child(private_quick_mode_t *this)
        enumerator_t *enumerator, *policies;
        traffic_selector_t *local, *remote;
        child_sa_t *child_sa;
+       proposal_t *proposal;
+       char *name;
 
+       name = this->config->get_name(this->config);
        enumerator = this->ike_sa->create_child_sa_enumerator(this->ike_sa);
        while (this->reqid == 0 && enumerator->enumerate(enumerator, &child_sa))
        {
-               if (child_sa->get_state(child_sa) == CHILD_INSTALLED &&
-                       streq(child_sa->get_name(child_sa),
-                                 this->config->get_name(this->config)))
+               if (streq(child_sa->get_name(child_sa), name))
                {
-                       policies = child_sa->create_policy_enumerator(child_sa);
-                       if (policies->enumerate(policies, &local, &remote))
+                       proposal = child_sa->get_proposal(child_sa);
+                       switch (child_sa->get_state(child_sa))
                        {
-                               if (local->equals(local, this->tsr) &&
-                                       remote->equals(remote, this->tsi) &&
-                                       this->proposal->equals(this->proposal,
-                                                                                  child_sa->get_proposal(child_sa)))
-                               {
-                                       this->reqid = child_sa->get_reqid(child_sa);
-                                       this->rekey = child_sa->get_spi(child_sa, TRUE);
-                                       child_sa->set_state(child_sa, CHILD_REKEYING);
-                                       DBG1(DBG_IKE, "detected rekeying of CHILD_SA %s{%u}",
-                                                child_sa->get_name(child_sa), this->reqid);
-                               }
+                               case CHILD_INSTALLED:
+                               case CHILD_REKEYING:
+                                       policies = child_sa->create_policy_enumerator(child_sa);
+                                       if (policies->enumerate(policies, &local, &remote) &&
+                                               local->equals(local, this->tsr) &&
+                                               remote->equals(remote, this->tsi) &&
+                                               this->proposal->equals(this->proposal, proposal))
+                                       {
+                                               this->reqid = child_sa->get_reqid(child_sa);
+                                               this->rekey = child_sa->get_spi(child_sa, TRUE);
+                                               child_sa->set_state(child_sa, CHILD_REKEYING);
+                                               DBG1(DBG_IKE, "detected rekeying of CHILD_SA %s{%u}",
+                                                        child_sa->get_name(child_sa), this->reqid);
+                                       }
+                                       policies->destroy(policies);
+                               break;
+                       default:
+                               break;
                        }
-                       policies->destroy(policies);
                }
        }
        enumerator->destroy(enumerator);
@@ -828,34 +994,35 @@ METHOD(task_t, process_r, status_t,
                case QM_INIT:
                {
                        sa_payload_t *sa_payload;
-                       linked_list_t *tsi, *tsr, *list = NULL;
+                       linked_list_t *tsi, *tsr, *hostsi, *hostsr, *list = NULL;
                        peer_cfg_t *peer_cfg;
-                       host_t *me, *other;
                        u_int16_t group;
                        bool private;
 
-                       if (!get_ts(this, message))
-                       {
-                               return FAILED;
-                       }
-                       me = this->ike_sa->get_virtual_ip(this->ike_sa, TRUE);
-                       if (!me)
+                       sa_payload = (sa_payload_t*)message->get_payload(message,
+                                                                                                       PLV1_SECURITY_ASSOCIATION);
+                       if (!sa_payload)
                        {
-                               me = this->ike_sa->get_my_host(this->ike_sa);
+                               DBG1(DBG_IKE, "sa payload missing");
+                               return send_notify(this, INVALID_PAYLOAD_TYPE);
                        }
-                       other = this->ike_sa->get_virtual_ip(this->ike_sa, FALSE);
-                       if (!other)
+
+                       this->mode = sa_payload->get_encap_mode(sa_payload, &this->udp);
+
+                       if (!get_ts(this, message))
                        {
-                               other = this->ike_sa->get_other_host(this->ike_sa);
+                               return FAILED;
                        }
                        peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa);
-                       tsi = linked_list_create();
-                       tsr = linked_list_create();
-                       tsi->insert_last(tsi, this->tsi);
-                       tsr->insert_last(tsr, this->tsr);
+                       tsi = linked_list_create_with_items(this->tsi, NULL);
+                       tsr = linked_list_create_with_items(this->tsr, NULL);
                        this->tsi = this->tsr = NULL;
+                       hostsi = get_dynamic_hosts(this->ike_sa, FALSE);
+                       hostsr = get_dynamic_hosts(this->ike_sa, TRUE);
                        this->config = peer_cfg->select_child_cfg(peer_cfg, tsr, tsi,
-                                                                                                         me, other);
+                                                                                                         hostsr, hostsi);
+                       hostsi->destroy(hostsi);
+                       hostsr->destroy(hostsr);
                        if (this->config)
                        {
                                this->tsi = select_ts(this, FALSE, tsi);
@@ -869,30 +1036,20 @@ METHOD(task_t, process_r, status_t,
                                return send_notify(this, INVALID_ID_INFORMATION);
                        }
 
-                       sa_payload = (sa_payload_t*)message->get_payload(message,
-                                                                                                       SECURITY_ASSOCIATION_V1);
-                       if (!sa_payload)
-                       {
-                               DBG1(DBG_IKE, "sa payload missing");
-                               return send_notify(this, INVALID_PAYLOAD_TYPE);
-                       }
                        if (this->config->use_ipcomp(this->config))
                        {
-                               if (this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY))
-                               {
-                                       DBG1(DBG_IKE, "IPComp is not supported if either peer is "
-                                                "natted, IPComp disabled");
-                               }
-                               else
+                               list = sa_payload->get_ipcomp_proposals(sa_payload,
+                                                                                                               &this->cpi_i);
+                               if (!list->get_count(list))
                                {
-                                       list = sa_payload->get_ipcomp_proposals(sa_payload,
-                                                                                                                       &this->cpi_i);
+                                       DBG1(DBG_IKE, "expected IPComp proposal but peer did "
+                                                "not send one, IPComp disabled");
+                                       this->cpi_i = 0;
                                }
                        }
                        if (!list || !list->get_count(list))
                        {
                                DESTROY_IF(list);
-                               this->cpi_i = 0;
                                list = sa_payload->get_proposals(sa_payload);
                        }
                        private = this->ike_sa->supports_extension(this->ike_sa,
@@ -901,8 +1058,6 @@ METHOD(task_t, process_r, status_t,
                                                                                                                   list, FALSE, private);
                        list->destroy_offset(list, offsetof(proposal_t, destroy));
 
-                       this->mode = sa_payload->get_encap_mode(sa_payload, &this->udp);
-
                        get_lifetimes(this);
                        apply_lifetimes(this, sa_payload);
 
@@ -942,6 +1097,22 @@ METHOD(task_t, process_r, status_t,
                                                                        this->ike_sa->get_my_host(this->ike_sa),
                                                                        this->ike_sa->get_other_host(this->ike_sa),
                                                                        this->config, this->reqid, this->udp);
+
+                       tsi = linked_list_create_with_items(this->tsi, NULL);
+                       tsr = linked_list_create_with_items(this->tsr, NULL);
+                       this->tsi = this->tsr = NULL;
+                       charon->bus->narrow(charon->bus, this->child_sa,
+                                                               NARROW_RESPONDER, tsr, tsi);
+                       if (tsi->remove_first(tsi, (void**)&this->tsi) != SUCCESS ||
+                               tsr->remove_first(tsr, (void**)&this->tsr) != SUCCESS)
+                       {
+                               tsi->destroy_offset(tsi, offsetof(traffic_selector_t, destroy));
+                               tsr->destroy_offset(tsr, offsetof(traffic_selector_t, destroy));
+                               return send_notify(this, INVALID_ID_INFORMATION);
+                       }
+                       tsi->destroy_offset(tsi, offsetof(traffic_selector_t, destroy));
+                       tsr->destroy_offset(tsr, offsetof(traffic_selector_t, destroy));
+
                        return NEED_MORE;
                }
                case QM_NEGOTIATED:
@@ -953,11 +1124,15 @@ METHOD(task_t, process_r, status_t,
                        }
                        if (!install(this))
                        {
-                               this->ike_sa->flush_queue(this->ike_sa, TASK_QUEUE_PASSIVE);
-                               this->ike_sa->queue_task(this->ike_sa,
-                                       (task_t*)quick_delete_create(this->ike_sa,
+                               ike_sa_t *ike_sa = this->ike_sa;
+                               task_t *task;
+
+                               task = (task_t*)quick_delete_create(this->ike_sa,
                                                                this->proposal->get_protocol(this->proposal),
-                                                               this->spi_i, TRUE, TRUE));
+                                                               this->spi_i, TRUE, TRUE);
+                               /* flush_queue() destroys the current task */
+                               ike_sa->flush_queue(ike_sa, TASK_QUEUE_PASSIVE);
+                               ike_sa->queue_task(ike_sa, task);
                                return ALREADY_DONE;
                        }
                        return SUCCESS;
@@ -975,8 +1150,10 @@ METHOD(task_t, build_r, status_t,
                case QM_INIT:
                {
                        sa_payload_t *sa_payload;
+                       encap_t encap;
 
-                       this->spi_r = this->child_sa->alloc_spi(this->child_sa, PROTO_ESP);
+                       this->proto = this->proposal->get_protocol(this->proposal);
+                       this->spi_r = this->child_sa->alloc_spi(this->child_sa, this->proto);
                        if (!this->spi_r)
                        {
                                DBG1(DBG_IKE, "allocating SPI from kernel failed");
@@ -1001,9 +1178,10 @@ METHOD(task_t, build_r, status_t,
                                add_nat_oa_payloads(this, message);
                        }
 
+                       encap = get_encap(this->ike_sa, this->udp);
                        sa_payload = sa_payload_create_from_proposal_v1(this->proposal,
                                                                this->lifetime, this->lifebytes, AUTH_NONE,
-                                                               this->mode, this->udp, this->cpi_r);
+                                                               this->mode, encap, this->cpi_r);
                        message->add_payload(message, &sa_payload->payload_interface);
 
                        if (!add_nonce(this, &this->nonce_r, message))
@@ -1037,7 +1215,7 @@ METHOD(task_t, process_i, status_t,
                        bool private;
 
                        sa_payload = (sa_payload_t*)message->get_payload(message,
-                                                                                                       SECURITY_ASSOCIATION_V1);
+                                                                                                       PLV1_SECURITY_ASSOCIATION);
                        if (!sa_payload)
                        {
                                DBG1(DBG_IKE, "sa payload missing");
@@ -1047,11 +1225,16 @@ METHOD(task_t, process_i, status_t,
                        {
                                list = sa_payload->get_ipcomp_proposals(sa_payload,
                                                                                                                &this->cpi_r);
+                               if (!list->get_count(list))
+                               {
+                                       DBG1(DBG_IKE, "peer did not acccept our IPComp proposal, "
+                                                "IPComp disabled");
+                                       this->cpi_i = 0;
+                               }
                        }
                        if (!list || !list->get_count(list))
                        {
                                DESTROY_IF(list);
-                               this->cpi_i = 0;
                                list = sa_payload->get_proposals(sa_payload);
                        }
                        private = this->ike_sa->supports_extension(this->ike_sa,
@@ -1176,6 +1359,9 @@ quick_mode_t *quick_mode_create(ike_sa_t *ike_sa, child_cfg_t *config,
                .config = config,
                .keymat = (keymat_v1_t*)ike_sa->get_keymat(ike_sa),
                .state = QM_INIT,
+               .tsi = tsi ? tsi->clone(tsi) : NULL,
+               .tsr = tsr ? tsr->clone(tsr) : NULL,
+               .proto = PROTO_ESP,
        );
 
        if (config)