add priority management for kernel policy
[strongswan.git] / src / charon / sa / transactions / rekey_ike_sa.c
index f546540..8f0b869 100644 (file)
@@ -140,17 +140,9 @@ static u_int32_t requested(private_rekey_ike_sa_t *this)
 /**
  * Implementation of rekey_ike_sa_t.use_dh_group.
  */
-static bool use_dh_group(private_rekey_ike_sa_t *this, diffie_hellman_group_t dh_group)
+static void use_dh_group(private_rekey_ike_sa_t *this, diffie_hellman_group_t dh_group)
 {
-       if (this->connection->check_dh_group(this->connection, dh_group))
-       {
-               this->diffie_hellman = diffie_hellman_create(dh_group);
-               if (this->diffie_hellman)
-               {
-                       return TRUE;
-               }
-       }
-       return FALSE;
+       this->diffie_hellman = diffie_hellman_create(dh_group);
 }
 
 /**
@@ -190,6 +182,17 @@ static status_t get_request(private_rekey_ike_sa_t *this, message_t **result)
                return SUCCESS;
        }
        
+       /* check for correct state, except when retrying with another dh group */
+       if (this->ike_sa->get_state(this->ike_sa) != IKE_ESTABLISHED &&
+           !this->diffie_hellman)
+       {
+               this->logger->log(this->logger, ERROR,
+                                                 "tried to rekey in state %s, aborted",
+                                                 mapping_find(ike_sa_state_m,
+                                                                          this->ike_sa->get_state(this->ike_sa)));
+               return FAILED;
+       }
+       
        me = this->ike_sa->get_my_host(this->ike_sa);
        other = this->ike_sa->get_other_host(this->ike_sa);
        
@@ -274,7 +277,7 @@ static status_t get_request(private_rekey_ike_sa_t *this, message_t **result)
                        this->logger->log(this->logger, AUDIT,
                                                          "DH group %s (%d) not supported, aborting",
                                                          mapping_find(diffie_hellman_group_m, dh_group), dh_group);
-                       return DESTROY_ME;
+                       return FAILED;
                }
        }
        
@@ -288,6 +291,10 @@ static status_t get_request(private_rekey_ike_sa_t *this, message_t **result)
        this->message_id = this->ike_sa->get_next_message_id(this->ike_sa);
        request->set_message_id(request, this->message_id);
        
+       /* register us as rekeying to detect multiple rekeying */
+       this->ike_sa->set_state(this->ike_sa, IKE_REKEYING);
+       this->ike_sa->set_rekeying_transaction(this->ike_sa, &this->public);
+       
        return SUCCESS;
 }
 
@@ -369,7 +376,7 @@ static status_t switchto_new_sa(private_rekey_ike_sa_t* this, bool initiator)
        other_id = this->ike_sa->get_other_id(this->ike_sa);
        my_host = this->ike_sa->get_my_host(this->ike_sa);
        other_host = this->ike_sa->get_other_host(this->ike_sa);
-       name = this->connection->get_name(this->connection);
+       name = this->ike_sa->get_name(this->ike_sa);
        
        this->new_sa->set_my_id(this->new_sa, my_id->clone(my_id));
        this->new_sa->set_other_id(this->new_sa, other_id->clone(other_id));
@@ -380,7 +387,8 @@ static status_t switchto_new_sa(private_rekey_ike_sa_t* this, bool initiator)
        if (this->new_sa->derive_keys(this->new_sa, this->proposal,
                                                                  this->diffie_hellman, 
                                                                  this->nonce_i, this->nonce_r, initiator,
-                                                                 this->ike_sa->get_child_prf(this->ike_sa)
+                                                                 this->ike_sa->get_child_prf(this->ike_sa),
+                                                                 this->ike_sa->get_prf(this->ike_sa)
                                                                 ) != SUCCESS)
        {
                return FAILED;
@@ -388,14 +396,9 @@ static status_t switchto_new_sa(private_rekey_ike_sa_t* this, bool initiator)
        
        this->new_sa->set_state(this->new_sa, IKE_ESTABLISHED);
        
-       this->new_sa->adopt_children(this->new_sa, this->ike_sa);
-       
        this->new_sa->set_lifetimes(this->new_sa,
                                                this->connection->get_soft_lifetime(this->connection),
                                                this->connection->get_hard_lifetime(this->connection));
-       
-       charon->ike_sa_manager->checkin(charon->ike_sa_manager, this->new_sa);
-       this->new_sa = NULL;
        return SUCCESS;
 }
 
@@ -433,7 +436,8 @@ static status_t get_response(private_rekey_ike_sa_t *this, message_t *request,
        host_t *me, *other;
        message_t *response;
        status_t status;
-       iterator_t *payloads;
+       iterator_t *payloads, *iterator;
+       child_sa_t *child_sa;
        sa_payload_t *sa_request = NULL;
        nonce_payload_t *nonce_request = NULL;
        ke_payload_t *ke_request = NULL;
@@ -469,9 +473,54 @@ static status_t get_response(private_rekey_ike_sa_t *this, message_t *request,
                return FAILED;
        }
        
+       /* if we already initiate a delete, we do not allow rekeying */
+       if (this->ike_sa->get_state(this->ike_sa) == IKE_DELETING)
+       {
+               build_notify(NO_PROPOSAL_CHOSEN, CHUNK_INITIALIZER, response, TRUE);
+               this->logger->log(this->logger, CONTROL,
+                                                 "unable to rekey, as delete in progress. Sending NO_PROPOSAL_CHOSEN");
+               return FAILED;
+       }
+       
+       /* if we have a CHILD which is "half-open", we do not allow rekeying */
+       iterator = this->ike_sa->create_child_sa_iterator(this->ike_sa);
+       while (iterator->iterate(iterator, (void**)&child_sa))
+       {
+               child_sa_state_t state = child_sa->get_state(child_sa);
+               if (state == CHILD_CREATED ||
+                       state == CHILD_REKEYING ||
+                       state == CHILD_DELETING)
+               {
+                       build_notify(NO_PROPOSAL_CHOSEN, CHUNK_INITIALIZER, response, TRUE);
+                       this->logger->log(this->logger, CONTROL,
+                                                         "unable to rekey, one CHILD_SA is half open. Sending NO_PROPOSAL_CHOSEN");
+                       iterator->destroy(iterator);
+                       return FAILED;
+               }
+       }
+       iterator->destroy(iterator);
+       
        /* apply for notify processing */
        this->next = next;
        
+       
+       /* get a connection to replace current IKE_SA */
+       this->connection = charon->connections->get_connection_by_name(
+                                       charon->connections, this->ike_sa->get_name(this->ike_sa));
+       /* if connection lookup by name fails, try it with the hosts */
+       if (this->connection == NULL)
+       {
+               this->connection = charon->connections->get_connection_by_hosts(
+                                                               charon->connections, me, other);
+               if (this->connection == NULL)
+               {
+                       this->logger->log(this->logger, ERROR,
+                                                               "no connection found to rekey IKE_SA, sending NO_RROPOSAL_CHOSEN");
+                       build_notify(NO_PROPOSAL_CHOSEN, CHUNK_INITIALIZER, response, TRUE);
+                       return FAILED;
+               }
+       }
+       
        /* Iterate over all payloads. */
        payloads = request->get_payload_iterator(request);
        while (payloads->has_next(payloads))
@@ -533,26 +582,6 @@ static status_t get_response(private_rekey_ike_sa_t *this, message_t *request,
                nonce_response->set_nonce(nonce_response, this->nonce_r);
        }
        
-       {       /* get a connection to replace current IKE_SA */
-               this->connection = charon->connections->get_connection_by_name(
-                                                       charon->connections,
-                                                       this->ike_sa->get_name(this->ike_sa));
-               /* if connection lookup by name fails, try it with the hosts */
-               if (this->connection == NULL)
-               {
-                       this->connection = charon->connections->get_connection_by_hosts(
-                                                                                                               charon->connections,
-                                                                                                               me, other);
-                       if (this->connection == NULL)
-                       {
-                               this->logger->log(this->logger, ERROR,
-                                                                 "no connection found to rekey IKE_SA, sending NO_RROPOSAL_CHOSEN");
-                               build_notify(NO_PROPOSAL_CHOSEN, CHUNK_INITIALIZER, response, TRUE);
-                               return FAILED;
-                       }
-               }
-       }
-       
        {       /* process SA payload */
                linked_list_t *proposal_list;
                sa_payload_t *sa_response;
@@ -612,7 +641,7 @@ static status_t get_response(private_rekey_ike_sa_t *this, message_t *request,
                        notify_chunk.ptr = (u_int8_t*)&notify_group;
                        notify_chunk.len = sizeof(notify_group);
                        build_notify(INVALID_KE_PAYLOAD, notify_chunk, response, TRUE);
-                       return DESTROY_ME;
+                       return FAILED;
                }
                this->diffie_hellman->set_other_public_value(this->diffie_hellman,
                                                                ke_request->get_key_exchange_data(ke_request));
@@ -622,7 +651,49 @@ static status_t get_response(private_rekey_ike_sa_t *this, message_t *request,
                response->add_payload(response, (payload_t*)ke_response);
        }
        
-       return switchto_new_sa(this, FALSE);
+       status = switchto_new_sa(this, FALSE);
+       if (status != SUCCESS)
+       {
+               return status;
+       }
+       
+       /* IKE_SA successfully created. If another transaction is already rekeying
+        * this SA, our lower nonce must be registered for a later nonce compare. */
+       {
+               private_rekey_ike_sa_t *other;
+               
+               other = this->ike_sa->get_rekeying_transaction(this->ike_sa);
+               if (other)
+               {
+                       /* store our lower nonce in the simultaneus transaction, we 
+                        * will later compare it against his nonces when we calls conclude().
+                        * We do not adopt childrens yet, as we don't know if we'll win
+                        * the race...
+                        */
+                       if (memcmp(this->nonce_i.ptr, this->nonce_r.ptr,
+                               min(this->nonce_i.len, this->nonce_r.len)) < 0)
+                       {
+                               other->nonce_s = chunk_clone(this->nonce_i);
+                       }
+                       else
+                       {
+                               other->nonce_s = chunk_clone(this->nonce_r);
+                       }
+                       /* overwrite "other" in IKE_SA, allows "other" to access "this" */
+                       this->ike_sa->set_rekeying_transaction(this->ike_sa, &this->public);
+               }
+               else
+               {
+                       /* if we have no simultaneus transaction, we can safely adopt 
+                        * all children and complete. */
+                       this->new_sa->adopt_children(this->new_sa, this->ike_sa);
+                       charon->ike_sa_manager->checkin(charon->ike_sa_manager, this->new_sa);
+                       this->new_sa = NULL;
+               }
+               this->ike_sa->set_state(this->ike_sa, IKE_REKEYING);
+       }
+       
+       return SUCCESS;
 }
 
 /**
@@ -636,6 +707,7 @@ static status_t conclude(private_rekey_ike_sa_t *this, message_t *response,
        sa_payload_t *sa_payload = NULL;
        nonce_payload_t *nonce_payload = NULL;
        ke_payload_t *ke_payload = NULL;
+       private_rekey_ike_sa_t *other_trans;
        status_t status;
        
        /* check message type */
@@ -726,18 +798,72 @@ static status_t conclude(private_rekey_ike_sa_t *this, message_t *response,
                                                        ke_payload->get_key_exchange_data(ke_payload));
        }
        
-       if (switchto_new_sa(this, TRUE) == SUCCESS)
-       {
-               /* new IKE_SA is in use now, delete old */
-               *next = (transaction_t*)delete_ike_sa_create(this->ike_sa);
-               return SUCCESS;
-       }
-       else
+       if (switchto_new_sa(this, TRUE) != SUCCESS)
        {
                /* this should not happen. But if, we destroy both SAs */
                *next = (transaction_t*)delete_ike_sa_create(this->new_sa);
                return DESTROY_ME;
        }
+       
+       /* IKE_SA successfully created. If the other peer initiated rekeying
+        * in the meantime, we detect this by comparing the rekeying_transaction
+        * of the SA. If it changed, we are not alone. Then we must compare the nonces.
+        * If no simultaneous rekeying is going on, we just initiate the delete of
+        * the superseded SA. */
+       other_trans = this->ike_sa->get_rekeying_transaction(this->ike_sa);
+       this->ike_sa->set_rekeying_transaction(this->ike_sa, NULL);
+       
+       if (this->nonce_s.ptr)
+       {       /* simlutaneous rekeying is going on, not so good */
+               chunk_t this_lowest;
+               
+               /* first get our lowest nonce */
+               if (memcmp(this->nonce_i.ptr, this->nonce_r.ptr,
+                       min(this->nonce_i.len, this->nonce_r.len)) < 0)
+               {
+                       this_lowest = this->nonce_i;
+               }
+               else
+               {
+                       this_lowest = this->nonce_r;
+               }
+               /* then compare against other lowest nonce */
+               if (memcmp(this_lowest.ptr, this->nonce_s.ptr,
+                       min(this_lowest.len, this->nonce_s.len)) < 0)
+               {
+                       this->logger->log(this->logger, ERROR,
+                                                               "detected simultaneous IKE_SA rekeying, deleting ours");
+                       this->lost = TRUE;
+               }
+               else
+               {
+                       this->logger->log(this->logger, ERROR,
+                                                               "detected simultaneous IKE_SA rekeying, but ours is preferred");
+               }
+               if (this->lost)
+               {
+                       /* the other has won, he gets our children */
+                       other_trans->new_sa->adopt_children(other_trans->new_sa, this->ike_sa);
+                       /* we have lost simlutaneous rekeying, delete the SA we just have created */
+                       this->new_sa->delete(this->new_sa);
+               }
+               /* other trans' SA is still not checked in, so do it now. It's SA will get
+                * deleted by remote peer. */
+               charon->ike_sa_manager->checkin(charon->ike_sa_manager, other_trans->new_sa);
+               other_trans->new_sa = NULL;
+       }
+       
+       if (!this->lost)
+       {
+               /* we have won. delete old IKE_SA, and migrate all children */
+               *next = (transaction_t*)delete_ike_sa_create(this->ike_sa);
+               this->new_sa->adopt_children(this->new_sa, this->ike_sa);
+       }
+       
+       charon->ike_sa_manager->checkin(charon->ike_sa_manager, this->new_sa);
+       this->new_sa = NULL;
+       
+       return SUCCESS;
 }
 
 /**
@@ -777,7 +903,7 @@ rekey_ike_sa_t *rekey_ike_sa_create(ike_sa_t *ike_sa)
        this->public.transaction.destroy = (void(*)(transaction_t*))destroy;
        
        /* public functions */
-       this->public.use_dh_group = (bool(*)(rekey_ike_sa_t*,diffie_hellman_group_t))use_dh_group;
+       this->public.use_dh_group = (void(*)(rekey_ike_sa_t*,diffie_hellman_group_t))use_dh_group;
        this->public.cancel = (void(*)(rekey_ike_sa_t*))cancel;
        
        /* private data */