proper update of IPsec SA when roaming a host-to-host tunnel
authorMartin Willi <martin@strongswan.org>
Mon, 2 Jul 2007 09:49:22 +0000 (09:49 -0000)
committerMartin Willi <martin@strongswan.org>
Mon, 2 Jul 2007 09:49:22 +0000 (09:49 -0000)
roaming of IPsec SAs using virtual IPs

src/charon/config/traffic_selector.c
src/charon/kernel/kernel_interface.c
src/charon/kernel/kernel_interface.h
src/charon/sa/child_sa.c
src/charon/sa/ike_sa.c

index b399074..da39c43 100644 (file)
@@ -175,6 +175,7 @@ static int print(FILE *stream, const struct printf_info *info,
        bool has_proto;
        bool has_ports;
        size_t written = 0;
+       u_int32_t from[4], to[4];
        
        if (this == NULL)
        {
@@ -193,7 +194,11 @@ static int print(FILE *stream, const struct printf_info *info,
                return written;
        }
        
-       if (this->dynamic)
+       memset(from, 0, sizeof(from));
+       memset(to, 0xFF, sizeof(to));
+       if (this->dynamic &&
+               memeq(this->from, from, this->type == TS_IPV4_ADDR_RANGE ? 4 : 16) && 
+               memeq(this->to, to, this->type == TS_IPV4_ADDR_RANGE ? 4 : 16))
        {
                return fprintf(stream, "dynamic/%d",
                                           this->type == TS_IPV4_ADDR_RANGE ? 32 : 128);
@@ -341,6 +346,7 @@ static traffic_selector_t *get_subset(private_traffic_selector_t *this, private_
                /* we have a match in protocol, port, and address: return it... */
                new_ts = traffic_selector_create(protocol, this->type, from_port, to_port);
                new_ts->type = this->type;
+               new_ts->dynamic = this->dynamic || other->dynamic;
                memcpy(new_ts->from, from, size);
                memcpy(new_ts->to, to, size);
                
@@ -475,11 +481,6 @@ static u_int8_t get_protocol(private_traffic_selector_t *this)
  */
 static bool is_host(private_traffic_selector_t *this, host_t *host)
 {
-       if (this->dynamic)
-       {
-               return TRUE;
-       }
-
        if (host)
        {
                chunk_t addr;
@@ -498,7 +499,12 @@ static bool is_host(private_traffic_selector_t *this, host_t *host)
        }
        else
        {
-               size_t length =  (this->type == TS_IPV4_ADDR_RANGE) ? 4 : 16;
+               size_t length = (this->type == TS_IPV4_ADDR_RANGE) ? 4 : 16;
+               
+               if (this->dynamic)
+               {
+                       return TRUE;
+               }
                
                if (memeq(this->from, this->to, length))
                {
index 63e265a..c290df2 100644 (file)
@@ -231,6 +231,9 @@ struct addr_entry_t {
        /** virtual IP managed by us */
        bool virtual;
        
+       /** scope of the address */
+       u_char scope;
+       
        /** Number of times this IP is used, if virtual */
        u_int refcount;
 };
@@ -695,6 +698,7 @@ static void process_addr(private_kernel_interface_t *this,
                                        addr->ip = host->clone(host);
                                        addr->virtual = FALSE;
                                        addr->refcount = 1;
+                                       addr->scope = msg->ifa_scope;
                                        
                                        iface->addrs->insert_last(iface->addrs, addr);
                                        if (event)
@@ -1078,6 +1082,10 @@ static hook_result_t addr_hook(private_kernel_interface_t *this,
        {       /* skip virtual interfaces added by us */
                return HOOK_SKIP;
        }
+       if (in->scope >= RT_SCOPE_LINK)
+       {       /* skip addresses with a unusable scope */
+               return HOOK_SKIP;
+       }
        *out = in->ip;
        return HOOK_NEXT;
 }
@@ -1497,6 +1505,7 @@ static status_t add_ip(private_kernel_interface_t *this,
                                addr->ip = virtual_ip->clone(virtual_ip);
                                addr->refcount = 1;
                                addr->virtual = TRUE;
+                               addr->scope = RT_SCOPE_UNIVERSE;
                                pthread_mutex_lock(&this->mutex);
                                iface->addrs->insert_last(iface->addrs, addr);
                                pthread_mutex_unlock(&this->mutex);
@@ -2001,8 +2010,7 @@ static status_t add_policy(private_kernel_interface_t *this,
                                                   traffic_selector_t *src_ts,
                                                   traffic_selector_t *dst_ts,
                                                   policy_dir_t direction, protocol_id_t protocol,
-                                                  u_int32_t reqid, bool high_prio, mode_t mode,
-                                                  bool update)
+                                                  u_int32_t reqid, bool high_prio, mode_t mode)
 {
        iterator_t *iterator;
        policy_entry_t *current, *policy;
@@ -2026,12 +2034,9 @@ static status_t add_policy(private_kernel_interface_t *this,
                        policy->direction == current->direction)
                {
                        /* use existing policy */
-                       if (!update)
-                       {
-                               current->refcount++;
-                               DBG2(DBG_KNL, "policy %R===%R already exists, increasing ",
-                                        "refcount", src_ts, dst_ts);
-                       }
+                       current->refcount++;
+                       DBG2(DBG_KNL, "policy %R===%R already exists, increasing ",
+                                "refcount", src_ts, dst_ts);
                        free(policy);
                        policy = current;
                        found = TRUE;
@@ -2318,7 +2323,7 @@ kernel_interface_t *kernel_interface_create()
        this->public.update_sa = (status_t(*)(kernel_interface_t*,u_int32_t,protocol_id_t,host_t*,host_t*,host_t*,host_t*))update_sa;
        this->public.query_sa = (status_t(*)(kernel_interface_t*,host_t*,u_int32_t,protocol_id_t,u_int32_t*))query_sa;
        this->public.del_sa = (status_t(*)(kernel_interface_t*,host_t*,u_int32_t,protocol_id_t))del_sa;
-       this->public.add_policy = (status_t(*)(kernel_interface_t*,host_t*,host_t*,traffic_selector_t*,traffic_selector_t*,policy_dir_t,protocol_id_t,u_int32_t,bool,mode_t,bool))add_policy;
+       this->public.add_policy = (status_t(*)(kernel_interface_t*,host_t*,host_t*,traffic_selector_t*,traffic_selector_t*,policy_dir_t,protocol_id_t,u_int32_t,bool,mode_t))add_policy;
        this->public.query_policy = (status_t(*)(kernel_interface_t*,traffic_selector_t*,traffic_selector_t*,policy_dir_t,u_int32_t*))query_policy;
        this->public.del_policy = (status_t(*)(kernel_interface_t*,traffic_selector_t*,traffic_selector_t*,policy_dir_t))del_policy;
        this->public.get_interface = (char*(*)(kernel_interface_t*,host_t*))get_interface_name;
index 4474e70..a5d2d07 100644 (file)
@@ -185,10 +185,6 @@ struct kernel_interface_t {
         * 
         * A policy is always associated to an SA. Traffic which matches a
         * policy is handled by the SA with the same reqid.
-        * If the update flag is set, the policy is updated with the new
-        * src/dst addresses.
-        * If the update flag is not set, but a such policy is already in the
-        * kernel, the reference count to this policy is increased.
         * 
         * @param this                  calling object
         * @param src                   source address of SA
@@ -200,7 +196,6 @@ struct kernel_interface_t {
         * @param reqid                 uniqe ID of an SA to use to enforce policy
         * @param high_prio             if TRUE, uses a higher priority than any with FALSE
         * @param mode                  mode of SA (tunnel, transport)
-        * @param update                update an existing policy, if TRUE
         * @return
         *                                              - SUCCESS
         *                                              - FAILED if kernel comm failed
@@ -210,8 +205,7 @@ struct kernel_interface_t {
                                                        traffic_selector_t *src_ts,
                                                        traffic_selector_t *dst_ts,
                                                        policy_dir_t direction, protocol_id_t protocol,
-                                                       u_int32_t reqid, bool high_prio, 
-                                                       mode_t mode, bool update);
+                                                       u_int32_t reqid, bool high_prio, mode_t mode);
        
        /**
         * @brief Query the use time of a policy.
index 9fe19bc..fa62c27 100644 (file)
@@ -369,6 +369,7 @@ static void updown(private_child_sa_t *this, bool up)
                free(other_client);
                free(virtual_ip);
                
+               DBG3(DBG_CHD, "running updown script: %s", command);
                shell = popen(command, "r");
 
                if (shell == NULL)
@@ -676,15 +677,15 @@ static status_t add_policies(private_child_sa_t *this,
                        /* install 3 policies: out, in and forward */
                        status = charon->kernel_interface->add_policy(charon->kernel_interface,
                                        this->me.addr, this->other.addr, my_ts, other_ts, POLICY_OUT,
-                                       this->protocol, this->reqid, high_prio, mode, FALSE);
+                                       this->protocol, this->reqid, high_prio, mode);
                        
                        status |= charon->kernel_interface->add_policy(charon->kernel_interface,
                                        this->other.addr, this->me.addr, other_ts, my_ts, POLICY_IN,
-                                       this->protocol, this->reqid, high_prio, mode, FALSE);
+                                       this->protocol, this->reqid, high_prio, mode);
                        
                        status |= charon->kernel_interface->add_policy(charon->kernel_interface,
                                        this->other.addr, this->me.addr, other_ts, my_ts, POLICY_FWD,
-                                       this->protocol, this->reqid, high_prio, mode, FALSE);
+                                       this->protocol, this->reqid, high_prio, mode);
                        
                        if (status != SUCCESS)
                        {
@@ -780,6 +781,9 @@ static status_t update_hosts(private_child_sa_t *this,
                return SUCCESS;
        }
        
+       /* run updown script to remove iptables rules */
+       updown(this, FALSE);
+       
        /* update our (initator) SAs */
        if (charon->kernel_interface->update_sa(
                                charon->kernel_interface, this->me.spi, this->protocol,
@@ -808,20 +812,39 @@ static status_t update_hosts(private_child_sa_t *this,
                iterator = this->policies->create_iterator(this->policies, TRUE);
                while (iterator->iterate(iterator, (void**)&policy))
                {
+                       /* remove old policies first */
+                       charon->kernel_interface->del_policy(charon->kernel_interface,
+                                                               policy->my_ts, policy->other_ts, POLICY_OUT);
+                       charon->kernel_interface->del_policy(charon->kernel_interface,
+                                                               policy->other_ts, policy->my_ts,  POLICY_IN);
+                       charon->kernel_interface->del_policy(charon->kernel_interface,
+                                                               policy->other_ts, policy->my_ts, POLICY_FWD);
+               
+                       /* check wether we have to update a "dynamic" traffic selector */
+                       if (!me->ip_equals(me, this->me.addr) &&
+                               policy->my_ts->is_host(policy->my_ts, this->me.addr))
+                       {
+                               policy->my_ts->set_address(policy->my_ts, me);
+                       }
+                       if (!other->ip_equals(other, this->other.addr) &&
+                               policy->other_ts->is_host(policy->other_ts, this->other.addr))
+                       {
+                               policy->other_ts->set_address(policy->other_ts, other);
+                       }
+               
+                       /* reinstall updated policies */
                        status = charon->kernel_interface->add_policy(
                                                charon->kernel_interface, me, other, 
                                                policy->my_ts, policy->other_ts, POLICY_OUT,
-                                               this->protocol, this->reqid, TRUE, this->mode, TRUE);
-                       
+                                               this->protocol, this->reqid, TRUE, this->mode);
                        status |= charon->kernel_interface->add_policy(
                                                charon->kernel_interface, other, me,
                                                policy->other_ts, policy->my_ts, POLICY_IN,
-                                               this->protocol, this->reqid, TRUE, this->mode, TRUE);
-                       
+                                               this->protocol, this->reqid, TRUE, this->mode);
                        status |= charon->kernel_interface->add_policy(
                                                charon->kernel_interface, other, me,
                                                policy->other_ts, policy->my_ts, POLICY_FWD,
-                                               this->protocol, this->reqid, TRUE, this->mode, TRUE);
+                                               this->protocol, this->reqid, TRUE, this->mode);
                        
                        if (status != SUCCESS)
                        {
@@ -832,7 +855,7 @@ static status_t update_hosts(private_child_sa_t *this,
                iterator->destroy(iterator);
        }
 
-       /* finally apply hosts */
+       /* apply hosts */
        if (!me->equals(me, this->me.addr))
        {
                this->me.addr->destroy(this->me.addr);
@@ -843,6 +866,10 @@ static status_t update_hosts(private_child_sa_t *this,
                this->other.addr->destroy(this->other.addr);
                this->other.addr = other->clone(other);
        }
+       
+       /* install new iptables rules */
+       updown(this, TRUE);
+       
        return SUCCESS;
 }
 
index b422e01..dc4786a 100644 (file)
@@ -1686,15 +1686,48 @@ static status_t reestablish(private_ike_sa_t *this)
        
        return this->task_manager->initiate(this->task_manager);
 }
+
+/**
+ * get a priority for a src/dst connection path
+ */
+static int get_path_prio(host_t *me, host_t *other)
+{
+       chunk_t a, b;
+       int prio = 1;
        
+       a = me->get_address(me);
+       b = other->get_address(other);
+       
+       while (a.len > 0 && b.len > 0)
+       {
+               if (a.ptr[0] == b.ptr[0])
+               {
+                       prio++;
+               }
+               else
+               {
+                       break;
+               }
+               a = chunk_skip(a, 1);
+               b = chunk_skip(b, 1);
+       }
+       if (me->get_family(me) == AF_INET)
+       {
+               prio *= 4;
+       }
+       return prio;
+}
+
+
 /**
  * Implementation of ike_sa_t.roam.
  */
 static status_t roam(private_ike_sa_t *this)
 {
        iterator_t *iterator;
-       host_t *me, *other;
+       host_t *me, *other, *cand_me, *cand_other;
        ike_mobike_t *mobike;
+       int prio, best = 0;
        
        /* only initiator handles address updated actively */
        if (!this->ike_sa_id->is_initiator(this->ike_sa_id))
@@ -1702,79 +1735,77 @@ static status_t roam(private_ike_sa_t *this)
                return SUCCESS;
        }
        
+       /* get best address pair to use */
+       other = this->other_host;
        me = charon->kernel_interface->get_source_addr(charon->kernel_interface,
-                                                                                                  this->other_host);   
-       if (me && this->my_virtual_ip && me->ip_equals(me, this->my_virtual_ip))
-       {       /* do not roam to the virtual IP of this IKE_SA */
-               me->destroy(me);
-               me = NULL;
-       }
-       
+                                                                                                  other);
        if (me)
        {
-               set_condition(this, COND_STALE, FALSE);
-               /* attachment still the same? */
-               if (me->ip_equals(me, this->my_host))
+               best = get_path_prio(me, other);
+       }
+       iterator = create_additional_address_iterator(this);
+       while (iterator->iterate(iterator, (void**)&cand_other))
+       {
+               cand_me = charon->kernel_interface->get_source_addr(
+                                                                               charon->kernel_interface, cand_other);
+               if (!cand_me)
                {
-                       DBG2(DBG_IKE, "%H still reached through %H, no update needed",
-                                this->other_host, me);
-                       me->destroy(me);
-                       return SUCCESS;
+                       continue;
                }
-               me->set_port(me, this->my_host->get_port(this->my_host));
-               
-               /* our attachement changed, update if we have mobike */
-               if (supports_extension(this, EXT_MOBIKE))
+               if (this->my_virtual_ip && 
+                       cand_me->ip_equals(cand_me, this->my_virtual_ip))
+               {       /* never roam IKE_SA to our virtual IP! */
+                       cand_me->destroy(cand_me);
+                       continue;
+               }
+               prio = get_path_prio(cand_me, cand_other);
+               if (prio > best)
                {
-                       DBG1(DBG_IKE, "requesting address change using MOBIKE");
-                       mobike = ike_mobike_create(&this->public, TRUE);
-                       mobike->roam(mobike, me, NULL);
-                       this->task_manager->queue_task(this->task_manager, (task_t*)mobike);
-                       return this->task_manager->initiate(this->task_manager);
+                       best = prio;
+                       DESTROY_IF(me);
+                       me = cand_me;
+                       other = cand_other;
+               }
+               else
+               {
+                       cand_me->destroy(cand_me);
                }
-               DBG1(DBG_IKE, "reestablishing IKE_SA due address change");
-               /* reestablish if not */
-               set_my_host(this, me);
-               return reestablish(this);
        }
+       iterator->destroy(iterator);
        
-       /* there is nothing we can do without mobike */
-       if (!supports_extension(this, EXT_MOBIKE))
+       if (!me)
        {
+               /* no route found to host, set to stale, wait for a new route */
                set_condition(this, COND_STALE, TRUE);
                return FAILED;
        }
-
-       /* we are unable to reach the peer. Try an alternative address */
-       iterator = create_additional_address_iterator(this);
-       while (iterator->iterate(iterator, (void**)&other))
-       {
-               me = charon->kernel_interface->get_source_addr(charon->kernel_interface,
-                                                                                                          other);
-               if (me && me->ip_equals(me, this->my_virtual_ip))
-               {       /* do not roam to the virtual IP of this IKE_SA */
-                       me->destroy(me);
-                       me = NULL;
-               }
-               
-               if (me)
-               {
-                       /* good, we have a new route. Use MOBIKE to update */
-                       set_condition(this, COND_STALE, FALSE);
-                       iterator->destroy(iterator);
-                       me->set_port(me, this->my_host->get_port(this->my_host));
-                       other->set_port(other, this->other_host->get_port(this->other_host));
-                       mobike = ike_mobike_create(&this->public, TRUE);
-                       mobike->roam(mobike, me, other);
-                       this->task_manager->queue_task(this->task_manager, (task_t*)mobike);
-                       return this->task_manager->initiate(this->task_manager);
-               }
+       
+       set_condition(this, COND_STALE, FALSE);
+       if (me->ip_equals(me, this->my_host) &&
+               other->ip_equals(other, this->other_host))
+       {
+               DBG2(DBG_IKE, "%H still reached through %H, no update needed",
+                        this->other_host, me);
+               me->destroy(me);
+               return SUCCESS;
        }
-       iterator->destroy(iterator);
-
-       /* no route found to host, give up (temporary) */
-       set_condition(this, COND_STALE, TRUE);
-       return FAILED;
+       me->set_port(me, this->my_host->get_port(this->my_host));
+       other = other->clone(other);
+       other->set_port(other, this->other_host->get_port(this->other_host));
+       
+       /* update addresses with mobike, if supported ... */
+       if (supports_extension(this, EXT_MOBIKE))
+       {
+               DBG1(DBG_IKE, "requesting address change using MOBIKE");
+               mobike = ike_mobike_create(&this->public, TRUE);
+               mobike->roam(mobike, me, other);
+               this->task_manager->queue_task(this->task_manager, (task_t*)mobike);
+               return this->task_manager->initiate(this->task_manager);
+       }
+       DBG1(DBG_IKE, "reestablishing IKE_SA due address change");
+       /* ... reestablish if not */
+       set_my_host(this, me);
+       return reestablish(this);
 }
 
 /**