ike-sa: Handle redirect requests for established SAs as reestablishment
authorTobias Brunner <tobias@strongswan.org>
Fri, 24 Apr 2015 14:57:43 +0000 (16:57 +0200)
committerTobias Brunner <tobias@strongswan.org>
Fri, 4 Mar 2016 15:02:59 +0000 (16:02 +0100)
We handle this similar to how we do reestablishing IKE_SAs with all CHILD_SAs,
which also includes the one actively queued during IKE_AUTH.

To delete the old SA we use the recently added ike_reauth_complete task.

src/libcharon/sa/ike_sa.c

index 79960fc..f0c38ef 100644 (file)
@@ -56,6 +56,7 @@
 #include <processing/jobs/rekey_ike_sa_job.h>
 #include <processing/jobs/retry_initiate_job.h>
 #include <sa/ikev2/tasks/ike_auth_lifetime.h>
+#include <sa/ikev2/tasks/ike_reauth_complete.h>
 
 #ifdef ME
 #include <sa/ikev2/tasks/ike_me.h>
@@ -1768,6 +1769,86 @@ static bool is_child_queued(private_ike_sa_t *this, task_queue_t queue)
        return found;
 }
 
+/**
+ * Reestablish CHILD_SAs and migrate queued tasks.
+ *
+ * If force is true all SAs are restarted, otherwise their close/dpd_action
+ * is followed.
+ */
+static status_t reestablish_children(private_ike_sa_t *this, ike_sa_t *new,
+                                                                        bool force)
+{
+       enumerator_t *enumerator;
+       child_sa_t *child_sa;
+       child_cfg_t *child_cfg;
+       action_t action;
+       status_t status = FAILED;
+
+       /* handle existing CHILD_SAs */
+       enumerator = create_child_sa_enumerator(this);
+       while (enumerator->enumerate(enumerator, (void**)&child_sa))
+       {
+               if (force)
+               {
+                       switch (child_sa->get_state(child_sa))
+                       {
+                               case CHILD_ROUTED:
+                               {       /* move routed child directly */
+                                       remove_child_sa(this, enumerator);
+                                       new->add_child_sa(new, child_sa);
+                                       action = ACTION_NONE;
+                                       break;
+                               }
+                               default:
+                               {       /* initiate/queue all other CHILD_SAs */
+                                       action = ACTION_RESTART;
+                                       break;
+                               }
+                       }
+               }
+               else
+               {       /* 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)
+               {
+                       case ACTION_RESTART:
+                               child_cfg = child_sa->get_config(child_sa);
+                               DBG1(DBG_IKE, "restarting CHILD_SA %s",
+                                        child_cfg->get_name(child_cfg));
+                               child_cfg->get_ref(child_cfg);
+                               status = new->initiate(new, child_cfg,
+                                                               child_sa->get_reqid(child_sa), NULL, NULL);
+                               break;
+                       default:
+                               continue;
+               }
+               if (status == DESTROY_ME)
+               {
+                       break;
+               }
+       }
+       enumerator->destroy(enumerator);
+       /* adopt any active or queued CHILD-creating tasks */
+       if (status != DESTROY_ME)
+       {
+               task_manager_t *other_tasks = ((private_ike_sa_t*)new)->task_manager;
+               other_tasks->adopt_child_tasks(other_tasks, this->task_manager);
+               if (new->get_state(new) == IKE_CREATED)
+               {
+                       status = new->initiate(new, NULL, 0, NULL, NULL);
+               }
+       }
+       return status;
+}
+
 METHOD(ike_sa_t, reestablish, status_t,
        private_ike_sa_t *this)
 {
@@ -1776,7 +1857,6 @@ METHOD(ike_sa_t, reestablish, status_t,
        action_t action;
        enumerator_t *enumerator;
        child_sa_t *child_sa;
-       child_cfg_t *child_cfg;
        bool restart = FALSE;
        status_t status = FAILED;
 
@@ -1887,68 +1967,8 @@ METHOD(ike_sa_t, reestablish, status_t,
        else
 #endif /* ME */
        {
-               /* handle existing CHILD_SAs */
-               enumerator = create_child_sa_enumerator(this);
-               while (enumerator->enumerate(enumerator, (void**)&child_sa))
-               {
-                       if (has_condition(this, COND_REAUTHENTICATING))
-                       {
-                               switch (child_sa->get_state(child_sa))
-                               {
-                                       case CHILD_ROUTED:
-                                       {       /* move routed child directly */
-                                               remove_child_sa(this, enumerator);
-                                               new->add_child_sa(new, child_sa);
-                                               action = ACTION_NONE;
-                                               break;
-                                       }
-                                       default:
-                                       {       /* initiate/queue all other CHILD_SAs */
-                                               action = ACTION_RESTART;
-                                               break;
-                                       }
-                               }
-                       }
-                       else
-                       {       /* 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)
-                       {
-                               case ACTION_RESTART:
-                                       child_cfg = child_sa->get_config(child_sa);
-                                       DBG1(DBG_IKE, "restarting CHILD_SA %s",
-                                                child_cfg->get_name(child_cfg));
-                                       child_cfg->get_ref(child_cfg);
-                                       status = new->initiate(new, child_cfg,
-                                                                       child_sa->get_reqid(child_sa), NULL, NULL);
-                                       break;
-                               default:
-                                       continue;
-                       }
-                       if (status == DESTROY_ME)
-                       {
-                               break;
-                       }
-               }
-               enumerator->destroy(enumerator);
-               /* adopt any active or queued CHILD-creating tasks */
-               if (status != DESTROY_ME)
-               {
-                       task_manager_t *other_tasks = ((private_ike_sa_t*)new)->task_manager;
-                       other_tasks->adopt_child_tasks(other_tasks, this->task_manager);
-                       if (new->get_state(new) == IKE_CREATED)
-                       {
-                               status = new->initiate(new, NULL, 0, NULL, NULL);
-                       }
-               }
+               status = reestablish_children(this, new,
+                                                                       has_condition(this, COND_REAUTHENTICATING));
        }
 
        if (status == DESTROY_ME)
@@ -1969,42 +1989,114 @@ METHOD(ike_sa_t, reestablish, status_t,
        return status;
 }
 
-METHOD(ike_sa_t, handle_redirect, bool,
-       private_ike_sa_t *this, identification_t *gateway)
+/**
+ * Resolve the given gateway ID
+ */
+static host_t *resolve_gateway_id(identification_t *gateway)
 {
        char gw[BUF_LEN];
-       host_t *other, *from;
+       host_t *addr;
 
-       DBG1(DBG_IKE, "redirected to %Y", gateway);
-       if (!this->follow_redirects)
+       snprintf(gw, sizeof(gw), "%Y", gateway);
+       gw[sizeof(gw)-1] = '\0';
+       addr = host_create_from_dns(gw, AF_UNSPEC, IKEV2_UDP_PORT);
+       if (!addr)
+       {
+               DBG1(DBG_IKE, "unable to resolve gateway ID '%Y', redirect failed",
+                        gateway);
+       }
+       return addr;
+}
+
+/**
+ * Redirect the current SA to the given target host
+ */
+static bool redirect_established(private_ike_sa_t *this, identification_t *to)
+{
+       private_ike_sa_t *new_priv;
+       ike_sa_t *new;
+       host_t *other;
+
+       new = charon->ike_sa_manager->checkout_new(charon->ike_sa_manager,
+                                                                                          this->version, TRUE);
+       if (!new)
        {
-               DBG1(DBG_IKE, "server sent REDIRECT even though we disabled it");
                return FALSE;
        }
+       new_priv = (private_ike_sa_t*)new;
+       new->set_peer_cfg(new, this->peer_cfg);
+       new_priv->redirected_from = this->other_host->clone(this->other_host);
+       charon->bus->ike_reestablish_pre(charon->bus, &this->public, new);
+       other = resolve_gateway_id(to);
+       if (other)
+       {
+               set_my_host(new_priv, this->my_host->clone(this->my_host));
+               /* this allows us to force the remote address while we still properly
+                * resolve the local address */
+               new_priv->remote_host = other;
+               resolve_hosts(new_priv);
+               if (reestablish_children(this, new, TRUE) != DESTROY_ME)
+               {
+#ifdef USE_IKEV2
+                       new->queue_task(new, (task_t*)ike_reauth_complete_create(new,
+                                                                                                                        this->ike_sa_id));
+#endif
+                       charon->bus->ike_reestablish_post(charon->bus, &this->public, new,
+                                                                                         TRUE);
+                       charon->ike_sa_manager->checkin(charon->ike_sa_manager, new);
+                       charon->bus->set_sa(charon->bus, &this->public);
+                       return TRUE;
+               }
+       }
+       charon->bus->ike_reestablish_post(charon->bus, &this->public, new,
+                                                                         FALSE);
+       charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, new);
+       charon->bus->set_sa(charon->bus, &this->public);
+       return FALSE;
+}
 
-       snprintf(gw, sizeof(gw), "%Y", gateway);
-       gw[sizeof(gw)-1] = '\0';
-       other = host_create_from_dns(gw, AF_UNSPEC, IKEV2_UDP_PORT);
+/**
+ * Redirect the current connecting SA to the given target host
+ */
+static bool redirect_connecting(private_ike_sa_t *this, identification_t *to)
+{
+       host_t *other;
+
+       other = resolve_gateway_id(to);
        if (!other)
        {
-               DBG1(DBG_IKE, "unable to resolve gateway ID '%Y', redirect failed",
-                        gateway);
                return FALSE;
        }
-       from = this->other_host->clone(this->other_host);
+       reset(this);
+       DESTROY_IF(this->redirected_from);
+       this->redirected_from = this->other_host->clone(this->other_host);
+       DESTROY_IF(this->remote_host);
+       /* this allows us to force the remote address while we still properly
+        * resolve the local address */
+       this->remote_host = other;
+       resolve_hosts(this);
+       return TRUE;
+}
+
+METHOD(ike_sa_t, handle_redirect, bool,
+       private_ike_sa_t *this, identification_t *gateway)
+{
+       DBG1(DBG_IKE, "redirected to %Y", gateway);
+       if (!this->follow_redirects)
+       {
+               DBG1(DBG_IKE, "server sent REDIRECT even though we disabled it");
+               return FALSE;
+       }
+
        switch (this->state)
        {
                case IKE_CONNECTING:
-                       reset(this);
-                       set_other_host(this, other);
-                       DESTROY_IF(this->redirected_from);
-                       this->redirected_from = from;
-                       return TRUE;
+                       return redirect_connecting(this, gateway);
+               case IKE_ESTABLISHED:
+                       return redirect_established(this, gateway);
                default:
                        DBG1(DBG_IKE, "unable to handle redirect for IKE_SA in state %N",
                                 ike_sa_state_names, this->state);
-                       other->destroy(other);
-                       from->destroy(from);
                        return FALSE;
        }
 }