ike_sa: Defer task manager destruction after child destruction
[strongswan.git] / src / libcharon / sa / ikev1 / task_manager_v1.c
index 606a981..131d812 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2007-2012 Tobias Brunner
+ * Copyright (C) 2007-2013 Tobias Brunner
  * Copyright (C) 2007-2011 Martin Willi
  * Hochschule fuer Technik Rapperswil
  *
@@ -217,6 +217,11 @@ struct private_task_manager_t {
                size_t max_packet;
 
                /**
+                * Maximum length of a single fragment (when sending)
+                */
+               size_t size;
+
+               /**
                 * The exchange type we use for fragments. Always the initial type even
                 * for fragmented quick mode or transaction messages (i.e. either
                 * ID_PROT or AGGRESSIVE)
@@ -226,11 +231,6 @@ struct private_task_manager_t {
        } frag;
 
        /**
-        * TRUE if fragmentation (as sender) is enabled in config
-        */
-       bool fragmentation;
-
-       /**
         * List of queued tasks not yet in action
         */
        linked_list_t *queued_tasks;
@@ -339,10 +339,8 @@ METHOD(task_manager_t, flush_queue, void,
        }
 }
 
-/**
- * flush all tasks in the task manager
- */
-static void flush(private_task_manager_t *this)
+METHOD(task_manager_t, flush, void,
+       private_task_manager_t *this)
 {
        flush_queue(this, TASK_QUEUE_QUEUED);
        flush_queue(this, TASK_QUEUE_PASSIVE);
@@ -411,25 +409,44 @@ static bool send_fragment(private_task_manager_t *this, bool request,
 static bool send_packet(private_task_manager_t *this, bool request,
                                                packet_t *packet)
 {
-       host_t *src, *dst;
+       bool use_frags = FALSE;
+       ike_cfg_t *ike_cfg;
        chunk_t data;
 
+       ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa);
+       if (ike_cfg)
+       {
+               switch (ike_cfg->fragmentation(ike_cfg))
+               {
+                       case FRAGMENTATION_FORCE:
+                               use_frags = TRUE;
+                               break;
+                       case FRAGMENTATION_YES:
+                               use_frags = this->ike_sa->supports_extension(this->ike_sa,
+                                                                                                               EXT_IKE_FRAGMENTATION);
+                               break;
+                       default:
+                               break;
+               }
+       }
        data = packet->get_data(packet);
-       if (this->ike_sa->supports_extension(this->ike_sa, EXT_IKE_FRAGMENTATION) &&
-               this->fragmentation && data.len > MAX_FRAGMENT_SIZE)
+       if (data.len > this->frag.size && use_frags)
        {
                fragment_payload_t *fragment;
                u_int8_t num, count;
                size_t len, frag_size;
-               bool nat;
-
-               /* reduce size due to non-ESP marker */
-               nat = this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY);
-               frag_size = MAX_FRAGMENT_SIZE - (nat ? 4 : 0);
+               host_t *src, *dst;
 
                src = packet->get_source(packet);
                dst = packet->get_destination(packet);
-               count = (data.len / (frag_size + 1)) + 1;
+
+               frag_size = this->frag.size;
+               if (dst->get_port(dst) != IKEV2_UDP_PORT &&
+                       src->get_port(src) != IKEV2_UDP_PORT)
+               {       /* reduce size due to non-ESP marker */
+                       frag_size -= 4;
+               }
+               count = data.len / frag_size + (data.len % frag_size ? 1 : 0);
 
                DBG1(DBG_IKE, "sending IKE message with length of %zu bytes in "
                         "%hhu fragments", data.len, count);
@@ -520,23 +537,40 @@ static bool mode_config_expected(private_task_manager_t *this)
        enumerator_t *enumerator;
        peer_cfg_t *peer_cfg;
        char *pool;
+       bool local;
        host_t *host;
 
        peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa);
        if (peer_cfg)
        {
-               enumerator = peer_cfg->create_pool_enumerator(peer_cfg);
-               if (!enumerator->enumerate(enumerator, &pool))
-               {       /* no pool configured */
+               if (peer_cfg->use_pull_mode(peer_cfg))
+               {
+                       enumerator = peer_cfg->create_pool_enumerator(peer_cfg);
+                       if (!enumerator->enumerate(enumerator, &pool))
+                       {       /* no pool configured */
+                               enumerator->destroy(enumerator);
+                               return FALSE;
+                       }
                        enumerator->destroy(enumerator);
-                       return FALSE;
+
+                       local = FALSE;
                }
-               enumerator->destroy(enumerator);
+               else
+               {
+                       enumerator = peer_cfg->create_virtual_ip_enumerator(peer_cfg);
+                       if (!enumerator->enumerate(enumerator, &host))
+                       {       /* not requesting a vip */
+                               enumerator->destroy(enumerator);
+                               return FALSE;
+                       }
+                       enumerator->destroy(enumerator);
 
+                       local = TRUE;
+               }
                enumerator = this->ike_sa->create_virtual_ip_enumerator(this->ike_sa,
-                                                                                                                               FALSE);
+                                                                                                                               local);
                if (!enumerator->enumerate(enumerator, &host))
-               {       /* have a pool, but no VIP assigned yet */
+               {       /* expecting a VIP exchange, but no VIP assigned yet */
                        enumerator->destroy(enumerator);
                        return TRUE;
                }
@@ -1068,7 +1102,8 @@ static status_t process_request(private_task_manager_t *this,
                        case TRANSACTION:
                                if (this->ike_sa->get_state(this->ike_sa) != IKE_CONNECTING)
                                {
-                                       task = (task_t *)mode_config_create(this->ike_sa, FALSE);
+                                       task = (task_t *)mode_config_create(this->ike_sa,
+                                                                                                               FALSE, TRUE);
                                }
                                else
                                {
@@ -1155,6 +1190,15 @@ static status_t process_response(private_task_manager_t *this,
 
        if (message->get_exchange_type(message) != this->initiating.type)
        {
+               /* Windows server sends a fourth quick mode message having an initial
+                * contact notify. Ignore this message for compatibility. */
+               if (this->initiating.type == EXCHANGE_TYPE_UNDEFINED &&
+                       message->get_exchange_type(message) == QUICK_MODE &&
+                       message->get_notify(message, INITIAL_CONTACT))
+               {
+                       DBG1(DBG_IKE, "ignoring fourth Quick Mode message");
+                       return SUCCESS;
+               }
                DBG1(DBG_IKE, "received %N response, but expected %N",
                         exchange_type_names, message->get_exchange_type(message),
                         exchange_type_names, this->initiating.type);
@@ -1227,7 +1271,7 @@ static status_t handle_fragment(private_task_manager_t *this, message_t *msg)
                return FAILED;
        }
 
-       if (this->frag.id != payload->get_id(payload))
+       if (!this->frag.list || this->frag.id != payload->get_id(payload))
        {
                clear_fragments(this, payload->get_id(payload));
                this->frag.list = linked_list_create();
@@ -1463,6 +1507,21 @@ METHOD(task_manager_t, process_message, status_t,
                        charon->bus->alert(charon->bus, ALERT_RETRANSMIT_RECEIVE, msg);
                        return SUCCESS;
                }
+
+               /* reject Main/Aggressive Modes once established */
+               if (msg->get_exchange_type(msg) == ID_PROT ||
+                       msg->get_exchange_type(msg) == AGGRESSIVE)
+               {
+                       if (this->ike_sa->get_state(this->ike_sa) != IKE_CREATED &&
+                               this->ike_sa->get_state(this->ike_sa) != IKE_CONNECTING &&
+                               msg->get_first_payload_type(msg) != FRAGMENT_V1)
+                       {
+                               DBG1(DBG_IKE, "ignoring %N in established IKE_SA state",
+                                        exchange_type_names, msg->get_exchange_type(msg));
+                               return FAILED;
+                       }
+               }
+
                if (msg->get_exchange_type(msg) == TRANSACTION &&
                        this->active_tasks->get_count(this->active_tasks))
                {       /* main mode not yet complete, queue XAuth/Mode config tasks */
@@ -1712,21 +1771,22 @@ METHOD(task_manager_t, queue_child, void,
 /**
  * Check if two CHILD_SAs have the same traffic selector
  */
-static bool have_equal_ts(child_sa_t *a, child_sa_t *b, bool local)
+static bool have_equal_ts(child_sa_t *child1, child_sa_t *child2, bool local)
 {
-       linked_list_t *list;
-       traffic_selector_t *ts_a, *ts_b;
+       enumerator_t *e1, *e2;
+       traffic_selector_t *ts1, *ts2;
+       bool equal = FALSE;
 
-       list = a->get_traffic_selectors(a, local);
-       if (list->get_first(list, (void**)&ts_a) == SUCCESS)
+       e1 = child1->create_ts_enumerator(child1, local);
+       e2 = child2->create_ts_enumerator(child2, local);
+       if (e1->enumerate(e1, &ts1) && e2->enumerate(e2, &ts2))
        {
-               list = b->get_traffic_selectors(b, local);
-               if (list->get_first(list, (void**)&ts_b) == SUCCESS)
-               {
-                       return ts_a->equals(ts_a, ts_b);
-               }
+               equal = ts1->equals(ts1, ts2);
        }
-       return FALSE;
+       e2->destroy(e2);
+       e1->destroy(e1);
+
+       return equal;
 }
 
 /**
@@ -1765,14 +1825,13 @@ static bool is_redundant(private_task_manager_t *this, child_sa_t *child_sa)
 static traffic_selector_t* get_first_ts(child_sa_t *child_sa, bool local)
 {
        traffic_selector_t *ts = NULL;
-       linked_list_t *list;
+       enumerator_t *enumerator;
 
-       list = child_sa->get_traffic_selectors(child_sa, local);
-       if (list->get_first(list, (void**)&ts) == SUCCESS)
-       {
-               return ts;
-       }
-       return NULL;
+       enumerator = child_sa->create_ts_enumerator(child_sa, local);
+       enumerator->enumerate(enumerator, &ts);
+       enumerator->destroy(enumerator);
+
+       return ts;
 }
 
 METHOD(task_manager_t, queue_child_rekey, void,
@@ -1859,6 +1918,39 @@ METHOD(task_manager_t, adopt_tasks, void,
        }
 }
 
+/**
+ * Migrates child-creating tasks from src to dst
+ */
+static void migrate_child_tasks(private_task_manager_t *this,
+                                                               linked_list_t *src, linked_list_t *dst)
+{
+       enumerator_t *enumerator;
+       task_t *task;
+
+       enumerator = src->create_enumerator(src);
+       while (enumerator->enumerate(enumerator, &task))
+       {
+               if (task->get_type(task) == TASK_QUICK_MODE)
+               {
+                       src->remove_at(src, enumerator);
+                       task->migrate(task, this->ike_sa);
+                       dst->insert_last(dst, task);
+               }
+       }
+       enumerator->destroy(enumerator);
+}
+
+METHOD(task_manager_t, adopt_child_tasks, void,
+       private_task_manager_t *this, task_manager_t *other_public)
+{
+       private_task_manager_t *other = (private_task_manager_t*)other_public;
+
+       /* move active child tasks from other to this */
+       migrate_child_tasks(this, other->active_tasks, this->queued_tasks);
+       /* do the same for queued tasks */
+       migrate_child_tasks(this, other->queued_tasks, this->queued_tasks);
+}
+
 METHOD(task_manager_t, busy, bool,
        private_task_manager_t *this)
 {
@@ -1973,8 +2065,10 @@ task_manager_v1_t *task_manager_v1_create(ike_sa_t *ike_sa)
                                .incr_mid = _incr_mid,
                                .reset = _reset,
                                .adopt_tasks = _adopt_tasks,
+                               .adopt_child_tasks = _adopt_child_tasks,
                                .busy = _busy,
                                .create_task_enumerator = _create_task_enumerator,
+                               .flush = _flush,
                                .flush_queue = _flush_queue,
                                .destroy = _destroy,
                        },
@@ -1989,6 +2083,8 @@ task_manager_v1_t *task_manager_v1_create(ike_sa_t *ike_sa)
                        .exchange = ID_PROT,
                        .max_packet = lib->settings->get_int(lib->settings,
                                        "%s.max_packet", MAX_PACKET, charon->name),
+                       .size = lib->settings->get_int(lib->settings,
+                                       "%s.fragment_size", MAX_FRAGMENT_SIZE, charon->name),
                },
                .ike_sa = ike_sa,
                .rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK),
@@ -2001,8 +2097,6 @@ task_manager_v1_t *task_manager_v1_create(ike_sa_t *ike_sa)
                                        "%s.retransmit_timeout", RETRANSMIT_TIMEOUT, charon->name),
                .retransmit_base = lib->settings->get_double(lib->settings,
                                        "%s.retransmit_base", RETRANSMIT_BASE, charon->name),
-               .fragmentation = lib->settings->get_bool(lib->settings,
-                                       "%s.ike_fragmentation", FALSE, charon->name),
        );
 
        if (!this->rng)
@@ -2022,4 +2116,3 @@ task_manager_v1_t *task_manager_v1_create(ike_sa_t *ike_sa)
 
        return &this->public;
 }
-