diff options
author | Yves-Alexis Perez <corsac@debian.org> | 2016-07-16 15:19:53 +0200 |
---|---|---|
committer | Yves-Alexis Perez <corsac@debian.org> | 2016-07-16 15:19:53 +0200 |
commit | bf372706c469764d59e9f29c39e3ecbebd72b8d2 (patch) | |
tree | 0f0e296e2d50e4a7faf99ae6fa428d2681e81ea1 /src/libcharon/sa/ikev2 | |
parent | 518dd33c94e041db0444c7d1f33da363bb8e3faf (diff) | |
download | vyos-strongswan-bf372706c469764d59e9f29c39e3ecbebd72b8d2.tar.gz vyos-strongswan-bf372706c469764d59e9f29c39e3ecbebd72b8d2.zip |
Imported Upstream version 5.5.0
Diffstat (limited to 'src/libcharon/sa/ikev2')
20 files changed, 707 insertions, 357 deletions
diff --git a/src/libcharon/sa/ikev2/authenticators/eap_authenticator.c b/src/libcharon/sa/ikev2/authenticators/eap_authenticator.c index 91f6187f9..3ab59fada 100644 --- a/src/libcharon/sa/ikev2/authenticators/eap_authenticator.c +++ b/src/libcharon/sa/ikev2/authenticators/eap_authenticator.c @@ -104,7 +104,7 @@ struct private_eap_authenticator_t { * load an EAP method */ static eap_method_t *load_method(private_eap_authenticator_t *this, - eap_type_t type, u_int32_t vendor, eap_role_t role) + eap_type_t type, uint32_t vendor, eap_role_t role) { identification_t *server, *peer, *aaa; auth_cfg_t *auth; @@ -143,7 +143,7 @@ static eap_payload_t* server_initiate_eap(private_eap_authenticator_t *this, auth_cfg_t *auth; eap_type_t type; identification_t *id; - u_int32_t vendor; + uint32_t vendor; eap_payload_t *out; char *action; @@ -237,7 +237,7 @@ static eap_payload_t* server_process_eap(private_eap_authenticator_t *this, eap_payload_t *in) { eap_type_t type, received_type, conf_type; - u_int32_t vendor, received_vendor, conf_vendor; + uint32_t vendor, received_vendor, conf_vendor; eap_payload_t *out; auth_cfg_t *auth; @@ -341,7 +341,7 @@ static eap_payload_t* client_process_eap(private_eap_authenticator_t *this, eap_payload_t *in) { eap_type_t type, conf_type; - u_int32_t vendor, conf_vendor; + uint32_t vendor, conf_vendor; auth_cfg_t *auth; eap_payload_t *out; identification_t *id; @@ -449,7 +449,7 @@ static bool verify_auth(private_eap_authenticator_t *this, message_t *message, auth_cfg_t *auth; keymat_v2_t *keymat; eap_type_t type; - u_int32_t vendor; + uint32_t vendor; auth_payload = (auth_payload_t*)message->get_payload(message, PLV2_AUTH); @@ -595,7 +595,7 @@ METHOD(authenticator_t, process_client, status_t, } if (this->require_mutual && !this->method->is_mutual(this->method)) { /* we require mutual authentication due to EAP-only */ - u_int32_t vendor; + uint32_t vendor; DBG1(DBG_IKE, "EAP-only authentication requires a mutual and " "MSK deriving EAP method, but %N is not", @@ -623,7 +623,7 @@ METHOD(authenticator_t, process_client, status_t, case EAP_SUCCESS: { eap_type_t type; - u_int32_t vendor; + uint32_t vendor; auth_cfg_t *cfg; if (this->method->get_msk(this->method, &this->msk) == SUCCESS) @@ -685,7 +685,7 @@ METHOD(authenticator_t, is_mutual, bool, { if (this->method) { - u_int32_t vendor; + uint32_t vendor; if (this->method->get_type(this->method, &vendor) != EAP_IDENTITY || vendor != 0) diff --git a/src/libcharon/sa/ikev2/authenticators/pubkey_authenticator.c b/src/libcharon/sa/ikev2/authenticators/pubkey_authenticator.c index 04ccd4f4f..6fd34e0a6 100644 --- a/src/libcharon/sa/ikev2/authenticators/pubkey_authenticator.c +++ b/src/libcharon/sa/ikev2/authenticators/pubkey_authenticator.c @@ -63,7 +63,7 @@ struct private_pubkey_authenticator_t { static bool parse_signature_auth_data(chunk_t *auth_data, key_type_t *key_type, signature_scheme_t *scheme) { - u_int8_t len; + uint8_t len; int oid; if (!auth_data->len) @@ -91,7 +91,7 @@ static bool build_signature_auth_data(chunk_t *auth_data, signature_scheme_t scheme) { chunk_t data; - u_int8_t len; + uint8_t len; int oid; oid = signature_scheme_to_oid(scheme); diff --git a/src/libcharon/sa/ikev2/connect_manager.c b/src/libcharon/sa/ikev2/connect_manager.c index 161c4fdaf..280796d8c 100644 --- a/src/libcharon/sa/ikev2/connect_manager.c +++ b/src/libcharon/sa/ikev2/connect_manager.c @@ -92,10 +92,10 @@ typedef struct endpoint_pair_t endpoint_pair_t; */ struct endpoint_pair_t { /** pair id */ - u_int32_t id; + uint32_t id; /** priority */ - u_int64_t priority; + uint64_t priority; /** local endpoint */ host_t *local; @@ -107,7 +107,7 @@ struct endpoint_pair_t { check_state_t state; /** number of retransmissions */ - u_int32_t retransmitted; + uint32_t retransmitted; /** the generated packet */ packet_t *packet; @@ -132,8 +132,8 @@ static endpoint_pair_t *endpoint_pair_create(endpoint_notify_t *initiator, { endpoint_pair_t *this; - u_int32_t pi = initiator->get_priority(initiator); - u_int32_t pr = responder->get_priority(responder); + uint32_t pi = initiator->get_priority(initiator); + uint32_t pr = responder->get_priority(responder); INIT(this, .priority = pow(2, 32) * min(pi, pr) + 2 * max(pi, pr) @@ -313,7 +313,7 @@ typedef struct check_t check_t; */ struct check_t { /** message id */ - u_int32_t mid; + uint32_t mid; /** source of the connectivity check */ host_t *src; @@ -375,7 +375,7 @@ struct callback_data_t { chunk_t connect_id; /** message (pair) id */ - u_int32_t mid; + uint32_t mid; }; /** @@ -406,7 +406,7 @@ static callback_data_t *callback_data_create(private_connect_manager_t *connect_ * Creates a new retransmission data object */ static callback_data_t *retransmit_data_create(private_connect_manager_t *connect_manager, - chunk_t connect_id, u_int32_t mid) + chunk_t connect_id, uint32_t mid) { callback_data_t *this = callback_data_create(connect_manager, connect_id); this->mid = mid; @@ -576,7 +576,7 @@ static status_t get_pair_by_hosts(linked_list_t *pairs, host_t *local, (void**)pair, local, remote); } -static bool match_pair_by_id(endpoint_pair_t *current, u_int32_t *id) +static bool match_pair_by_id(endpoint_pair_t *current, uint32_t *id) { return current->id == *id; } @@ -584,7 +584,7 @@ static bool match_pair_by_id(endpoint_pair_t *current, u_int32_t *id) /** * Searches for a pair with a specific id */ -static status_t get_pair_by_id(check_list_t *checklist, u_int32_t id, +static status_t get_pair_by_id(check_list_t *checklist, uint32_t id, endpoint_pair_t **pair) { return checklist->pairs->find_first(checklist->pairs, @@ -669,7 +669,7 @@ static void prune_pairs(linked_list_t *pairs) { enumerator_t *enumerator, *search; endpoint_pair_t *current, *other; - u_int32_t id = 0; + uint32_t id = 0; enumerator = pairs->create_enumerator(pairs); search = pairs->create_enumerator(pairs); @@ -826,7 +826,7 @@ static status_t process_payloads(message_t *message, check_t *check) static chunk_t build_signature(private_connect_manager_t *this, check_list_t *checklist, check_t *check, bool outbound) { - u_int32_t mid; + uint32_t mid; chunk_t mid_chunk, key_chunk, sig_chunk; chunk_t sig_hash; @@ -851,7 +851,7 @@ static chunk_t build_signature(private_connect_manager_t *this, } static void queue_retransmission(private_connect_manager_t *this, check_list_t *checklist, endpoint_pair_t *pair); -static void schedule_checks(private_connect_manager_t *this, check_list_t *checklist, u_int32_t time); +static void schedule_checks(private_connect_manager_t *this, check_list_t *checklist, uint32_t time); static void finish_checks(private_connect_manager_t *this, check_list_t *checklist); /** @@ -1019,11 +1019,11 @@ static void queue_retransmission(private_connect_manager_t *this, check_list_t * job = (job_t*)callback_job_create((callback_job_cb_t)retransmit, data, (callback_job_cleanup_t)callback_data_destroy, NULL); - u_int32_t retransmission = pair->retransmitted + 1; - u_int32_t rto = ME_INTERVAL; + uint32_t retransmission = pair->retransmitted + 1; + uint32_t rto = ME_INTERVAL; if (retransmission > ME_BOOST) { - rto = (u_int32_t)(ME_INTERVAL * pow(ME_RETRANS_BASE, retransmission - ME_BOOST)); + rto = (uint32_t)(ME_INTERVAL * pow(ME_RETRANS_BASE, retransmission - ME_BOOST)); } DBG2(DBG_IKE, "scheduling retransmission %d of pair '%d' in %dms", retransmission, pair->id, rto); @@ -1165,7 +1165,7 @@ static job_requeue_t sender(callback_data_t *data) * Schedules checks for a checklist (time in ms) */ static void schedule_checks(private_connect_manager_t *this, - check_list_t *checklist, u_int32_t time) + check_list_t *checklist, uint32_t time) { callback_data_t *data = callback_data_create(this, checklist->connect_id); checklist->sender = (job_t*)callback_job_create((callback_job_cb_t)sender, diff --git a/src/libcharon/sa/ikev2/keymat_v2.c b/src/libcharon/sa/ikev2/keymat_v2.c index 55cb5dd9c..e37399841 100644 --- a/src/libcharon/sa/ikev2/keymat_v2.c +++ b/src/libcharon/sa/ikev2/keymat_v2.c @@ -99,8 +99,8 @@ METHOD(keymat_t, create_nonce_gen, nonce_gen_t*, /** * Derive IKE keys for a combined AEAD algorithm */ -static bool derive_ike_aead(private_keymat_v2_t *this, u_int16_t alg, - u_int16_t key_size, prf_plus_t *prf_plus) +static bool derive_ike_aead(private_keymat_v2_t *this, uint16_t alg, + uint16_t key_size, prf_plus_t *prf_plus) { aead_t *aead_i, *aead_r; chunk_t key = chunk_empty; @@ -189,8 +189,8 @@ failure: /** * Derive IKE keys for traditional encryption and MAC algorithms */ -static bool derive_ike_traditional(private_keymat_v2_t *this, u_int16_t enc_alg, - u_int16_t enc_size, u_int16_t int_alg, prf_plus_t *prf_plus) +static bool derive_ike_traditional(private_keymat_v2_t *this, uint16_t enc_alg, + uint16_t enc_size, uint16_t int_alg, prf_plus_t *prf_plus) { crypter_t *crypter_i = NULL, *crypter_r = NULL; signer_t *signer_i, *signer_r; @@ -302,11 +302,11 @@ METHOD(keymat_v2_t, derive_ike_keys, bool, chunk_t skeyseed, key, secret, full_nonce, fixed_nonce, prf_plus_seed; chunk_t spi_i, spi_r; prf_plus_t *prf_plus = NULL; - u_int16_t alg, key_size, int_alg; + uint16_t alg, key_size, int_alg; prf_t *rekey_prf = NULL; - spi_i = chunk_alloca(sizeof(u_int64_t)); - spi_r = chunk_alloca(sizeof(u_int64_t)); + spi_i = chunk_alloca(sizeof(uint64_t)); + spi_r = chunk_alloca(sizeof(uint64_t)); if (!dh->get_shared_secret(dh, &secret)) { @@ -354,8 +354,8 @@ METHOD(keymat_v2_t, derive_ike_keys, bool, break; } fixed_nonce = chunk_cat("cc", nonce_i, nonce_r); - *((u_int64_t*)spi_i.ptr) = id->get_initiator_spi(id); - *((u_int64_t*)spi_r.ptr) = id->get_responder_spi(id); + *((uint64_t*)spi_i.ptr) = id->get_initiator_spi(id); + *((uint64_t*)spi_r.ptr) = id->get_responder_spi(id); prf_plus_seed = chunk_cat("ccc", full_nonce, spi_i, spi_r); /* KEYMAT = prf+ (SKEYSEED, Ni | Nr | SPIi | SPIr) @@ -489,7 +489,7 @@ METHOD(keymat_v2_t, derive_child_keys, bool, chunk_t nonce_i, chunk_t nonce_r, chunk_t *encr_i, chunk_t *integ_i, chunk_t *encr_r, chunk_t *integ_r) { - u_int16_t enc_alg, int_alg, enc_size = 0, int_size = 0; + uint16_t enc_alg, int_alg, enc_size = 0, int_size = 0; chunk_t seed, secret = chunk_empty; prf_plus_t *prf_plus; diff --git a/src/libcharon/sa/ikev2/task_manager_v2.c b/src/libcharon/sa/ikev2/task_manager_v2.c index c2f972ab1..41a4e1b75 100644 --- a/src/libcharon/sa/ikev2/task_manager_v2.c +++ b/src/libcharon/sa/ikev2/task_manager_v2.c @@ -1,7 +1,7 @@ /* - * Copyright (C) 2007-2015 Tobias Brunner + * Copyright (C) 2007-2016 Tobias Brunner * Copyright (C) 2007-2010 Martin Willi - * Hochschule fuer Technik Rapperswil + * HSR Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -43,30 +43,14 @@ #include <encoding/payloads/unknown_payload.h> #include <processing/jobs/retransmit_job.h> #include <processing/jobs/delete_ike_sa_job.h> +#include <processing/jobs/initiate_tasks_job.h> #ifdef ME #include <sa/ikev2/tasks/ike_me.h> #endif -typedef struct exchange_t exchange_t; - -/** - * An exchange in the air, used do detect and handle retransmission - */ -struct exchange_t { - - /** - * Message ID used for this transaction - */ - u_int32_t mid; - - /** - * generated packet for retransmission - */ - packet_t *packet; -}; - typedef struct private_task_manager_t private_task_manager_t; +typedef struct queued_task_t queued_task_t; /** * private data of the task manager @@ -90,7 +74,7 @@ struct private_task_manager_t { /** * Message ID of the exchange */ - u_int32_t mid; + uint32_t mid; /** * packet(s) for retransmission @@ -111,7 +95,7 @@ struct private_task_manager_t { /** * Message ID of the exchange */ - u_int32_t mid; + uint32_t mid; /** * how many times we have retransmitted so far @@ -182,6 +166,22 @@ struct private_task_manager_t { }; /** + * Queued tasks + */ +struct queued_task_t { + + /** + * Queued task + */ + task_t *task; + + /** + * Time before which the task is not to be initiated + */ + timeval_t time; +}; + +/** * Reset retransmission packet list */ static void clear_packets(array_t *array) @@ -216,6 +216,12 @@ METHOD(task_manager_t, flush_queue, void, } while (array_remove(array, ARRAY_TAIL, &task)) { + if (queue == TASK_QUEUE_QUEUED) + { + queued_task_t *queued = (queued_task_t*)task; + task = queued->task; + free(queued); + } task->destroy(task); } } @@ -229,22 +235,28 @@ METHOD(task_manager_t, flush, void, } /** - * move a task of a specific type from the queue to the active list + * Move a task of a specific type from the queue to the active list, if it is + * not delayed. */ static bool activate_task(private_task_manager_t *this, task_type_t type) { enumerator_t *enumerator; - task_t *task; + queued_task_t *queued; + timeval_t now; bool found = FALSE; + time_monotonic(&now); + enumerator = array_create_enumerator(this->queued_tasks); - while (enumerator->enumerate(enumerator, (void**)&task)) + while (enumerator->enumerate(enumerator, (void**)&queued)) { - if (task->get_type(task) == type) + if (queued->task->get_type(queued->task) == type && + !timercmp(&now, &queued->time, <)) { DBG2(DBG_IKE, " activating %N task", task_type_names, type); array_remove_at(this->queued_tasks, enumerator); - array_insert(this->active_tasks, ARRAY_TAIL, task); + array_insert(this->active_tasks, ARRAY_TAIL, queued->task); + free(queued); found = TRUE; break; } @@ -303,12 +315,12 @@ static bool generate_message(private_task_manager_t *this, message_t *message, } METHOD(task_manager_t, retransmit, status_t, - private_task_manager_t *this, u_int32_t message_id) + private_task_manager_t *this, uint32_t message_id) { if (message_id == this->initiating.mid && array_count(this->initiating.packets)) { - u_int32_t timeout; + uint32_t timeout; job_t *job; enumerator_t *enumerator; packet_t *packet; @@ -336,7 +348,7 @@ METHOD(task_manager_t, retransmit, status_t, { if (this->initiating.retransmitted <= this->retransmit_tries) { - timeout = (u_int32_t)(this->retransmit_timeout * 1000.0 * + timeout = (uint32_t)(this->retransmit_timeout * 1000.0 * pow(this->retransmit_base, this->initiating.retransmitted)); } else @@ -352,7 +364,8 @@ METHOD(task_manager_t, retransmit, status_t, { DBG1(DBG_IKE, "retransmit %d of request with message ID %d", this->initiating.retransmitted, message_id); - charon->bus->alert(charon->bus, ALERT_RETRANSMIT_SEND, packet); + charon->bus->alert(charon->bus, ALERT_RETRANSMIT_SEND, packet, + this->initiating.retransmitted); } if (!mobike) { @@ -534,6 +547,7 @@ METHOD(task_manager_t, initiate, status_t, break; } case IKE_REKEYING: + case IKE_REKEYED: if (activate_task(this, TASK_IKE_DELETE)) { exchange = INFORMATIONAL; @@ -610,7 +624,8 @@ METHOD(task_manager_t, initiate, status_t, case FAILED: default: this->initiating.type = EXCHANGE_TYPE_UNDEFINED; - if (this->ike_sa->get_state(this->ike_sa) != IKE_CONNECTING) + if (this->ike_sa->get_state(this->ike_sa) != IKE_CONNECTING && + this->ike_sa->get_state(this->ike_sa) != IKE_REKEYED) { charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); } @@ -694,6 +709,13 @@ static status_t process_response(private_task_manager_t *this, } enumerator->destroy(enumerator); + if (this->initiating.retransmitted) + { + packet_t *packet = NULL; + array_get(this->initiating.packets, 0, &packet); + charon->bus->alert(charon->bus, ALERT_RETRANSMIT_SEND_CLEARED, packet); + } + /* catch if we get resetted while processing */ this->reset = FALSE; enumerator = array_create_enumerator(this->active_tasks); @@ -751,8 +773,7 @@ static bool handle_collisions(private_task_manager_t *this, task_t *task) /* do we have to check */ if (type == TASK_IKE_REKEY || type == TASK_CHILD_REKEY || - type == TASK_CHILD_DELETE || type == TASK_IKE_DELETE || - type == TASK_IKE_REAUTH) + type == TASK_CHILD_DELETE || type == TASK_IKE_DELETE) { /* find an exchange collision, and notify these tasks */ enumerator = array_create_enumerator(this->active_tasks); @@ -761,8 +782,7 @@ static bool handle_collisions(private_task_manager_t *this, task_t *task) switch (active->get_type(active)) { case TASK_IKE_REKEY: - if (type == TASK_IKE_REKEY || type == TASK_IKE_DELETE || - type == TASK_IKE_REAUTH) + if (type == TASK_IKE_REKEY || type == TASK_IKE_DELETE) { ike_rekey_t *rekey = (ike_rekey_t*)active; rekey->collide(rekey, task); @@ -799,7 +819,7 @@ static status_t build_response(private_task_manager_t *this, message_t *request) host_t *me, *other; bool delete = FALSE, hook = FALSE; ike_sa_id_t *id = NULL; - u_int64_t responder_spi = 0; + uint64_t responder_spi = 0; bool result; me = request->get_destination(request); @@ -839,6 +859,10 @@ static status_t build_response(private_task_manager_t *this, message_t *request) /* FALL */ case DESTROY_ME: /* destroy IKE_SA, but SEND response first */ + if (handle_collisions(this, task)) + { + array_remove_at(this->passive_tasks, enumerator); + } delete = TRUE; break; } @@ -901,9 +925,11 @@ static status_t process_request(private_task_manager_t *this, payload_t *payload; notify_payload_t *notify; delete_payload_t *delete; + ike_sa_state_t state; if (array_count(this->passive_tasks) == 0) { /* create tasks depending on request type, if not already some queued */ + state = this->ike_sa->get_state(this->ike_sa); switch (message->get_exchange_type(message)) { case IKE_SA_INIT: @@ -939,8 +965,8 @@ static status_t process_request(private_task_manager_t *this, { /* FIXME: we should prevent this on mediation connections */ bool notify_found = FALSE, ts_found = FALSE; - if (this->ike_sa->get_state(this->ike_sa) == IKE_CREATED || - this->ike_sa->get_state(this->ike_sa) == IKE_CONNECTING) + if (state == IKE_CREATED || + state == IKE_CONNECTING) { DBG1(DBG_IKE, "received CREATE_CHILD_SA request for " "unestablished IKE_SA, rejected"); @@ -1005,6 +1031,14 @@ static status_t process_request(private_task_manager_t *this, case PLV2_NOTIFY: { notify = (notify_payload_t*)payload; + if (state == IKE_REKEYED) + { + DBG1(DBG_IKE, "received unexpected notify %N " + "for rekeyed IKE_SA, ignored", + notify_type_names, + notify->get_notify_type(notify)); + break; + } switch (notify->get_notify_type(notify)) { case ADDITIONAL_IP4_ADDRESS: @@ -1252,7 +1286,7 @@ static void send_notify_response(private_task_manager_t *this, static status_t parse_message(private_task_manager_t *this, message_t *msg) { status_t status; - u_int8_t type = 0; + uint8_t type = 0; status = msg->parse_body(msg, this->ike_sa->get_keymat(this->ike_sa)); @@ -1345,8 +1379,10 @@ METHOD(task_manager_t, process_message, status_t, { host_t *me, *other; status_t status; - u_int32_t mid; + uint32_t mid; bool schedule_delete_job = FALSE; + ike_sa_state_t state; + exchange_type_t type; charon->bus->message(charon->bus, msg, TRUE, FALSE); status = parse_message(this, msg); @@ -1387,15 +1423,16 @@ METHOD(task_manager_t, process_message, status_t, { if (mid == this->responding.mid) { - /* reject initial messages if not received in specific states */ - if ((msg->get_exchange_type(msg) == IKE_SA_INIT && - this->ike_sa->get_state(this->ike_sa) != IKE_CREATED) || - (msg->get_exchange_type(msg) == IKE_AUTH && - this->ike_sa->get_state(this->ike_sa) != IKE_CONNECTING)) + /* reject initial messages if not received in specific states, + * after rekeying we only expect a DELETE in an INFORMATIONAL */ + type = msg->get_exchange_type(msg); + state = this->ike_sa->get_state(this->ike_sa); + if ((type == IKE_SA_INIT && state != IKE_CREATED) || + (type == IKE_AUTH && state != IKE_CONNECTING) || + (state == IKE_REKEYED && type != INFORMATIONAL)) { DBG1(DBG_IKE, "ignoring %N in IKE_SA state %N", - exchange_type_names, msg->get_exchange_type(msg), - ike_sa_state_names, this->ike_sa->get_state(this->ike_sa)); + exchange_type_names, type, ike_sa_state_names, state); return FAILED; } if (!this->ike_sa->supports_extension(this->ike_sa, EXT_MOBIKE)) @@ -1499,18 +1536,19 @@ METHOD(task_manager_t, process_message, status_t, return SUCCESS; } -METHOD(task_manager_t, queue_task, void, - private_task_manager_t *this, task_t *task) +METHOD(task_manager_t, queue_task_delayed, void, + private_task_manager_t *this, task_t *task, uint32_t delay) { + enumerator_t *enumerator; + queued_task_t *queued; + timeval_t time; + if (task->get_type(task) == TASK_IKE_MOBIKE) { /* there is no need to queue more than one mobike task */ - enumerator_t *enumerator; - task_t *current; - enumerator = array_create_enumerator(this->queued_tasks); - while (enumerator->enumerate(enumerator, ¤t)) + while (enumerator->enumerate(enumerator, &queued)) { - if (current->get_type(current) == TASK_IKE_MOBIKE) + if (queued->task->get_type(queued->task) == TASK_IKE_MOBIKE) { enumerator->destroy(enumerator); task->destroy(task); @@ -1519,8 +1557,35 @@ METHOD(task_manager_t, queue_task, void, } enumerator->destroy(enumerator); } - DBG2(DBG_IKE, "queueing %N task", task_type_names, task->get_type(task)); - array_insert(this->queued_tasks, ARRAY_TAIL, task); + time_monotonic(&time); + if (delay) + { + job_t *job; + + DBG2(DBG_IKE, "queueing %N task (delayed by %us)", task_type_names, + task->get_type(task), delay); + time.tv_sec += delay; + + job = (job_t*)initiate_tasks_job_create( + this->ike_sa->get_id(this->ike_sa)); + lib->scheduler->schedule_job_tv(lib->scheduler, job, time); + } + else + { + DBG2(DBG_IKE, "queueing %N task", task_type_names, + task->get_type(task)); + } + INIT(queued, + .task = task, + .time = time, + ); + array_insert(this->queued_tasks, ARRAY_TAIL, queued); +} + +METHOD(task_manager_t, queue_task, void, + private_task_manager_t *this, task_t *task) +{ + queue_task_delayed(this, task, 0); } /** @@ -1530,12 +1595,12 @@ static bool has_queued(private_task_manager_t *this, task_type_t type) { enumerator_t *enumerator; bool found = FALSE; - task_t *task; + queued_task_t *queued; enumerator = array_create_enumerator(this->queued_tasks); - while (enumerator->enumerate(enumerator, &task)) + while (enumerator->enumerate(enumerator, &queued)) { - if (task->get_type(task) == type) + if (queued->task->get_type(queued->task) == type) { found = TRUE; break; @@ -1614,7 +1679,7 @@ static void trigger_mbb_reauth(private_task_manager_t *this) child_cfg_t *cfg; ike_sa_t *new; host_t *host; - task_t *task; + queued_task_t *queued; new = charon->ike_sa_manager->checkout_new(charon->ike_sa_manager, this->ike_sa->get_version(this->ike_sa), TRUE); @@ -1645,13 +1710,14 @@ static void trigger_mbb_reauth(private_task_manager_t *this) enumerator->destroy(enumerator); enumerator = array_create_enumerator(this->queued_tasks); - while (enumerator->enumerate(enumerator, &task)) + while (enumerator->enumerate(enumerator, &queued)) { - if (task->get_type(task) == TASK_CHILD_CREATE) + if (queued->task->get_type(queued->task) == TASK_CHILD_CREATE) { - task->migrate(task, new); - new->queue_task(new, task); + queued->task->migrate(queued->task, new); + new->queue_task(new, queued->task); array_remove_at(this->queued_tasks, enumerator); + free(queued); } } enumerator->destroy(enumerator); @@ -1726,7 +1792,7 @@ METHOD(task_manager_t, queue_mobike, void, } METHOD(task_manager_t, queue_child, void, - private_task_manager_t *this, child_cfg_t *cfg, u_int32_t reqid, + private_task_manager_t *this, child_cfg_t *cfg, uint32_t reqid, traffic_selector_t *tsi, traffic_selector_t *tsr) { child_create_t *task; @@ -1740,13 +1806,13 @@ METHOD(task_manager_t, queue_child, void, } METHOD(task_manager_t, queue_child_rekey, void, - private_task_manager_t *this, protocol_id_t protocol, u_int32_t spi) + private_task_manager_t *this, protocol_id_t protocol, uint32_t spi) { queue_task(this, (task_t*)child_rekey_create(this->ike_sa, protocol, spi)); } METHOD(task_manager_t, queue_child_delete, void, - private_task_manager_t *this, protocol_id_t protocol, u_int32_t spi, + private_task_manager_t *this, protocol_id_t protocol, uint32_t spi, bool expired) { queue_task(this, (task_t*)child_delete_create(this->ike_sa, @@ -1776,34 +1842,62 @@ METHOD(task_manager_t, adopt_tasks, void, private_task_manager_t *this, task_manager_t *other_public) { private_task_manager_t *other = (private_task_manager_t*)other_public; - task_t *task; + queued_task_t *queued; + timeval_t now; + + time_monotonic(&now); /* move queued tasks from other to this */ - while (array_remove(other->queued_tasks, ARRAY_TAIL, &task)) + while (array_remove(other->queued_tasks, ARRAY_TAIL, &queued)) { - DBG2(DBG_IKE, "migrating %N task", task_type_names, task->get_type(task)); - task->migrate(task, this->ike_sa); - array_insert(this->queued_tasks, ARRAY_HEAD, task); + DBG2(DBG_IKE, "migrating %N task", task_type_names, + queued->task->get_type(queued->task)); + queued->task->migrate(queued->task, this->ike_sa); + /* don't delay tasks on the new IKE_SA */ + queued->time = now; + array_insert(this->queued_tasks, ARRAY_HEAD, queued); } } /** - * Migrates child-creating tasks from src to dst + * Migrates child-creating tasks from other to this */ static void migrate_child_tasks(private_task_manager_t *this, - array_t *src, array_t *dst) + private_task_manager_t *other, + task_queue_t queue) { enumerator_t *enumerator; + array_t *array; task_t *task; - enumerator = array_create_enumerator(src); + switch (queue) + { + case TASK_QUEUE_ACTIVE: + array = other->active_tasks; + break; + case TASK_QUEUE_QUEUED: + array = other->queued_tasks; + break; + default: + return; + } + + enumerator = array_create_enumerator(array); while (enumerator->enumerate(enumerator, &task)) { + queued_task_t *queued = NULL; + + if (queue == TASK_QUEUE_QUEUED) + { + queued = (queued_task_t*)task; + task = queued->task; + } if (task->get_type(task) == TASK_CHILD_CREATE) { - array_remove_at(src, enumerator); + array_remove_at(array, enumerator); task->migrate(task, this->ike_sa); - array_insert(dst, ARRAY_TAIL, task); + queue_task(this, task); + free(queued); } } enumerator->destroy(enumerator); @@ -1815,9 +1909,9 @@ METHOD(task_manager_t, adopt_child_tasks, void, 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); + migrate_child_tasks(this, other, TASK_QUEUE_ACTIVE); /* do the same for queued tasks */ - migrate_child_tasks(this, other->queued_tasks, this->queued_tasks); + migrate_child_tasks(this, other, TASK_QUEUE_QUEUED); } METHOD(task_manager_t, busy, bool, @@ -1827,10 +1921,12 @@ METHOD(task_manager_t, busy, bool, } METHOD(task_manager_t, reset, void, - private_task_manager_t *this, u_int32_t initiate, u_int32_t respond) + private_task_manager_t *this, uint32_t initiate, uint32_t respond) { enumerator_t *enumerator; + queued_task_t *queued; task_t *task; + timeval_t now; /* reset message counters and retransmit packets */ clear_packets(this->responding.packets); @@ -1849,11 +1945,13 @@ METHOD(task_manager_t, reset, void, } this->initiating.type = EXCHANGE_TYPE_UNDEFINED; + time_monotonic(&now); /* reset queued tasks */ enumerator = array_create_enumerator(this->queued_tasks); - while (enumerator->enumerate(enumerator, &task)) + while (enumerator->enumerate(enumerator, &queued)) { - task->migrate(task, this->ike_sa); + queued->time = now; + queued->task->migrate(queued->task, this->ike_sa); } enumerator->destroy(enumerator); @@ -1861,12 +1959,25 @@ METHOD(task_manager_t, reset, void, while (array_remove(this->active_tasks, ARRAY_TAIL, &task)) { task->migrate(task, this->ike_sa); - array_insert(this->queued_tasks, ARRAY_HEAD, task); + INIT(queued, + .task = task, + .time = now, + ); + array_insert(this->queued_tasks, ARRAY_HEAD, queued); } this->reset = TRUE; } +/** + * Filter queued tasks + */ +static bool filter_queued(void *unused, queued_task_t **queued, task_t **task) +{ + *task = (*queued)->task; + return TRUE; +} + METHOD(task_manager_t, create_task_enumerator, enumerator_t*, private_task_manager_t *this, task_queue_t queue) { @@ -1877,7 +1988,9 @@ METHOD(task_manager_t, create_task_enumerator, enumerator_t*, case TASK_QUEUE_PASSIVE: return array_create_enumerator(this->passive_tasks); case TASK_QUEUE_QUEUED: - return array_create_enumerator(this->queued_tasks); + return enumerator_create_filter( + array_create_enumerator(this->queued_tasks), + (void*)filter_queued, NULL, NULL); default: return enumerator_create_empty(); } @@ -1913,6 +2026,7 @@ task_manager_v2_t *task_manager_v2_create(ike_sa_t *ike_sa) .task_manager = { .process_message = _process_message, .queue_task = _queue_task, + .queue_task_delayed = _queue_task_delayed, .queue_ike = _queue_ike, .queue_ike_rekey = _queue_ike_rekey, .queue_ike_reauth = _queue_ike_reauth, diff --git a/src/libcharon/sa/ikev2/tasks/child_create.c b/src/libcharon/sa/ikev2/tasks/child_create.c index 3d4ded944..64a82850b 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.c +++ b/src/libcharon/sa/ikev2/tasks/child_create.c @@ -1,8 +1,8 @@ /* - * Copyright (C) 2008 Tobias Brunner + * Copyright (C) 2008-2016 Tobias Brunner * Copyright (C) 2005-2008 Martin Willi * Copyright (C) 2005 Jan Hutter - * Hochschule fuer Technik Rapperswil + * HSR Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -29,7 +29,7 @@ #include <encoding/payloads/delete_payload.h> #include <processing/jobs/delete_ike_sa_job.h> #include <processing/jobs/inactivity_job.h> - +#include <processing/jobs/initiate_tasks_job.h> typedef struct private_child_create_t private_child_create_t; @@ -151,27 +151,27 @@ struct private_child_create_t { /** * Own allocated SPI */ - u_int32_t my_spi; + uint32_t my_spi; /** * SPI received in proposal */ - u_int32_t other_spi; + uint32_t other_spi; /** * Own allocated Compression Parameter Index (CPI) */ - u_int16_t my_cpi; + uint16_t my_cpi; /** * Other Compression Parameter Index (CPI), received via IPCOMP_SUPPORTED */ - u_int16_t other_cpi; + uint16_t other_cpi; /** * reqid to use if we are rekeying */ - u_int32_t reqid; + uint32_t reqid; /** * Explicit inbound mark value @@ -205,6 +205,25 @@ struct private_child_create_t { }; /** + * Schedule a retry if creating the CHILD_SA temporary failed + */ +static void schedule_delayed_retry(private_child_create_t *this) +{ + child_create_t *task; + uint32_t retry; + + retry = RETRY_INTERVAL - (random() % RETRY_JITTER); + + task = child_create_create(this->ike_sa, + this->config->get_ref(this->config), FALSE, + this->packet_tsi, this->packet_tsr); + task->use_reqid(task, this->reqid); + DBG1(DBG_IKE, "creating CHILD_SA failed, trying again in %d seconds", + retry); + this->ike_sa->queue_task_delayed(this->ike_sa, (task_t*)task, retry); +} + +/** * get the nonce from a message */ static status_t get_nonce(message_t *message, chunk_t *nonce) @@ -306,7 +325,7 @@ static bool allocate_spi(private_child_create_t *this) */ static void schedule_inactivity_timeout(private_child_create_t *this) { - u_int32_t timeout, id; + uint32_t timeout, id; bool close_ike; timeout = this->config->get_inactivity(this->config); @@ -386,7 +405,7 @@ static linked_list_t* get_transport_nat_ts(private_child_create_t *this, linked_list_t *out; traffic_selector_t *ts; host_t *ike, *first = NULL; - u_int8_t mask; + uint8_t mask; if (local) { @@ -464,7 +483,7 @@ static status_t select_and_install(private_child_create_t *this, chunk_t integ_i = chunk_empty, integ_r = chunk_empty; linked_list_t *my_ts, *other_ts; host_t *me, *other; - bool private; + bool private, prefer_configured; if (this->proposals == NULL) { @@ -481,8 +500,10 @@ static status_t select_and_install(private_child_create_t *this, other = this->ike_sa->get_other_host(this->ike_sa); private = this->ike_sa->supports_extension(this->ike_sa, EXT_STRONGSWAN); + prefer_configured = lib->settings->get_bool(lib->settings, + "%s.prefer_configured_proposals", TRUE, lib->ns); this->proposal = this->config->select_proposal(this->config, - this->proposals, no_dh, private); + this->proposals, no_dh, private, prefer_configured); if (this->proposal == NULL) { DBG1(DBG_IKE, "no acceptable proposal found"); @@ -501,7 +522,7 @@ static status_t select_and_install(private_child_create_t *this, if (!this->proposal->has_dh_group(this->proposal, this->dh_group)) { - u_int16_t group; + uint16_t group; if (this->proposal->get_algorithm(this->proposal, DIFFIE_HELLMAN_GROUP, &group, NULL)) @@ -798,7 +819,7 @@ static bool build_payloads(private_child_create_t *this, message_t *message) * Adds an IPCOMP_SUPPORTED notify to the message, allocating a CPI */ static void add_ipcomp_notify(private_child_create_t *this, - message_t *message, u_int8_t ipcomp) + message_t *message, uint8_t ipcomp) { this->my_cpi = this->child_sa->alloc_cpi(this->child_sa); if (this->my_cpi) @@ -838,11 +859,11 @@ static void handle_notify(private_child_create_t *this, notify_payload_t *notify case IPCOMP_SUPPORTED: { ipcomp_transform_t ipcomp; - u_int16_t cpi; + uint16_t cpi; chunk_t data; data = notify->get_notification_data(notify); - cpi = *(u_int16_t*)data.ptr; + cpi = *(uint16_t*)data.ptr; ipcomp = (ipcomp_transform_t)(*(data.ptr + 2)); switch (ipcomp) { @@ -1232,13 +1253,13 @@ METHOD(task_t, build_r, status_t, if (this->ike_sa->get_state(this->ike_sa) == IKE_REKEYING) { DBG1(DBG_IKE, "unable to create CHILD_SA while rekeying IKE_SA"); - message->add_notify(message, TRUE, NO_ADDITIONAL_SAS, chunk_empty); + message->add_notify(message, TRUE, TEMPORARY_FAILURE, chunk_empty); return SUCCESS; } if (this->ike_sa->get_state(this->ike_sa) == IKE_DELETING) { DBG1(DBG_IKE, "unable to create CHILD_SA while deleting IKE_SA"); - message->add_notify(message, TRUE, NO_ADDITIONAL_SAS, chunk_empty); + message->add_notify(message, TRUE, TEMPORARY_FAILURE, chunk_empty); return SUCCESS; } @@ -1310,7 +1331,7 @@ METHOD(task_t, build_r, status_t, return SUCCESS; case INVALID_ARG: { - u_int16_t group = htons(this->dh_group); + uint16_t group = htons(this->dh_group); message->add_notify(message, FALSE, INVALID_KE_PAYLOAD, chunk_from_thing(group)); handle_child_sa_failure(this, message); @@ -1441,10 +1462,21 @@ METHOD(task_t, process_i, status_t, /* an error in CHILD_SA creation is not critical */ return SUCCESS; } + case TEMPORARY_FAILURE: + { + DBG1(DBG_IKE, "received %N notify, will retry later", + notify_type_names, type); + enumerator->destroy(enumerator); + if (!this->rekey) + { /* the rekey task will retry itself if necessary */ + schedule_delayed_retry(this); + } + return SUCCESS; + } case INVALID_KE_PAYLOAD: { chunk_t data; - u_int16_t group = MODP_NONE; + uint16_t group = MODP_NONE; data = notify->get_notification_data(notify); if (data.len == sizeof(group)) @@ -1529,7 +1561,7 @@ METHOD(task_t, process_i, status_t, } METHOD(child_create_t, use_reqid, void, - private_child_create_t *this, u_int32_t reqid) + private_child_create_t *this, uint32_t reqid) { this->reqid = reqid; } diff --git a/src/libcharon/sa/ikev2/tasks/child_create.h b/src/libcharon/sa/ikev2/tasks/child_create.h index 46d9403ee..f48d7b0a9 100644 --- a/src/libcharon/sa/ikev2/tasks/child_create.h +++ b/src/libcharon/sa/ikev2/tasks/child_create.h @@ -49,7 +49,7 @@ struct child_create_t { * * @param reqid reqid to use */ - void (*use_reqid) (child_create_t *this, u_int32_t reqid); + void (*use_reqid) (child_create_t *this, uint32_t reqid); /** * Use specific mark values to override configuration. diff --git a/src/libcharon/sa/ikev2/tasks/child_delete.c b/src/libcharon/sa/ikev2/tasks/child_delete.c index 877ae0531..6fa8836ac 100644 --- a/src/libcharon/sa/ikev2/tasks/child_delete.c +++ b/src/libcharon/sa/ikev2/tasks/child_delete.c @@ -1,6 +1,7 @@ /* + * Copyright (C) 2009-2016 Tobias Brunner * Copyright (C) 2006-2007 Martin Willi - * Hochschule fuer Technik Rapperswil + * HSR Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -18,7 +19,7 @@ #include <daemon.h> #include <encoding/payloads/delete_payload.h> #include <sa/ikev2/tasks/child_create.h> - +#include <sa/ikev2/tasks/child_rekey.h> typedef struct private_child_delete_t private_child_delete_t; @@ -50,7 +51,7 @@ struct private_child_delete_t { /** * Inbound SPI of CHILD_SA to delete */ - u_int32_t spi; + uint32_t spi; /** * whether to enforce delete action policy @@ -86,7 +87,7 @@ static void build_payloads(private_child_delete_t *this, message_t *message) while (enumerator->enumerate(enumerator, (void**)&child_sa)) { protocol_id_t protocol = child_sa->get_protocol(child_sa); - u_int32_t spi = child_sa->get_spi(child_sa, TRUE); + uint32_t spi = child_sa->get_spi(child_sa, TRUE); switch (protocol) { @@ -119,6 +120,33 @@ static void build_payloads(private_child_delete_t *this, message_t *message) } /** + * Check if the given CHILD_SA is the redundant SA created in a rekey collision. + */ +static bool is_redundant(private_child_delete_t *this, child_sa_t *child) +{ + enumerator_t *tasks; + task_t *task; + + tasks = this->ike_sa->create_task_enumerator(this->ike_sa, + TASK_QUEUE_ACTIVE); + while (tasks->enumerate(tasks, &task)) + { + if (task->get_type(task) == TASK_CHILD_REKEY) + { + child_rekey_t *rekey = (child_rekey_t*)task; + + if (rekey->is_redundant(rekey, child)) + { + tasks->destroy(tasks); + return TRUE; + } + } + } + tasks->destroy(tasks); + return FALSE; +} + +/** * read in payloads and find the children to delete */ static void process_payloads(private_child_delete_t *this, message_t *message) @@ -126,7 +154,7 @@ static void process_payloads(private_child_delete_t *this, message_t *message) enumerator_t *payloads, *spis; payload_t *payload; delete_payload_t *delete_payload; - u_int32_t spi; + uint32_t spi; protocol_id_t protocol; child_sa_t *child_sa; @@ -157,24 +185,31 @@ static void process_payloads(private_child_delete_t *this, message_t *message) switch (child_sa->get_state(child_sa)) { - case CHILD_REKEYING: + case CHILD_REKEYED: this->rekeyed = TRUE; - /* we reply as usual, rekeying will fail */ break; case CHILD_DELETING: /* we don't send back a delete if we initiated ourself */ if (!this->initiator) { - this->ike_sa->destroy_child_sa(this->ike_sa, - protocol, spi); continue; } /* fall through */ + case CHILD_REKEYING: + /* we reply as usual, rekeying will fail */ case CHILD_INSTALLED: if (!this->initiator) - { /* reestablish installed children if required */ - this->check_delete_action = TRUE; + { + if (is_redundant(this, child_sa)) + { + this->rekeyed = TRUE; + } + else + { + this->check_delete_action = TRUE; + } } + break; default: break; } @@ -199,14 +234,14 @@ static status_t destroy_and_reestablish(private_child_delete_t *this) child_sa_t *child_sa; child_cfg_t *child_cfg; protocol_id_t protocol; - u_int32_t spi, reqid; + uint32_t spi, reqid; action_t action; status_t status = SUCCESS; enumerator = this->child_sas->create_enumerator(this->child_sas); while (enumerator->enumerate(enumerator, (void**)&child_sa)) { - /* signal child down event if we are not rekeying */ + /* signal child down event if we weren't rekeying */ if (!this->rekeyed) { charon->bus->child_updown(charon->bus, child_sa, FALSE); @@ -254,7 +289,7 @@ static void log_children(private_child_delete_t *this) linked_list_t *my_ts, *other_ts; enumerator_t *enumerator; child_sa_t *child_sa; - u_int64_t bytes_in, bytes_out; + uint64_t bytes_in, bytes_out; enumerator = this->child_sas->create_enumerator(this->child_sas); while (enumerator->enumerate(enumerator, (void**)&child_sa)) @@ -308,7 +343,7 @@ METHOD(task_t, build_i, status_t, this->spi = child_sa->get_spi(child_sa, TRUE); } this->child_sas->insert_last(this->child_sas, child_sa); - if (child_sa->get_state(child_sa) == CHILD_REKEYING) + if (child_sa->get_state(child_sa) == CHILD_REKEYED) { this->rekeyed = TRUE; } @@ -347,11 +382,7 @@ METHOD(task_t, process_r, status_t, METHOD(task_t, build_r, status_t, private_child_delete_t *this, message_t *message) { - /* if we are rekeying, we send an empty informational */ - if (this->ike_sa->get_state(this->ike_sa) != IKE_REKEYING) - { - build_payloads(this, message); - } + build_payloads(this, message); DBG1(DBG_IKE, "CHILD_SA closed"); return destroy_and_reestablish(this); } @@ -391,7 +422,7 @@ METHOD(task_t, destroy, void, * Described in header. */ child_delete_t *child_delete_create(ike_sa_t *ike_sa, protocol_id_t protocol, - u_int32_t spi, bool expired) + uint32_t spi, bool expired) { private_child_delete_t *this; diff --git a/src/libcharon/sa/ikev2/tasks/child_delete.h b/src/libcharon/sa/ikev2/tasks/child_delete.h index 1ada0699e..1e9b2d2f7 100644 --- a/src/libcharon/sa/ikev2/tasks/child_delete.h +++ b/src/libcharon/sa/ikev2/tasks/child_delete.h @@ -56,6 +56,6 @@ struct child_delete_t { * @return child_delete task to handle by the task_manager */ child_delete_t *child_delete_create(ike_sa_t *ike_sa, protocol_id_t protocol, - u_int32_t spi, bool expired); + uint32_t spi, bool expired); #endif /** CHILD_DELETE_H_ @}*/ diff --git a/src/libcharon/sa/ikev2/tasks/child_rekey.c b/src/libcharon/sa/ikev2/tasks/child_rekey.c index 6f0c2b2c7..c04ec141f 100644 --- a/src/libcharon/sa/ikev2/tasks/child_rekey.c +++ b/src/libcharon/sa/ikev2/tasks/child_rekey.c @@ -1,7 +1,8 @@ /* + * Copyright (C) 2009-2016 Tobias Brunner * Copyright (C) 2005-2007 Martin Willi * Copyright (C) 2005 Jan Hutter - * Hochschule fuer Technik Rapperswil + * HSR Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -54,7 +55,7 @@ struct private_child_rekey_t { /** * Inbound SPI of CHILD_SA to rekey */ - u_int32_t spi; + uint32_t spi; /** * the CHILD_CREATE task which is reused to simplify rekeying @@ -91,7 +92,7 @@ struct private_child_rekey_t { */ static void schedule_delayed_rekey(private_child_rekey_t *this) { - u_int32_t retry; + uint32_t retry; job_t *job; retry = RETRY_INTERVAL - (random() % RETRY_JITTER); @@ -130,7 +131,7 @@ static void find_child(private_child_rekey_t *this, message_t *message) { notify_payload_t *notify; protocol_id_t protocol; - u_int32_t spi; + uint32_t spi; notify = message->get_notify(message, REKEY_SA); if (notify) @@ -150,7 +151,7 @@ METHOD(task_t, build_i, status_t, private_child_rekey_t *this, message_t *message) { notify_payload_t *notify; - u_int32_t reqid; + uint32_t reqid; child_cfg_t *config; this->child_sa = this->ike_sa->get_child_sa(this->ike_sa, this->protocol, @@ -159,14 +160,21 @@ METHOD(task_t, build_i, status_t, { /* check if it is an outbound CHILD_SA */ this->child_sa = this->ike_sa->get_child_sa(this->ike_sa, this->protocol, this->spi, FALSE); - if (!this->child_sa) - { /* CHILD_SA is gone, unable to rekey. As an empty CREATE_CHILD_SA - * exchange is invalid, we fall back to an INFORMATIONAL exchange.*/ - message->set_exchange_type(message, INFORMATIONAL); - return SUCCESS; + if (this->child_sa) + { + /* we work only with the inbound SPI */ + this->spi = this->child_sa->get_spi(this->child_sa, TRUE); } - /* we work only with the inbound SPI */ - this->spi = this->child_sa->get_spi(this->child_sa, TRUE); + } + if (!this->child_sa || + (!this->child_create && + this->child_sa->get_state(this->child_sa) != CHILD_INSTALLED) || + (this->child_create && + this->child_sa->get_state(this->child_sa) != CHILD_REKEYING)) + { + /* CHILD_SA is gone or in the wrong state, unable to rekey */ + message->set_exchange_type(message, EXCHANGE_TYPE_UNDEFINED); + return SUCCESS; } config = this->child_sa->get_config(this->child_sa); @@ -217,13 +225,19 @@ METHOD(task_t, build_r, status_t, private_child_rekey_t *this, message_t *message) { child_cfg_t *config; - u_int32_t reqid; + uint32_t reqid; + child_sa_state_t state; - if (this->child_sa == NULL || - this->child_sa->get_state(this->child_sa) == CHILD_DELETING) + if (!this->child_sa) { DBG1(DBG_IKE, "unable to rekey, CHILD_SA not found"); - message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty); + message->add_notify(message, TRUE, CHILD_SA_NOT_FOUND, chunk_empty); + return SUCCESS; + } + if (this->child_sa->get_state(this->child_sa) == CHILD_DELETING) + { + DBG1(DBG_IKE, "unable to rekey, we are deleting the CHILD_SA"); + message->add_notify(message, TRUE, TEMPORARY_FAILURE, chunk_empty); return SUCCESS; } @@ -237,14 +251,16 @@ METHOD(task_t, build_r, status_t, this->child_create->set_config(this->child_create, config->get_ref(config)); this->child_create->task.build(&this->child_create->task, message); + state = this->child_sa->get_state(this->child_sa); + this->child_sa->set_state(this->child_sa, CHILD_REKEYING); + if (message->get_payload(message, PLV2_SECURITY_ASSOCIATION) == NULL) - { - /* rekeying failed, reuse old child */ - this->child_sa->set_state(this->child_sa, CHILD_INSTALLED); + { /* rekeying failed, reuse old child */ + this->child_sa->set_state(this->child_sa, state); return SUCCESS; } - this->child_sa->set_state(this->child_sa, CHILD_REKEYING); + this->child_sa->set_state(this->child_sa, CHILD_REKEYED); /* invoke rekey hook */ charon->bus->child_rekey(charon->bus, this->child_sa, @@ -284,9 +300,9 @@ static child_sa_t *handle_collision(private_child_rekey_t *this) if (child_sa) { child_sa->set_close_action(child_sa, ACTION_NONE); - if (child_sa->get_state(child_sa) != CHILD_REKEYING) + if (child_sa->get_state(child_sa) != CHILD_REKEYED) { - child_sa->set_state(child_sa, CHILD_REKEYING); + child_sa->set_state(child_sa, CHILD_REKEYED); } } } @@ -324,7 +340,7 @@ METHOD(task_t, process_i, status_t, private_child_rekey_t *this, message_t *message) { protocol_id_t protocol; - u_int32_t spi; + uint32_t spi; child_sa_t *to_delete; if (message->get_notify(message, NO_ADDITIONAL_SAS)) @@ -337,6 +353,34 @@ METHOD(task_t, process_i, status_t, this->ike_sa->get_id(this->ike_sa), TRUE)); return SUCCESS; } + if (message->get_notify(message, CHILD_SA_NOT_FOUND)) + { + child_cfg_t *child_cfg; + uint32_t reqid; + + if (this->collision && + this->collision->get_type(this->collision) == TASK_CHILD_DELETE) + { /* ignore this error if we already deleted the CHILD_SA on the + * peer's behalf (could happen if the other peer does not detect + * the collision and did not respond with TEMPORARY_FAILURE) */ + return SUCCESS; + } + DBG1(DBG_IKE, "peer didn't find the CHILD_SA we tried to rekey"); + /* FIXME: according to RFC 7296 we should only create a new CHILD_SA if + * it does not exist yet, we currently have no good way of checking for + * that (we could go by name, but that might be tricky e.g. due to + * narrowing) */ + spi = this->child_sa->get_spi(this->child_sa, TRUE); + reqid = this->child_sa->get_reqid(this->child_sa); + protocol = this->child_sa->get_protocol(this->child_sa); + child_cfg = this->child_sa->get_config(this->child_sa); + child_cfg->get_ref(child_cfg); + charon->bus->child_updown(charon->bus, this->child_sa, FALSE); + this->ike_sa->destroy_child_sa(this->ike_sa, protocol, spi); + return this->ike_sa->initiate(this->ike_sa, + child_cfg->get_ref(child_cfg), reqid, + NULL, NULL); + } if (this->child_create->task.process(&this->child_create->task, message) == NEED_MORE) @@ -346,10 +390,10 @@ METHOD(task_t, process_i, status_t, } if (message->get_payload(message, PLV2_SECURITY_ASSOCIATION) == NULL) { - /* establishing new child failed, reuse old. but not when we - * received a delete in the meantime */ - if (!(this->collision && - this->collision->get_type(this->collision) == TASK_CHILD_DELETE)) + /* establishing new child failed, reuse old and try again. but not when + * we received a delete in the meantime */ + if (!this->collision || + this->collision->get_type(this->collision) != TASK_CHILD_DELETE) { schedule_delayed_rekey(this); } @@ -377,9 +421,9 @@ METHOD(task_t, process_i, status_t, return SUCCESS; } /* disable updown event for redundant CHILD_SA */ - if (to_delete->get_state(to_delete) != CHILD_REKEYING) + if (to_delete->get_state(to_delete) != CHILD_REKEYED) { - to_delete->set_state(to_delete, CHILD_REKEYING); + to_delete->set_state(to_delete, CHILD_REKEYED); } spi = to_delete->get_spi(to_delete, TRUE); protocol = to_delete->get_protocol(to_delete); @@ -398,6 +442,18 @@ METHOD(task_t, get_type, task_type_t, return TASK_CHILD_REKEY; } +METHOD(child_rekey_t, is_redundant, bool, + private_child_rekey_t *this, child_sa_t *child) +{ + if (this->collision && + this->collision->get_type(this->collision) == TASK_CHILD_REKEY) + { + private_child_rekey_t *rekey = (private_child_rekey_t*)this->collision; + return child == rekey->child_create->get_child(rekey->child_create); + } + return FALSE; +} + METHOD(child_rekey_t, collide, void, private_child_rekey_t *this, task_t *other) { @@ -406,9 +462,18 @@ METHOD(child_rekey_t, collide, void, if (other->get_type(other) == TASK_CHILD_REKEY) { private_child_rekey_t *rekey = (private_child_rekey_t*)other; + child_sa_t *other_child; + if (rekey->child_sa != this->child_sa) + { /* not the same child => no collision */ + other->destroy(other); + return; + } + /* ignore passive tasks that did not successfully create a CHILD_SA */ + other_child = rekey->child_create->get_child(rekey->child_create); + if (!other_child || + other_child->get_state(other_child) != CHILD_INSTALLED) { - /* not the same child => no collision */ other->destroy(other); return; } @@ -416,19 +481,11 @@ METHOD(child_rekey_t, collide, void, else if (other->get_type(other) == TASK_CHILD_DELETE) { child_delete_t *del = (child_delete_t*)other; - if (this->collision && - this->collision->get_type(this->collision) == TASK_CHILD_REKEY) + if (is_redundant(this, del->get_child(del))) { - private_child_rekey_t *rekey; - - rekey = (private_child_rekey_t*)this->collision; - if (del->get_child(del) == rekey->child_create->get_child(rekey->child_create)) - { - /* peer deletes redundant child created in collision */ - this->other_child_destroyed = TRUE; - other->destroy(other); - return; - } + this->other_child_destroyed = TRUE; + other->destroy(other); + return; } if (del->get_child(del) != this->child_sa) { @@ -439,7 +496,7 @@ METHOD(child_rekey_t, collide, void, } else { - /* any other task is not critical for collisisions, ignore */ + /* any other task is not critical for collisions, ignore */ other->destroy(other); return; } @@ -485,7 +542,7 @@ METHOD(task_t, destroy, void, * Described in header. */ child_rekey_t *child_rekey_create(ike_sa_t *ike_sa, protocol_id_t protocol, - u_int32_t spi) + uint32_t spi) { private_child_rekey_t *this; @@ -496,6 +553,7 @@ child_rekey_t *child_rekey_create(ike_sa_t *ike_sa, protocol_id_t protocol, .migrate = _migrate, .destroy = _destroy, }, + .is_redundant = _is_redundant, .collide = _collide, }, .ike_sa = ike_sa, diff --git a/src/libcharon/sa/ikev2/tasks/child_rekey.h b/src/libcharon/sa/ikev2/tasks/child_rekey.h index 23384653d..0ad1a062d 100644 --- a/src/libcharon/sa/ikev2/tasks/child_rekey.h +++ b/src/libcharon/sa/ikev2/tasks/child_rekey.h @@ -1,6 +1,7 @@ /* + * Copyright (C) 2016 Tobias Brunner * Copyright (C) 2007 Martin Willi - * Hochschule fuer Technik Rapperswil + * HSR Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -39,13 +40,25 @@ struct child_rekey_t { task_t task; /** - * Register a rekeying task which collides with this one + * Check if the given SA is the redundant CHILD_SA created during a rekey + * collision. + * + * This is called if the other peer deletes the redundant SA before we were + * able to handle the CREATE_CHILD_SA response. + * + * @param child CHILD_SA to check + * @return TRUE if the SA is the redundant CHILD_SA + */ + bool (*is_redundant)(child_rekey_t *this, child_sa_t *child); + + /** + * Register a rekeying/delete task which collides with this one * * If two peers initiate rekeying at the same time, the collision must * be handled gracefully. The task manager is aware of what exchanges - * are going on and notifies the outgoing task by passing the incoming. + * are going on and notifies the active task by passing the passive. * - * @param other incoming task + * @param other passive task (adopted) */ void (*collide)(child_rekey_t* this, task_t *other); }; @@ -59,6 +72,6 @@ struct child_rekey_t { * @return child_rekey task to handle by the task_manager */ child_rekey_t *child_rekey_create(ike_sa_t *ike_sa, protocol_id_t protocol, - u_int32_t spi); + uint32_t spi); #endif /** CHILD_REKEY_H_ @}*/ diff --git a/src/libcharon/sa/ikev2/tasks/ike_auth.c b/src/libcharon/sa/ikev2/tasks/ike_auth.c index 79a436fbf..036910d0e 100644 --- a/src/libcharon/sa/ikev2/tasks/ike_auth.c +++ b/src/libcharon/sa/ikev2/tasks/ike_auth.c @@ -186,7 +186,7 @@ static status_t collect_other_init_data(private_ike_auth_t *this, */ static void get_reserved_id_bytes(private_ike_auth_t *this, id_payload_t *id) { - u_int8_t *byte; + uint8_t *byte; int i; for (i = 0; i < countof(this->reserved); i++) @@ -564,6 +564,10 @@ METHOD(task_t, process_r, status_t, this->ike_sa->enable_extension(this->ike_sa, EXT_EAP_ONLY_AUTHENTICATION); } + if (message->get_notify(message, INITIAL_CONTACT)) + { + this->initial_contact = TRUE; + } } if (this->other_auth == NULL) @@ -652,14 +656,6 @@ METHOD(task_t, process_r, status_t, return NEED_MORE; } - /* If authenticated (with non-EAP) and received INITIAL_CONTACT, - * delete any existing IKE_SAs with that peer. */ - if (message->get_message_id(message) == 1 && - message->get_notify(message, INITIAL_CONTACT)) - { - this->initial_contact = TRUE; - } - /* another auth round done, invoke authorize hook */ if (!charon->bus->authorize(charon->bus, FALSE)) { @@ -749,13 +745,6 @@ METHOD(task_t, build_r, status_t, get_reserved_id_bytes(this, id_payload); message->add_payload(message, (payload_t*)id_payload); - if (this->initial_contact) - { - charon->ike_sa_manager->check_uniqueness(charon->ike_sa_manager, - this->ike_sa, TRUE); - this->initial_contact = FALSE; - } - if ((uintptr_t)cfg->get(cfg, AUTH_RULE_AUTH_CLASS) == AUTH_CLASS_EAP) { /* EAP-only authentication */ if (!this->ike_sa->supports_extension(this->ike_sa, @@ -830,7 +819,7 @@ METHOD(task_t, build_r, status_t, } if (charon->ike_sa_manager->check_uniqueness(charon->ike_sa_manager, - this->ike_sa, FALSE)) + this->ike_sa, this->initial_contact)) { DBG1(DBG_IKE, "cancelling IKE_SA setup due to uniqueness policy"); charon->bus->alert(charon->bus, ALERT_UNIQUE_KEEP); diff --git a/src/libcharon/sa/ikev2/tasks/ike_auth_lifetime.c b/src/libcharon/sa/ikev2/tasks/ike_auth_lifetime.c index a7d162e68..47b0a3ed1 100644 --- a/src/libcharon/sa/ikev2/tasks/ike_auth_lifetime.c +++ b/src/libcharon/sa/ikev2/tasks/ike_auth_lifetime.c @@ -45,14 +45,14 @@ struct private_ike_auth_lifetime_t { static void add_auth_lifetime(private_ike_auth_lifetime_t *this, message_t *message) { chunk_t chunk; - u_int32_t lifetime; + uint32_t lifetime; lifetime = this->ike_sa->get_statistic(this->ike_sa, STAT_REAUTH); if (lifetime) { lifetime -= time_monotonic(NULL); chunk = chunk_from_thing(lifetime); - *(u_int32_t*)chunk.ptr = htonl(lifetime); + *(uint32_t*)chunk.ptr = htonl(lifetime); message->add_notify(message, FALSE, AUTH_LIFETIME, chunk); } } @@ -64,13 +64,13 @@ static void process_payloads(private_ike_auth_lifetime_t *this, message_t *messa { notify_payload_t *notify; chunk_t data; - u_int32_t lifetime; + uint32_t lifetime; notify = message->get_notify(message, AUTH_LIFETIME); if (notify) { data = notify->get_notification_data(notify); - lifetime = ntohl(*(u_int32_t*)data.ptr); + lifetime = ntohl(*(uint32_t*)data.ptr); this->ike_sa->set_auth_lifetime(this->ike_sa, lifetime); } } diff --git a/src/libcharon/sa/ikev2/tasks/ike_delete.c b/src/libcharon/sa/ikev2/tasks/ike_delete.c index e972dba07..fd36b144a 100644 --- a/src/libcharon/sa/ikev2/tasks/ike_delete.c +++ b/src/libcharon/sa/ikev2/tasks/ike_delete.c @@ -1,6 +1,7 @@ /* + * Copyright (C) 2016 Tobias Brunner * Copyright (C) 2006-2007 Martin Willi - * Hochschule fuer Technik Rapperswil + * HSR Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -17,7 +18,7 @@ #include <daemon.h> #include <encoding/payloads/delete_payload.h> - +#include <sa/ikev2/tasks/ike_rekey.h> typedef struct private_ike_delete_t private_ike_delete_t; @@ -45,11 +46,6 @@ struct private_ike_delete_t { * are we deleting a rekeyed SA? */ bool rekeyed; - - /** - * are we responding to a delete, but have initated our own? - */ - bool simultaneous; }; METHOD(task_t, build_i, status_t, @@ -68,7 +64,8 @@ METHOD(task_t, build_i, status_t, delete_payload = delete_payload_create(PLV2_DELETE, PROTO_IKE); message->add_payload(message, (payload_t*)delete_payload); - if (this->ike_sa->get_state(this->ike_sa) == IKE_REKEYING) + if (this->ike_sa->get_state(this->ike_sa) == IKE_REKEYING || + this->ike_sa->get_state(this->ike_sa) == IKE_REKEYED) { this->rekeyed = TRUE; } @@ -93,6 +90,33 @@ METHOD(task_t, process_i, status_t, return DESTROY_ME; } +/** + * Check if this delete happened after a rekey collsion + */ +static bool after_rekey_collision(private_ike_delete_t *this) +{ + enumerator_t *tasks; + task_t *task; + + tasks = this->ike_sa->create_task_enumerator(this->ike_sa, + TASK_QUEUE_ACTIVE); + while (tasks->enumerate(tasks, &task)) + { + if (task->get_type(task) == TASK_IKE_REKEY) + { + ike_rekey_t *rekey = (ike_rekey_t*)task; + + if (rekey->did_collide(rekey)) + { + tasks->destroy(tasks); + return TRUE; + } + } + } + tasks->destroy(tasks); + return FALSE; +} + METHOD(task_t, process_r, status_t, private_ike_delete_t *this, message_t *message) { @@ -119,16 +143,24 @@ METHOD(task_t, process_r, status_t, switch (this->ike_sa->get_state(this->ike_sa)) { + case IKE_REKEYING: + /* if the peer concurrently deleted the IKE_SA we treat this as + * regular delete. however, in case the peer did not detect a rekey + * collision it will delete the replaced IKE_SA if we are still in + * state IKE_REKEYING */ + if (after_rekey_collision(this)) + { + this->rekeyed = TRUE; + break; + } + /* fall-through */ case IKE_ESTABLISHED: this->ike_sa->set_state(this->ike_sa, IKE_DELETING); this->ike_sa->reestablish(this->ike_sa); return NEED_MORE; - case IKE_REKEYING: + case IKE_REKEYED: this->rekeyed = TRUE; break; - case IKE_DELETING: - this->simultaneous = TRUE; - break; default: break; } @@ -141,11 +173,6 @@ METHOD(task_t, build_r, status_t, { DBG0(DBG_IKE, "IKE_SA deleted"); - if (this->simultaneous) - { - /* wait for peer's response for our delete request */ - return SUCCESS; - } if (!this->rekeyed) { /* invoke ike_down() hook if SA has not been rekeyed */ charon->bus->ike_updown(charon->bus, this->ike_sa, FALSE); @@ -164,7 +191,6 @@ METHOD(task_t, migrate, void, private_ike_delete_t *this, ike_sa_t *ike_sa) { this->ike_sa = ike_sa; - this->simultaneous = FALSE; } METHOD(task_t, destroy, void, diff --git a/src/libcharon/sa/ikev2/tasks/ike_init.c b/src/libcharon/sa/ikev2/tasks/ike_init.c index 78579be95..801b6d8f3 100644 --- a/src/libcharon/sa/ikev2/tasks/ike_init.c +++ b/src/libcharon/sa/ikev2/tasks/ike_init.c @@ -221,7 +221,7 @@ static void handle_supported_hash_algorithms(private_ike_init_t *this, notify_payload_t *notify) { bio_reader_t *reader; - u_int16_t algo; + uint16_t algo; bool added = FALSE; reader = bio_reader_create(notify->get_notification_data(notify)); @@ -373,13 +373,15 @@ static void process_payloads(private_ike_init_t *this, message_t *message) { sa_payload_t *sa_payload = (sa_payload_t*)payload; linked_list_t *proposal_list; - bool private; + bool private, prefer_configured; proposal_list = sa_payload->get_proposals(sa_payload); private = this->ike_sa->supports_extension(this->ike_sa, EXT_STRONGSWAN); + prefer_configured = lib->settings->get_bool(lib->settings, + "%s.prefer_configured_proposals", TRUE, lib->ns); this->proposal = this->config->select_proposal(this->config, - proposal_list, private); + proposal_list, private, prefer_configured); if (!this->proposal) { charon->bus->alert(charon->bus, ALERT_PROPOSAL_MISMATCH_IKE, @@ -633,7 +635,7 @@ METHOD(task_t, build_r, status_t, if (this->dh == NULL || !this->proposal->has_dh_group(this->proposal, this->dh_group)) { - u_int16_t group; + uint16_t group; if (this->proposal->get_algorithm(this->proposal, DIFFIE_HELLMAN_GROUP, &group, NULL)) @@ -765,7 +767,7 @@ METHOD(task_t, process_i, status_t, bad_group = this->dh_group; data = notify->get_notification_data(notify); - this->dh_group = ntohs(*((u_int16_t*)data.ptr)); + this->dh_group = ntohs(*((uint16_t*)data.ptr)); DBG1(DBG_IKE, "peer didn't accept DH group %N, " "it requested %N", diffie_hellman_group_names, bad_group, diffie_hellman_group_names, this->dh_group); diff --git a/src/libcharon/sa/ikev2/tasks/ike_me.c b/src/libcharon/sa/ikev2/tasks/ike_me.c index 10d412ffd..f077ccfb5 100644 --- a/src/libcharon/sa/ikev2/tasks/ike_me.c +++ b/src/libcharon/sa/ikev2/tasks/ike_me.c @@ -128,7 +128,7 @@ static void gather_and_add_endpoints(private_ike_me_t *this, message_t *message) { enumerator_t *enumerator; host_t *addr, *host; - u_int16_t port; + uint16_t port; /* get the port that is used to communicate with the ms */ host = this->ike_sa->get_my_host(this->ike_sa); diff --git a/src/libcharon/sa/ikev2/tasks/ike_mobike.c b/src/libcharon/sa/ikev2/tasks/ike_mobike.c index 3f7bb175f..dc0f24fb8 100644 --- a/src/libcharon/sa/ikev2/tasks/ike_mobike.c +++ b/src/libcharon/sa/ikev2/tasks/ike_mobike.c @@ -299,7 +299,7 @@ static void update_children(private_ike_mobike_t *this) /** * Apply the port of the old host, if its ip equals the new, use port otherwise. */ -static void apply_port(host_t *host, host_t *old, u_int16_t port, bool local) +static void apply_port(host_t *host, host_t *old, uint16_t port, bool local) { if (host->ip_equals(host, old)) { diff --git a/src/libcharon/sa/ikev2/tasks/ike_natd.c b/src/libcharon/sa/ikev2/tasks/ike_natd.c index 4bf5264dd..f3f32d7af 100644 --- a/src/libcharon/sa/ikev2/tasks/ike_natd.c +++ b/src/libcharon/sa/ikev2/tasks/ike_natd.c @@ -99,8 +99,8 @@ static chunk_t generate_natd_hash(private_ike_natd_t *this, { chunk_t natd_chunk, spi_i_chunk, spi_r_chunk, addr_chunk, port_chunk; chunk_t natd_hash; - u_int64_t spi_i, spi_r; - u_int16_t port; + uint64_t spi_i, spi_r; + uint16_t port; /* prepare all required chunks */ spi_i = ike_sa_id->get_initiator_spi(ike_sa_id); @@ -142,7 +142,7 @@ static notify_payload_t *build_natd_payload(private_ike_natd_t *this, config = this->ike_sa->get_ike_cfg(this->ike_sa); if (force_encap(config) && type == NAT_DETECTION_SOURCE_IP) { - u_int32_t addr; + uint32_t addr; /* chunk_hash() is randomly keyed so this produces a random IPv4 address * that changes with every restart but otherwise stays the same */ diff --git a/src/libcharon/sa/ikev2/tasks/ike_rekey.c b/src/libcharon/sa/ikev2/tasks/ike_rekey.c index eaba04e3a..2f0552a33 100644 --- a/src/libcharon/sa/ikev2/tasks/ike_rekey.c +++ b/src/libcharon/sa/ikev2/tasks/ike_rekey.c @@ -1,7 +1,8 @@ /* + * Copyright (C) 2015-2016 Tobias Brunner * Copyright (C) 2005-2008 Martin Willi * Copyright (C) 2005 Jan Hutter - * Hochschule fuer Technik Rapperswil + * HSR Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -66,9 +67,30 @@ struct private_ike_rekey_t { * colliding task detected by the task manager */ task_t *collision; + + /** + * TRUE if rekeying can't be handled temporarily + */ + bool failed_temporarily; }; /** + * Schedule a retry if rekeying temporary failed + */ +static void schedule_delayed_rekey(private_ike_rekey_t *this) +{ + uint32_t retry; + job_t *job; + + retry = RETRY_INTERVAL - (random() % RETRY_JITTER); + job = (job_t*)rekey_ike_sa_job_create( + this->ike_sa->get_id(this->ike_sa), FALSE); + DBG1(DBG_IKE, "IKE_SA rekeying failed, trying again in %d seconds", retry); + this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED); + lib->scheduler->schedule_job(lib->scheduler, job, retry); +} + +/** * Check if an IKE_SA has any queued tasks, return initiation job */ static job_t* check_queued_tasks(ike_sa_t *ike_sa) @@ -83,7 +105,6 @@ static job_t* check_queued_tasks(ike_sa_t *ike_sa) job = (job_t*)initiate_tasks_job_create(ike_sa->get_id(ike_sa)); } enumerator->destroy(enumerator); - return job; } @@ -117,20 +138,9 @@ static void establish_new(private_ike_rekey_t *this) } this->new_sa = NULL; charon->bus->set_sa(charon->bus, this->ike_sa); - } -} -METHOD(task_t, process_r_delete, status_t, - private_ike_rekey_t *this, message_t *message) -{ - establish_new(this); - return this->ike_delete->task.process(&this->ike_delete->task, message); -} - -METHOD(task_t, build_r_delete, status_t, - private_ike_rekey_t *this, message_t *message) -{ - return this->ike_delete->task.build(&this->ike_delete->task, message); + this->ike_sa->set_state(this->ike_sa, IKE_REKEYED); + } } METHOD(task_t, build_i_delete, status_t, @@ -172,36 +182,59 @@ METHOD(task_t, build_i, status_t, return NEED_MORE; } -METHOD(task_t, process_r, status_t, - private_ike_rekey_t *this, message_t *message) +/** + * Check if there are any half-open children + */ +static bool have_half_open_children(private_ike_rekey_t *this) { enumerator_t *enumerator; child_sa_t *child_sa; - - if (this->ike_sa->get_state(this->ike_sa) == IKE_DELETING) - { - DBG1(DBG_IKE, "peer initiated rekeying, but we are deleting"); - return NEED_MORE; - } + task_t *task; enumerator = this->ike_sa->create_child_sa_enumerator(this->ike_sa); while (enumerator->enumerate(enumerator, (void**)&child_sa)) { switch (child_sa->get_state(child_sa)) { - case CHILD_CREATED: case CHILD_REKEYING: case CHILD_RETRYING: case CHILD_DELETING: - /* we do not allow rekeying while we have children in-progress */ - DBG1(DBG_IKE, "peer initiated rekeying, but a child is half-open"); enumerator->destroy(enumerator); - return NEED_MORE; + return TRUE; default: break; } } enumerator->destroy(enumerator); + enumerator = this->ike_sa->create_task_enumerator(this->ike_sa, + TASK_QUEUE_ACTIVE); + while (enumerator->enumerate(enumerator, (void**)&task)) + { + if (task->get_type(task) == TASK_CHILD_CREATE) + { + enumerator->destroy(enumerator); + return TRUE; + } + } + enumerator->destroy(enumerator); + return FALSE; +} + +METHOD(task_t, process_r, status_t, + private_ike_rekey_t *this, message_t *message) +{ + if (this->ike_sa->get_state(this->ike_sa) == IKE_DELETING) + { + DBG1(DBG_IKE, "peer initiated rekeying, but we are deleting"); + this->failed_temporarily = TRUE; + return NEED_MORE; + } + if (have_half_open_children(this)) + { + DBG1(DBG_IKE, "peer initiated rekeying, but a child is half-open"); + this->failed_temporarily = TRUE; + return NEED_MORE; + } this->new_sa = charon->ike_sa_manager->checkout_new(charon->ike_sa_manager, this->ike_sa->get_version(this->ike_sa), FALSE); @@ -219,33 +252,57 @@ METHOD(task_t, process_r, status_t, METHOD(task_t, build_r, status_t, private_ike_rekey_t *this, message_t *message) { + if (this->failed_temporarily) + { + message->add_notify(message, TRUE, TEMPORARY_FAILURE, chunk_empty); + return SUCCESS; + } if (this->new_sa == NULL) { /* IKE_SA/a CHILD_SA is in an inacceptable state, deny rekeying */ message->add_notify(message, TRUE, NO_PROPOSAL_CHOSEN, chunk_empty); return SUCCESS; } - if (this->ike_init->task.build(&this->ike_init->task, message) == FAILED) { + this->ike_init->task.destroy(&this->ike_init->task); + this->ike_init = NULL; charon->bus->set_sa(charon->bus, this->ike_sa); return SUCCESS; } charon->bus->set_sa(charon->bus, this->ike_sa); - this->ike_sa->set_state(this->ike_sa, IKE_REKEYING); - /* rekeying successful, delete the IKE_SA using a subtask */ - this->ike_delete = ike_delete_create(this->ike_sa, FALSE); - this->public.task.build = _build_r_delete; - this->public.task.process = _process_r_delete; - - /* the peer does have to delete the IKE_SA. If it does not, we get a - * unusable IKE_SA in REKEYING state without a replacement. We consider - * this a timeout condition by the peer, and trigger a delete actively. */ - lib->scheduler->schedule_job(lib->scheduler, (job_t*) - delete_ike_sa_job_create(this->ike_sa->get_id(this->ike_sa), TRUE), 90); + if (this->ike_sa->get_state(this->ike_sa) != IKE_REKEYING) + { /* in case of a collision we let the initiating task handle this */ + establish_new(this); + /* make sure the IKE_SA is gone in case the peer fails to delete it */ + lib->scheduler->schedule_job(lib->scheduler, (job_t*) + delete_ike_sa_job_create(this->ike_sa->get_id(this->ike_sa), TRUE), + 90); + } + return SUCCESS; +} - return NEED_MORE; +/** + * Conclude any undetected rekey collision. + * + * If the peer does not detect the collision it will delete this IKE_SA. + * Depending on when our request reaches the peer and we receive the delete + * this may get called at different times. + * + * Returns TRUE if there was a collision, FALSE otherwise. + */ +static bool conclude_undetected_collision(private_ike_rekey_t *this) +{ + if (this->collision && + this->collision->get_type(this->collision) == TASK_IKE_REKEY) + { + DBG1(DBG_IKE, "peer did not notice IKE_SA rekey collision, abort " + "active rekeying"); + establish_new((private_ike_rekey_t*)this->collision); + return TRUE; + } + return FALSE; } METHOD(task_t, process_i, status_t, @@ -266,18 +323,9 @@ METHOD(task_t, process_i, status_t, { case FAILED: /* rekeying failed, fallback to old SA */ - if (!(this->collision && ( - this->collision->get_type(this->collision) == TASK_IKE_DELETE || - this->collision->get_type(this->collision) == TASK_IKE_REAUTH))) + if (!conclude_undetected_collision(this)) { - job_t *job; - u_int32_t retry = RETRY_INTERVAL - (random() % RETRY_JITTER); - job = (job_t*)rekey_ike_sa_job_create( - this->ike_sa->get_id(this->ike_sa), FALSE); - DBG1(DBG_IKE, "IKE_SA rekeying failed, " - "trying again in %d seconds", retry); - this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED); - lib->scheduler->schedule_job(lib->scheduler, job, retry); + schedule_delayed_rekey(this); } return SUCCESS; case NEED_MORE: @@ -293,55 +341,53 @@ METHOD(task_t, process_i, status_t, this->collision->get_type(this->collision) == TASK_IKE_REKEY) { private_ike_rekey_t *other = (private_ike_rekey_t*)this->collision; + host_t *host; + chunk_t this_nonce, other_nonce; - /* ike_init can be NULL, if child_sa is half-open */ - if (other->ike_init) - { - host_t *host; - chunk_t this_nonce, other_nonce; - - this_nonce = this->ike_init->get_lower_nonce(this->ike_init); - other_nonce = other->ike_init->get_lower_nonce(other->ike_init); + this_nonce = this->ike_init->get_lower_nonce(this->ike_init); + other_nonce = other->ike_init->get_lower_nonce(other->ike_init); - /* if we have the lower nonce, delete rekeyed SA. If not, delete - * the redundant. */ - if (memcmp(this_nonce.ptr, other_nonce.ptr, - min(this_nonce.len, other_nonce.len)) > 0) + /* if we have the lower nonce, delete rekeyed SA. If not, delete + * the redundant. */ + if (memcmp(this_nonce.ptr, other_nonce.ptr, + min(this_nonce.len, other_nonce.len)) < 0) + { + DBG1(DBG_IKE, "IKE_SA rekey collision lost, deleting redundant " + "IKE_SA %s[%d]", this->new_sa->get_name(this->new_sa), + this->new_sa->get_unique_id(this->new_sa)); + /* apply host for a proper delete */ + host = this->ike_sa->get_my_host(this->ike_sa); + this->new_sa->set_my_host(this->new_sa, host->clone(host)); + host = this->ike_sa->get_other_host(this->ike_sa); + this->new_sa->set_other_host(this->new_sa, host->clone(host)); + /* IKE_SAs in state IKE_REKEYED are silently deleted, so we use + * IKE_REKEYING */ + this->new_sa->set_state(this->new_sa, IKE_REKEYING); + if (this->new_sa->delete(this->new_sa) == DESTROY_ME) { - /* peer should delete this SA. Add a timeout just in case. */ - job_t *job = (job_t*)delete_ike_sa_job_create( - other->new_sa->get_id(other->new_sa), TRUE); - lib->scheduler->schedule_job(lib->scheduler, job, 10); - DBG1(DBG_IKE, "IKE_SA rekey collision won, waiting for delete"); - charon->ike_sa_manager->checkin(charon->ike_sa_manager, other->new_sa); - other->new_sa = NULL; + this->new_sa->destroy(this->new_sa); } else { - DBG1(DBG_IKE, "IKE_SA rekey collision lost, " - "deleting redundant IKE_SA"); - /* apply host for a proper delete */ - host = this->ike_sa->get_my_host(this->ike_sa); - this->new_sa->set_my_host(this->new_sa, host->clone(host)); - host = this->ike_sa->get_other_host(this->ike_sa); - this->new_sa->set_other_host(this->new_sa, host->clone(host)); - this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED); - this->new_sa->set_state(this->new_sa, IKE_REKEYING); - if (this->new_sa->delete(this->new_sa) == DESTROY_ME) - { - this->new_sa->destroy(this->new_sa); - } - else - { - charon->ike_sa_manager->checkin( - charon->ike_sa_manager, this->new_sa); - } - charon->bus->set_sa(charon->bus, this->ike_sa); - this->new_sa = NULL; - establish_new(other); - return SUCCESS; + charon->ike_sa_manager->checkin(charon->ike_sa_manager, + this->new_sa); } + charon->bus->set_sa(charon->bus, this->ike_sa); + this->new_sa = NULL; + establish_new(other); + return SUCCESS; } + /* peer should delete this SA. Add a timeout just in case. */ + job_t *job = (job_t*)delete_ike_sa_job_create( + other->new_sa->get_id(other->new_sa), TRUE); + lib->scheduler->schedule_job(lib->scheduler, job, + HALF_OPEN_IKE_SA_TIMEOUT); + DBG1(DBG_IKE, "IKE_SA rekey collision won, waiting for delete for " + "redundant IKE_SA %s[%d]", other->new_sa->get_name(other->new_sa), + other->new_sa->get_unique_id(other->new_sa)); + other->new_sa->set_state(other->new_sa, IKE_REKEYED); + charon->ike_sa_manager->checkin(charon->ike_sa_manager, other->new_sa); + other->new_sa = NULL; charon->bus->set_sa(charon->bus, this->ike_sa); } @@ -361,11 +407,41 @@ METHOD(task_t, get_type, task_type_t, return TASK_IKE_REKEY; } +METHOD(ike_rekey_t, did_collide, bool, + private_ike_rekey_t *this) +{ + return this->collision && + this->collision->get_type(this->collision) == TASK_IKE_REKEY; +} + METHOD(ike_rekey_t, collide, void, private_ike_rekey_t* this, task_t *other) { DBG1(DBG_IKE, "detected %N collision with %N", task_type_names, TASK_IKE_REKEY, task_type_names, other->get_type(other)); + + switch (other->get_type(other)) + { + case TASK_IKE_DELETE: + conclude_undetected_collision(this); + other->destroy(other); + return; + case TASK_IKE_REKEY: + { + private_ike_rekey_t *rekey = (private_ike_rekey_t*)other; + + if (!rekey->ike_init) + { + DBG1(DBG_IKE, "colliding exchange did not result in an IKE_SA, " + "ignore"); + other->destroy(other); + return; + } + break; + } + default: + break; + } DESTROY_IF(this->collision); this->collision = other; } @@ -425,6 +501,7 @@ ike_rekey_t *ike_rekey_create(ike_sa_t *ike_sa, bool initiator) .migrate = _migrate, .destroy = _destroy, }, + .did_collide = _did_collide, .collide = _collide, }, .ike_sa = ike_sa, diff --git a/src/libcharon/sa/ikev2/tasks/ike_rekey.h b/src/libcharon/sa/ikev2/tasks/ike_rekey.h index 6a12e9034..86b512c92 100644 --- a/src/libcharon/sa/ikev2/tasks/ike_rekey.h +++ b/src/libcharon/sa/ikev2/tasks/ike_rekey.h @@ -1,6 +1,7 @@ /* + * Copyright (C) 2016 Tobias Brunner * Copyright (C) 2007 Martin Willi - * Hochschule fuer Technik Rapperswil + * HSR Hochschule fuer Technik Rapperswil * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the @@ -38,6 +39,13 @@ struct ike_rekey_t { task_t task; /** + * Check if there was a rekey collision. + * + * @return TRUE if there was a rekey collision before + */ + bool (*did_collide)(ike_rekey_t *this); + + /** * Register a rekeying task which collides with this one. * * If two peers initiate rekeying at the same time, the collision must |