diff options
Diffstat (limited to 'src/charon/sa/connect_manager.c')
-rw-r--r-- | src/charon/sa/connect_manager.c | 1623 |
1 files changed, 0 insertions, 1623 deletions
diff --git a/src/charon/sa/connect_manager.c b/src/charon/sa/connect_manager.c deleted file mode 100644 index b78ba070d..000000000 --- a/src/charon/sa/connect_manager.c +++ /dev/null @@ -1,1623 +0,0 @@ -/* - * Copyright (C) 2007-2008 Tobias Brunner - * 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 - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - */ - -#include "connect_manager.h" - -#include <math.h> - -#include <daemon.h> -#include <threading/mutex.h> -#include <utils/linked_list.h> -#include <crypto/hashers/hasher.h> - -#include <processing/jobs/callback_job.h> -#include <processing/jobs/initiate_mediation_job.h> -#include <encoding/payloads/endpoint_notify.h> - -/* base timeout - * the check interval is ME_INTERVAL */ -#define ME_INTERVAL 25 /* ms */ -/* retransmission timeout is first ME_INTERVAL for ME_BOOST retransmissions - * then gets reduced to ME_INTERVAL * ME_RETRANS_BASE ^ (sent retransmissions - ME_BOOST). */ -/* number of initial retransmissions sent in short interval */ -#define ME_BOOST 2 -/* base for retransmissions */ -#define ME_RETRANS_BASE 1.8 -/* max number of retransmissions */ -#define ME_MAX_RETRANS 13 - -/* time to wait before the initiator finishes the connectivity checks after - * the first check has succeeded */ -#define ME_WAIT_TO_FINISH 1000 /* ms */ - -typedef struct private_connect_manager_t private_connect_manager_t; - -/** - * Additional private members of connect_manager_t. - */ -struct private_connect_manager_t { - /** - * Public interface of connect_manager_t. - */ - connect_manager_t public; - - /** - * Lock for exclusivly accessing the manager. - */ - mutex_t *mutex; - - /** - * Hasher to generate signatures - */ - hasher_t *hasher; - - /** - * Linked list with initiated mediated connections - */ - linked_list_t *initiated; - - /** - * Linked list with checklists (hash table with connect ID as key would - * be better). - */ - linked_list_t *checklists; -}; - -typedef enum check_state_t check_state_t; - -enum check_state_t { - CHECK_NONE, - CHECK_WAITING, - CHECK_IN_PROGRESS, - CHECK_SUCCEEDED, - CHECK_FAILED -}; - -typedef struct endpoint_pair_t endpoint_pair_t; - -/** - * An entry in the check list. - */ -struct endpoint_pair_t { - /** pair id */ - u_int32_t id; - - /** priority */ - u_int64_t priority; - - /** local endpoint */ - host_t *local; - - /** remote endpoint */ - host_t *remote; - - /** state */ - check_state_t state; - - /** number of retransmissions */ - u_int32_t retransmitted; - - /** the generated packet */ - packet_t *packet; -}; - -/** - * Destroys an endpoint pair - */ -static void endpoint_pair_destroy(endpoint_pair_t *this) -{ - DESTROY_IF(this->local); - DESTROY_IF(this->remote); - DESTROY_IF(this->packet); - free(this); -} - -/** - * Creates a new entry for the list. - */ -static endpoint_pair_t *endpoint_pair_create(endpoint_notify_t *initiator, - endpoint_notify_t *responder, bool initiator_is_local) -{ - endpoint_pair_t *this = malloc_thing(endpoint_pair_t); - - this->id = 0; - - u_int32_t pi = initiator->get_priority(initiator); - u_int32_t pr = responder->get_priority(responder); - this->priority = pow(2, 32) * min(pi, pr) + 2 * max(pi, pr) + (pi > pr ? 1 : 0); - - this->local = initiator_is_local ? initiator->get_base(initiator) - : responder->get_base(responder); - this->local = this->local->clone(this->local); - this->remote = initiator_is_local ? responder->get_host(responder) - : initiator->get_host(initiator); - this->remote = this->remote->clone(this->remote); - - this->state = CHECK_WAITING; - this->retransmitted = 0; - this->packet = NULL; - - return this; -} - - -typedef struct check_list_t check_list_t; - -/** - * An entry in the linked list. - */ -struct check_list_t { - - struct { - /** initiator's id */ - identification_t *id; - - /** initiator's key */ - chunk_t key; - - /** initiator's endpoints */ - linked_list_t *endpoints; - } initiator; - - struct { - /** responder's id */ - identification_t *id; - - /** responder's key */ - chunk_t key; - - /** responder's endpoints */ - linked_list_t *endpoints; - } responder; - - /** connect id */ - chunk_t connect_id; - - /** list of endpoint pairs */ - linked_list_t *pairs; - - /** pairs queued for triggered checks */ - linked_list_t *triggered; - - /** state */ - check_state_t state; - - /** TRUE if this is the initiator */ - bool is_initiator; - - /** TRUE if the initiator is finishing the checks */ - bool is_finishing; - - /** the current sender job */ - job_t *sender; - -}; - -/** - * Destroys a checklist - */ -static void check_list_destroy(check_list_t *this) -{ - DESTROY_IF(this->initiator.id); - DESTROY_IF(this->responder.id); - - chunk_free(&this->connect_id); - chunk_free(&this->initiator.key); - chunk_free(&this->responder.key); - - DESTROY_OFFSET_IF(this->initiator.endpoints, - offsetof(endpoint_notify_t, destroy)); - DESTROY_OFFSET_IF(this->responder.endpoints, - offsetof(endpoint_notify_t, destroy)); - - DESTROY_FUNCTION_IF(this->pairs, (void*)endpoint_pair_destroy); - /* this list contains some of the elements contained in this->pairs */ - DESTROY_IF(this->triggered); - - free(this); -} - -/** - * Creates a new checklist - */ -static check_list_t *check_list_create(identification_t *initiator, - identification_t *responder, - chunk_t connect_id, - chunk_t initiator_key, - linked_list_t *initiator_endpoints, - bool is_initiator) -{ - check_list_t *this = malloc_thing(check_list_t); - - this->connect_id = chunk_clone(connect_id); - - this->initiator.id = initiator->clone(initiator); - this->initiator.key = chunk_clone(initiator_key); - this->initiator.endpoints = initiator_endpoints->clone_offset(initiator_endpoints, offsetof(endpoint_notify_t, clone)); - - this->responder.id = responder->clone(responder); - this->responder.key = chunk_empty; - this->responder.endpoints = NULL; - - this->pairs = linked_list_create(); - this->triggered = linked_list_create(); - this->state = CHECK_NONE; - this->is_initiator = is_initiator; - this->is_finishing = FALSE; - - return this; -} - -typedef struct initiated_t initiated_t; - -/** - * For an initiator, the data stored about initiated mediation connections - */ -struct initiated_t { - /** my id */ - identification_t *id; - - /** peer id */ - identification_t *peer_id; - - /** list of mediated sas */ - linked_list_t *mediated; -}; - -/** - * Destroys a queued initiation - */ -static void initiated_destroy(initiated_t *this) -{ - DESTROY_IF(this->id); - DESTROY_IF(this->peer_id); - this->mediated->destroy_offset(this->mediated, - offsetof(ike_sa_id_t, destroy)); - free(this); -} - -/** - * Creates a queued initiation - */ -static initiated_t *initiated_create(identification_t *id, - identification_t *peer_id) -{ - initiated_t *this = malloc_thing(initiated_t); - - this->id = id->clone(id); - this->peer_id = peer_id->clone(peer_id); - this->mediated = linked_list_create(); - - return this; -} - - -typedef struct check_t check_t; - -/** - * Data exchanged in a connectivity check - */ -struct check_t { - /** message id */ - u_int32_t mid; - - /** source of the connectivity check */ - host_t *src; - - /** destination of the connectivity check */ - host_t *dst; - - /** connect id */ - chunk_t connect_id; - - /** endpoint */ - endpoint_notify_t *endpoint; - - /** raw endpoint payload (to verify the signature) */ - chunk_t endpoint_raw; - - /** connect auth */ - chunk_t auth; -}; - -/** - * Destroys a connectivity check - */ -static void check_destroy(check_t *this) -{ - chunk_free(&this->connect_id); - chunk_free(&this->endpoint_raw); - chunk_free(&this->auth); - DESTROY_IF(this->src); - DESTROY_IF(this->dst); - DESTROY_IF(this->endpoint); - free(this); -} - -/** - * Creates a new connectivity check - */ -static check_t *check_create() -{ - check_t *this = malloc_thing(check_t); - - this->connect_id = chunk_empty; - this->auth = chunk_empty; - this->endpoint_raw = chunk_empty; - this->src = NULL; - this->dst = NULL; - this->endpoint = NULL; - - this->mid = 0; - - return this; -} - -typedef struct callback_data_t callback_data_t; - -/** - * Data required by several callback jobs used in this file - */ -struct callback_data_t { - /** connect manager */ - private_connect_manager_t *connect_manager; - - /** connect id */ - chunk_t connect_id; - - /** message (pair) id */ - u_int32_t mid; -}; - -/** - * Destroys a callback data object - */ -static void callback_data_destroy(callback_data_t *this) -{ - chunk_free(&this->connect_id); - free(this); -} - -/** - * Creates a new callback data object - */ -static callback_data_t *callback_data_create(private_connect_manager_t *connect_manager, - chunk_t connect_id) -{ - callback_data_t *this = malloc_thing(callback_data_t); - this->connect_manager = connect_manager; - this->connect_id = chunk_clone(connect_id); - this->mid = 0; - return this; -} - -/** - * 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) -{ - callback_data_t *this = callback_data_create(connect_manager, connect_id); - this->mid = mid; - return this; -} - -typedef struct initiate_data_t initiate_data_t; - -/** - * Data required by the initiate mediated - */ -struct initiate_data_t { - /** checklist */ - check_list_t *checklist; - - /** waiting mediated connections */ - initiated_t *initiated; -}; - -/** - * Destroys a initiate data object - */ -static void initiate_data_destroy(initiate_data_t *this) -{ - check_list_destroy(this->checklist); - initiated_destroy(this->initiated); - free(this); -} - -/** - * Creates a new initiate data object - */ -static initiate_data_t *initiate_data_create(check_list_t *checklist, - initiated_t *initiated) -{ - initiate_data_t *this = malloc_thing(initiate_data_t); - - this->checklist = checklist; - this->initiated = initiated; - - return this; -} - -/** - * Find an initiated connection by the peers' ids - */ -static bool match_initiated_by_ids(initiated_t *current, identification_t *id, - identification_t *peer_id) -{ - return id->equals(id, current->id) && peer_id->equals(peer_id, current->peer_id); -} - -static status_t get_initiated_by_ids(private_connect_manager_t *this, - identification_t *id, - identification_t *peer_id, - initiated_t **initiated) -{ - return this->initiated->find_first(this->initiated, - (linked_list_match_t)match_initiated_by_ids, - (void**)initiated, id, peer_id); -} - -/** - * Removes data about initiated connections - */ -static void remove_initiated(private_connect_manager_t *this, - initiated_t *initiated) -{ - iterator_t *iterator; - initiated_t *current; - - iterator = this->initiated->create_iterator(this->initiated, TRUE); - while (iterator->iterate(iterator, (void**)¤t)) - { - if (current == initiated) - { - iterator->remove(iterator); - break; - } - } - iterator->destroy(iterator); -} - -/** - * Find the checklist with a specific connect ID - */ -static bool match_checklist_by_id(check_list_t *current, chunk_t *connect_id) -{ - return chunk_equals(*connect_id, current->connect_id); -} - -static status_t get_checklist_by_id(private_connect_manager_t *this, - chunk_t connect_id, - check_list_t **check_list) -{ - return this->checklists->find_first(this->checklists, - (linked_list_match_t)match_checklist_by_id, - (void**)check_list, &connect_id); -} - -/** - * Removes a checklist - */ -static void remove_checklist(private_connect_manager_t *this, - check_list_t *checklist) -{ - iterator_t *iterator; - check_list_t *current; - - iterator = this->checklists->create_iterator(this->checklists, TRUE); - while (iterator->iterate(iterator, (void**)¤t)) - { - if (current == checklist) - { - iterator->remove(iterator); - break; - } - } - iterator->destroy(iterator); -} - -/** - * Checks if a list of endpoint_notify_t contains a certain host_t - */ -static bool match_endpoint_by_host(endpoint_notify_t *current, host_t *host) -{ - return host->equals(host, current->get_host(current)); -} - -static status_t endpoints_contain(linked_list_t *endpoints, host_t *host, - endpoint_notify_t **endpoint) -{ - return endpoints->find_first(endpoints, - (linked_list_match_t)match_endpoint_by_host, - (void**)endpoint, host); -} - -/** - * Inserts an endpoint pair into a list of pairs ordered by priority (high to low) - */ -static void insert_pair_by_priority(linked_list_t *pairs, endpoint_pair_t *pair) -{ - iterator_t *iterator; - endpoint_pair_t *current; - bool inserted = FALSE; - - iterator = pairs->create_iterator(pairs, TRUE); - while (iterator->iterate(iterator, (void**)¤t)) - { - if (current->priority < pair->priority) - { - iterator->insert_before(iterator, pair); - inserted = TRUE; - break; - } - } - iterator->destroy(iterator); - - if (!inserted) - { - pairs->insert_last(pairs, pair); - } -} - -/** - * Searches a list of endpoint_pair_t for a pair with specific host_ts - */ -static bool match_pair_by_hosts(endpoint_pair_t *current, host_t *local, - host_t *remote) -{ - return local->equals(local, current->local) && remote->equals(remote, current->remote); -} - -static status_t get_pair_by_hosts(linked_list_t *pairs, host_t *local, - host_t *remote, endpoint_pair_t **pair) -{ - return pairs->find_first(pairs, (linked_list_match_t)match_pair_by_hosts, - (void**)pair, local, remote); -} - -static bool match_pair_by_id(endpoint_pair_t *current, u_int32_t *id) -{ - return current->id == *id; -} - -/** - * Searches for a pair with a specific id - */ -static status_t get_pair_by_id(check_list_t *checklist, u_int32_t id, - endpoint_pair_t **pair) -{ - return checklist->pairs->find_first(checklist->pairs, - (linked_list_match_t)match_pair_by_id, - (void**)pair, &id); -} - -static bool match_succeeded_pair(endpoint_pair_t *current) -{ - return current->state == CHECK_SUCCEEDED; -} - -/** - * Returns the best pair of state CHECK_SUCCEEDED from a checklist. - */ -static status_t get_best_valid_pair(check_list_t *checklist, - endpoint_pair_t **pair) -{ - return checklist->pairs->find_first(checklist->pairs, - (linked_list_match_t)match_succeeded_pair, - (void**)pair); -} - -static bool match_waiting_pair(endpoint_pair_t *current) -{ - return current->state == CHECK_WAITING; -} - -/** - * Returns and *removes* the first triggered pair in state CHECK_WAITING. - */ -static status_t get_triggered_pair(check_list_t *checklist, - endpoint_pair_t **pair) -{ - iterator_t *iterator; - endpoint_pair_t *current; - status_t status = NOT_FOUND; - - iterator = checklist->triggered->create_iterator(checklist->triggered, TRUE); - while (iterator->iterate(iterator, (void**)¤t)) - { - iterator->remove(iterator); - - if (current->state == CHECK_WAITING) - { - if (pair) - { - *pair = current; - } - status = SUCCESS; - break; - } - } - iterator->destroy(iterator); - - return status; -} - -/** - * Prints all the pairs on a checklist - */ -static void print_checklist(check_list_t *checklist) -{ - iterator_t *iterator; - endpoint_pair_t *current; - - DBG1(DBG_IKE, "pairs on checklist %#B:", &checklist->connect_id); - iterator = checklist->pairs->create_iterator(checklist->pairs, TRUE); - while (iterator->iterate(iterator, (void**)¤t)) - { - DBG1(DBG_IKE, " * %#H - %#H (%d)", current->local, current->remote, - current->priority); - } - iterator->destroy(iterator); -} - -/** - * Prunes identical pairs with lower priority from the list - * Note: this function also numbers the remaining pairs serially - */ -static void prune_pairs(linked_list_t *pairs) -{ - iterator_t *iterator, *search; - endpoint_pair_t *current, *other; - u_int32_t id = 0; - - iterator = pairs->create_iterator(pairs, TRUE); - search = pairs->create_iterator(pairs, TRUE); - while (iterator->iterate(iterator, (void**)¤t)) - { - current->id = ++id; - - while (search->iterate(search, (void**)&other)) - { - if (current == other) - { - continue; - } - - if (current->local->equals(current->local, other->local) && - current->remote->equals(current->remote, other->remote)) - { - /* since the list of pairs is sorted by priority in descending - * order, and we iterate the list from the beginning, we are - * sure that the priority of 'other' is lower than that of - * 'current', remove it */ - DBG1(DBG_IKE, "pruning endpoint pair %#H - %#H with priority %d", - other->local, other->remote, other->priority); - search->remove(search); - endpoint_pair_destroy(other); - } - } - search->reset(search); - } - search->destroy(search); - iterator->destroy(iterator); -} - -/** - * Builds a list of endpoint pairs - */ -static void build_pairs(check_list_t *checklist) -{ - /* FIXME: limit endpoints and pairs */ - iterator_t *iterator_i, *iterator_r; - endpoint_notify_t *initiator, *responder; - - iterator_i = checklist->initiator.endpoints->create_iterator( - checklist->initiator.endpoints, TRUE); - while (iterator_i->iterate(iterator_i, (void**)&initiator)) - { - iterator_r = checklist->responder.endpoints->create_iterator( - checklist->responder.endpoints, TRUE); - while (iterator_r->iterate(iterator_r, (void**)&responder)) - { - if (initiator->get_family(initiator) != responder->get_family(responder)) - { - continue; - } - - insert_pair_by_priority(checklist->pairs, endpoint_pair_create( - initiator, responder, checklist->is_initiator)); - } - iterator_r->destroy(iterator_r); - } - iterator_i->destroy(iterator_i); - - print_checklist(checklist); - - prune_pairs(checklist->pairs); -} - -/** - * Processes the payloads of a connectivity check and returns the extracted data - */ -static status_t process_payloads(message_t *message, check_t *check) -{ - enumerator_t *enumerator; - payload_t *payload; - - enumerator = message->create_payload_enumerator(message); - while (enumerator->enumerate(enumerator, &payload)) - { - if (payload->get_type(payload) != NOTIFY) - { - DBG1(DBG_IKE, "ignoring payload of type '%N' while processing " - "connectivity check", payload_type_names, - payload->get_type(payload)); - continue; - } - - notify_payload_t *notify = (notify_payload_t*)payload; - - switch (notify->get_notify_type(notify)) - { - case ME_ENDPOINT: - { - if (check->endpoint) - { - DBG1(DBG_IKE, "connectivity check contains multiple " - "ME_ENDPOINT notifies"); - break; - } - - endpoint_notify_t *endpoint = endpoint_notify_create_from_payload(notify); - if (!endpoint) - { - DBG1(DBG_IKE, "received invalid ME_ENDPOINT notify"); - break; - } - check->endpoint = endpoint; - check->endpoint_raw = chunk_clone(notify->get_notification_data(notify)); - DBG2(DBG_IKE, "received ME_ENDPOINT notify"); - break; - } - case ME_CONNECTID: - { - if (check->connect_id.ptr) - { - DBG1(DBG_IKE, "connectivity check contains multiple " - "ME_CONNECTID notifies"); - break; - } - check->connect_id = chunk_clone(notify->get_notification_data(notify)); - DBG2(DBG_IKE, "received ME_CONNECTID %#B", &check->connect_id); - break; - } - case ME_CONNECTAUTH: - { - if (check->auth.ptr) - { - DBG1(DBG_IKE, "connectivity check contains multiple " - "ME_CONNECTAUTH notifies"); - break; - } - check->auth = chunk_clone(notify->get_notification_data(notify)); - DBG2(DBG_IKE, "received ME_CONNECTAUTH %#B", &check->auth); - break; - } - default: - break; - } - } - enumerator->destroy(enumerator); - - if (!check->connect_id.ptr || !check->endpoint || !check->auth.ptr) - { - DBG1(DBG_IKE, "at least one required payload was missing from the " - "connectivity check"); - return FAILED; - } - - return SUCCESS; -} - -/** - * Builds the signature for a connectivity check - */ -static chunk_t build_signature(private_connect_manager_t *this, - check_list_t *checklist, check_t *check, bool outbound) -{ - u_int32_t mid; - chunk_t mid_chunk, key_chunk, sig_chunk; - chunk_t sig_hash; - - mid = htonl(check->mid); - mid_chunk = chunk_from_thing(mid); - - key_chunk = (checklist->is_initiator && outbound) || (!checklist->is_initiator && !outbound) - ? checklist->initiator.key : checklist->responder.key; - - /* signature = SHA1( MID | ME_CONNECTID | ME_ENDPOINT | ME_CONNECTKEY ) */ - sig_chunk = chunk_cat("cccc", mid_chunk, check->connect_id, - check->endpoint_raw, key_chunk); - this->hasher->allocate_hash(this->hasher, sig_chunk, &sig_hash); - DBG3(DBG_IKE, "sig_chunk %#B", &sig_chunk); - DBG3(DBG_IKE, "sig_hash %#B", &sig_hash); - - chunk_free(&sig_chunk); - return sig_hash; -} - -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 finish_checks(private_connect_manager_t *this, check_list_t *checklist); - -/** - * After one of the initiator's pairs has succeeded we finish the checks without - * waiting for all the timeouts - */ -static job_requeue_t initiator_finish(callback_data_t *data) -{ - private_connect_manager_t *this = data->connect_manager; - - this->mutex->lock(this->mutex); - - check_list_t *checklist; - if (get_checklist_by_id(this, data->connect_id, &checklist) != SUCCESS) - { - DBG1(DBG_IKE, "checklist with id '%#B' not found, can't finish " - "connectivity checks", &data->connect_id); - this->mutex->unlock(this->mutex); - return JOB_REQUEUE_NONE; - } - - finish_checks(this, checklist); - - this->mutex->unlock(this->mutex); - - return JOB_REQUEUE_NONE; -} - -/** - * Updates the state of the whole checklist - */ -static void update_checklist_state(private_connect_manager_t *this, - check_list_t *checklist) -{ - iterator_t *iterator; - endpoint_pair_t *current; - bool in_progress = FALSE, succeeded = FALSE; - - iterator = checklist->pairs->create_iterator(checklist->pairs, TRUE); - while (iterator->iterate(iterator, (void**)¤t)) - { - switch(current->state) - { - case CHECK_WAITING: - /* at least one is still waiting -> checklist remains - * in waiting state */ - iterator->destroy(iterator); - return; - case CHECK_IN_PROGRESS: - in_progress = TRUE; - break; - case CHECK_SUCCEEDED: - succeeded = TRUE; - break; - default: - break; - } - } - iterator->destroy(iterator); - - if (checklist->is_initiator && succeeded && !checklist->is_finishing) - { - /* instead of waiting until all checks have finished (i.e. all - * retransmissions have failed) the initiator finishes the checks - * right after the first check has succeeded. to allow a probably - * better pair to succeed, we still wait a certain time */ - DBG2(DBG_IKE, "fast finishing checks for checklist '%#B'", - &checklist->connect_id); - - callback_data_t *data = callback_data_create(this, checklist->connect_id); - job_t *job = (job_t*)callback_job_create((callback_job_cb_t)initiator_finish, data, (callback_job_cleanup_t)callback_data_destroy, NULL); - charon->scheduler->schedule_job_ms(charon->scheduler, job, ME_WAIT_TO_FINISH); - checklist->is_finishing = TRUE; - } - - if (in_progress) - { - checklist->state = CHECK_IN_PROGRESS; - } - else if (succeeded) - { - checklist->state = CHECK_SUCCEEDED; - } - else - { - checklist->state = CHECK_FAILED; - } -} - -/** - * This function is triggered for each sent check after a specific timeout - */ -static job_requeue_t retransmit(callback_data_t *data) -{ - private_connect_manager_t *this = data->connect_manager; - - this->mutex->lock(this->mutex); - - check_list_t *checklist; - if (get_checklist_by_id(this, data->connect_id, &checklist) != SUCCESS) - { - DBG1(DBG_IKE, "checklist with id '%#B' not found, can't retransmit " - "connectivity check", &data->connect_id); - this->mutex->unlock(this->mutex); - return JOB_REQUEUE_NONE; - } - - endpoint_pair_t *pair; - if (get_pair_by_id(checklist, data->mid, &pair) != SUCCESS) - { - DBG1(DBG_IKE, "pair with id '%d' not found, can't retransmit " - "connectivity check", data->mid); - goto retransmit_end; - } - - if (pair->state != CHECK_IN_PROGRESS) - { - DBG2(DBG_IKE, "pair with id '%d' is in wrong state [%d], don't " - "retransmit the connectivity check", data->mid, pair->state); - goto retransmit_end; - } - - if (++pair->retransmitted > ME_MAX_RETRANS) - { - DBG2(DBG_IKE, "pair with id '%d' failed after %d retransmissions", - data->mid, ME_MAX_RETRANS); - pair->state = CHECK_FAILED; - goto retransmit_end; - } - - charon->sender->send(charon->sender, pair->packet->clone(pair->packet)); - - queue_retransmission(this, checklist, pair); - -retransmit_end: - update_checklist_state(this, checklist); - - switch(checklist->state) - { - case CHECK_SUCCEEDED: - case CHECK_FAILED: - finish_checks(this, checklist); - break; - default: - break; - } - - this->mutex->unlock(this->mutex); - - /* we reschedule it manually */ - return JOB_REQUEUE_NONE; -} - -/** - * Queues a retransmission job - */ -static void queue_retransmission(private_connect_manager_t *this, check_list_t *checklist, endpoint_pair_t *pair) -{ - callback_data_t *data = retransmit_data_create(this, checklist->connect_id, pair->id); - job_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; - if (retransmission > ME_BOOST) - { - rto = (u_int32_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); - - charon->scheduler->schedule_job_ms(charon->scheduler, (job_t*)job, rto); -} - -/** - * Sends a check - */ -static void send_check(private_connect_manager_t *this, check_list_t *checklist, - check_t *check, endpoint_pair_t *pair, bool request) -{ - message_t *message = message_create(); - message->set_message_id(message, check->mid); - message->set_exchange_type(message, INFORMATIONAL); - message->set_request(message, request); - message->set_destination(message, check->dst->clone(check->dst)); - message->set_source(message, check->src->clone(check->src)); - - ike_sa_id_t *ike_sa_id = ike_sa_id_create(0, 0, request); - message->set_ike_sa_id(message, ike_sa_id); - ike_sa_id->destroy(ike_sa_id); - - message->add_notify(message, FALSE, ME_CONNECTID, check->connect_id); - DBG2(DBG_IKE, "send ME_CONNECTID %#B", &check->connect_id); - - notify_payload_t *endpoint = check->endpoint->build_notify(check->endpoint); - check->endpoint_raw = chunk_clone(endpoint->get_notification_data(endpoint)); - message->add_payload(message, (payload_t*)endpoint); - DBG2(DBG_IKE, "send ME_ENDPOINT notify"); - - check->auth = build_signature(this, checklist, check, TRUE); - message->add_notify(message, FALSE, ME_CONNECTAUTH, check->auth); - DBG2(DBG_IKE, "send ME_CONNECTAUTH %#B", &check->auth); - - packet_t *packet; - if (message->generate(message, NULL, NULL, &packet) == SUCCESS) - { - charon->sender->send(charon->sender, packet->clone(packet)); - - if (request) - { - DESTROY_IF(pair->packet); - pair->packet = packet; - pair->retransmitted = 0; - queue_retransmission(this, checklist, pair); - } - else - { - packet->destroy(packet); - } - } - message->destroy(message); -} - -/** - * Queues a triggered check - */ -static void queue_triggered_check(private_connect_manager_t *this, - check_list_t *checklist, endpoint_pair_t *pair) -{ - DBG2(DBG_IKE, "queueing triggered check for pair '%d'", pair->id); - pair->state = CHECK_WAITING; - checklist->triggered->insert_last(checklist->triggered, pair); - - if (!checklist->sender) - { - /* if the sender is not running we restart it */ - schedule_checks(this, checklist, ME_INTERVAL); - } -} - -/** - * This function is triggered for each checklist at a specific interval - */ -static job_requeue_t sender(callback_data_t *data) -{ - private_connect_manager_t *this = data->connect_manager; - - this->mutex->lock(this->mutex); - - check_list_t *checklist; - if (get_checklist_by_id(this, data->connect_id, &checklist) != SUCCESS) - { - DBG1(DBG_IKE, "checklist with id '%#B' not found, can't send " - "connectivity check", &data->connect_id); - this->mutex->unlock(this->mutex); - return JOB_REQUEUE_NONE; - } - - /* reset the sender */ - checklist->sender = NULL; - - endpoint_pair_t *pair; - if (get_triggered_pair(checklist, &pair) != SUCCESS) - { - DBG1(DBG_IKE, "no triggered check queued, sending an ordinary check"); - - if (checklist->pairs->find_first(checklist->pairs, - (linked_list_match_t)match_waiting_pair, - (void**)&pair) != SUCCESS) - { - this->mutex->unlock(this->mutex); - DBG1(DBG_IKE, "no pairs in waiting state, aborting"); - return JOB_REQUEUE_NONE; - } - } - else - { - DBG1(DBG_IKE, "triggered check found"); - } - - check_t *check = check_create(); - check->mid = pair->id; - check->src = pair->local->clone(pair->local); - check->dst = pair->remote->clone(pair->remote); - check->connect_id = chunk_clone(checklist->connect_id); - check->endpoint = endpoint_notify_create_from_host(PEER_REFLEXIVE, NULL, - NULL); - - pair->state = CHECK_IN_PROGRESS; - - send_check(this, checklist, check, pair, TRUE); - - check_destroy(check); - - /* schedule this job again */ - schedule_checks(this, checklist, ME_INTERVAL); - - this->mutex->unlock(this->mutex); - - /* we reschedule it manually */ - return JOB_REQUEUE_NONE; -} - -/** - * 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) -{ - callback_data_t *data = callback_data_create(this, checklist->connect_id); - checklist->sender = (job_t*)callback_job_create((callback_job_cb_t)sender, data, (callback_job_cleanup_t)callback_data_destroy, NULL); - charon->scheduler->schedule_job_ms(charon->scheduler, checklist->sender, time); -} - -/** - * Initiates waiting mediated connections - */ -static job_requeue_t initiate_mediated(initiate_data_t *data) -{ - check_list_t *checklist = data->checklist; - initiated_t *initiated = data->initiated; - - endpoint_pair_t *pair; - if (get_best_valid_pair(checklist, &pair) == SUCCESS) - { - ike_sa_id_t *waiting_sa; - iterator_t *iterator = initiated->mediated->create_iterator(initiated->mediated, TRUE); - while (iterator->iterate(iterator, (void**)&waiting_sa)) - { - ike_sa_t *sa = charon->ike_sa_manager->checkout(charon->ike_sa_manager, waiting_sa); - if (sa->initiate_mediated(sa, pair->local, pair->remote, checklist->connect_id) != SUCCESS) - { - DBG1(DBG_IKE, "establishing mediated connection failed"); - charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, sa); - } - charon->ike_sa_manager->checkin(charon->ike_sa_manager, sa); - } - iterator->destroy(iterator); - } - else - { - /* this should (can?) not happen */ - } - - return JOB_REQUEUE_NONE; -} - -/** - * Finishes checks for a checklist - */ -static void finish_checks(private_connect_manager_t *this, check_list_t *checklist) -{ - if (checklist->is_initiator) - { - initiated_t *initiated; - if (get_initiated_by_ids(this, checklist->initiator.id, - checklist->responder.id, &initiated) == SUCCESS) - { - remove_checklist(this, checklist); - remove_initiated(this, initiated); - - initiate_data_t *data = initiate_data_create(checklist, initiated); - job_t *job = (job_t*)callback_job_create((callback_job_cb_t)initiate_mediated, data, (callback_job_cleanup_t)initiate_data_destroy, NULL); - charon->processor->queue_job(charon->processor, job); - return; - } - else - { - DBG1(DBG_IKE, "there is no mediated connection waiting between '%Y'" - " and '%Y'", checklist->initiator.id, checklist->responder.id); - } - } -} - -/** - * Process the response to one of our requests - */ -static void process_response(private_connect_manager_t *this, check_t *check, - check_list_t *checklist) -{ - endpoint_pair_t *pair; - if (get_pair_by_id(checklist, check->mid, &pair) == SUCCESS) - { - if (pair->local->equals(pair->local, check->dst) && - pair->remote->equals(pair->remote, check->src)) - { - DBG1(DBG_IKE, "endpoint pair '%d' is valid: '%#H' - '%#H'", - pair->id, pair->local, pair->remote); - pair->state = CHECK_SUCCEEDED; - } - - linked_list_t *local_endpoints = checklist->is_initiator ? - checklist->initiator.endpoints : checklist->responder.endpoints; - - endpoint_notify_t *local_endpoint; - if (endpoints_contain(local_endpoints, - check->endpoint->get_host(check->endpoint), - &local_endpoint) != SUCCESS) - { - local_endpoint = endpoint_notify_create_from_host(PEER_REFLEXIVE, - check->endpoint->get_host(check->endpoint), pair->local); - local_endpoint->set_priority(local_endpoint, - check->endpoint->get_priority(check->endpoint)); - local_endpoints->insert_last(local_endpoints, local_endpoint); - } - - update_checklist_state(this, checklist); - - switch(checklist->state) - { - case CHECK_SUCCEEDED: - case CHECK_FAILED: - finish_checks(this, checklist); - break; - default: - break; - } - } - else - { - DBG1(DBG_IKE, "pair with id '%d' not found", check->mid); - } -} - -static void process_request(private_connect_manager_t *this, check_t *check, - check_list_t *checklist) -{ - linked_list_t *remote_endpoints = checklist->is_initiator ? - checklist->responder.endpoints : checklist->initiator.endpoints; - - endpoint_notify_t *peer_reflexive, *remote_endpoint; - peer_reflexive = endpoint_notify_create_from_host(PEER_REFLEXIVE, - check->src, NULL); - peer_reflexive->set_priority(peer_reflexive, - check->endpoint->get_priority(check->endpoint)); - - if (endpoints_contain(remote_endpoints, check->src, &remote_endpoint) != SUCCESS) - { - remote_endpoint = peer_reflexive->clone(peer_reflexive); - remote_endpoints->insert_last(remote_endpoints, remote_endpoint); - } - - endpoint_pair_t *pair; - if (get_pair_by_hosts(checklist->pairs, check->dst, check->src, - &pair) == SUCCESS) - { - switch(pair->state) - { - case CHECK_IN_PROGRESS: - /* prevent retransmissions */ - pair->retransmitted = ME_MAX_RETRANS; - /* FIXME: we should wait to the next rto to send the triggered - * check */ - /* fall-through */ - case CHECK_WAITING: - case CHECK_FAILED: - queue_triggered_check(this, checklist, pair); - break; - case CHECK_SUCCEEDED: - default: - break; - } - } - else - { - endpoint_notify_t *local_endpoint = endpoint_notify_create_from_host(HOST, check->dst, NULL); - - endpoint_notify_t *initiator = checklist->is_initiator ? local_endpoint : remote_endpoint; - endpoint_notify_t *responder = checklist->is_initiator ? remote_endpoint : local_endpoint; - - pair = endpoint_pair_create(initiator, responder, checklist->is_initiator); - pair->id = checklist->pairs->get_count(checklist->pairs) + 1; - - insert_pair_by_priority(checklist->pairs, pair); - - queue_triggered_check(this, checklist, pair); - - local_endpoint->destroy(local_endpoint); - } - - check_t *response = check_create(); - - response->mid = check->mid; - response->src = check->dst->clone(check->dst); - response->dst = check->src->clone(check->src); - response->connect_id = chunk_clone(check->connect_id); - response->endpoint = peer_reflexive; - - send_check(this, checklist, response, pair, FALSE); - - check_destroy(response); -} - -/** - * Implementation of connect_manager_t.process_check. - */ -static void process_check(private_connect_manager_t *this, message_t *message) -{ - if (message->parse_body(message, NULL, NULL) != SUCCESS) - { - DBG1(DBG_IKE, "%N %s with message ID %d processing failed", - exchange_type_names, message->get_exchange_type(message), - message->get_request(message) ? "request" : "response", - message->get_message_id(message)); - return; - } - - check_t *check = check_create(); - check->mid = message->get_message_id(message); - check->src = message->get_source(message); - check->src = check->src->clone(check->src); - check->dst = message->get_destination(message); - check->dst = check->dst->clone(check->dst); - - if (process_payloads(message, check) != SUCCESS) - { - DBG1(DBG_IKE, "invalid connectivity check %s received", - message->get_request(message) ? "request" : "response"); - check_destroy(check); - return; - } - - this->mutex->lock(this->mutex); - - check_list_t *checklist; - if (get_checklist_by_id(this, check->connect_id, &checklist) != SUCCESS) - { - DBG1(DBG_IKE, "checklist with id '%#B' not found", - &check->connect_id); - check_destroy(check); - this->mutex->unlock(this->mutex); - return; - } - - chunk_t sig = build_signature(this, checklist, check, FALSE); - if (!chunk_equals(sig, check->auth)) - { - DBG1(DBG_IKE, "connectivity check verification failed"); - check_destroy(check); - chunk_free(&sig); - this->mutex->unlock(this->mutex); - return; - } - chunk_free(&sig); - - if (message->get_request(message)) - { - process_request(this, check, checklist); - } - else - { - process_response(this, check, checklist); - } - - this->mutex->unlock(this->mutex); - - check_destroy(check); -} - -/** - * Implementation of connect_manager_t.check_and_register. - */ -static bool check_and_register(private_connect_manager_t *this, - identification_t *id, identification_t *peer_id, - ike_sa_id_t *mediated_sa) -{ - initiated_t *initiated; - bool already_there = TRUE; - - this->mutex->lock(this->mutex); - - if (get_initiated_by_ids(this, id, peer_id, &initiated) != SUCCESS) - { - DBG2(DBG_IKE, "registered waiting mediated connection with '%Y'", - peer_id); - initiated = initiated_create(id, peer_id); - this->initiated->insert_last(this->initiated, initiated); - already_there = FALSE; - } - - if (initiated->mediated->find_first(initiated->mediated, - (linked_list_match_t)mediated_sa->equals, - NULL, mediated_sa) != SUCCESS) - { - initiated->mediated->insert_last(initiated->mediated, - mediated_sa->clone(mediated_sa)); - } - - this->mutex->unlock(this->mutex); - - return already_there; -} - -/** - * Implementation of connect_manager_t.check_and_initiate. - */ -static void check_and_initiate(private_connect_manager_t *this, - ike_sa_id_t *mediation_sa, identification_t *id, - identification_t *peer_id) -{ - initiated_t *initiated; - - this->mutex->lock(this->mutex); - - if (get_initiated_by_ids(this, id, peer_id, &initiated) != SUCCESS) - { - DBG2(DBG_IKE, "no waiting mediated connections with '%Y'", peer_id); - this->mutex->unlock(this->mutex); - return; - } - - ike_sa_id_t *waiting_sa; - iterator_t *iterator = initiated->mediated->create_iterator( - initiated->mediated, TRUE); - while (iterator->iterate(iterator, (void**)&waiting_sa)) - { - job_t *job = (job_t*)reinitiate_mediation_job_create(mediation_sa, - waiting_sa); - charon->processor->queue_job(charon->processor, job); - } - iterator->destroy(iterator); - - this->mutex->unlock(this->mutex); -} - -/** - * Implementation of connect_manager_t.set_initiator_data. - */ -static status_t set_initiator_data(private_connect_manager_t *this, - identification_t *initiator, - identification_t *responder, - chunk_t connect_id, chunk_t key, - linked_list_t *endpoints, bool is_initiator) -{ - check_list_t *checklist; - - this->mutex->lock(this->mutex); - - if (get_checklist_by_id(this, connect_id, NULL) == SUCCESS) - { - DBG1(DBG_IKE, "checklist with id '%#B' already exists, aborting", - &connect_id); - this->mutex->unlock(this->mutex); - return FAILED; - } - - checklist = check_list_create(initiator, responder, connect_id, key, - endpoints, is_initiator); - this->checklists->insert_last(this->checklists, checklist); - - this->mutex->unlock(this->mutex); - - return SUCCESS; -} - -/** - * Implementation of connect_manager_t.set_responder_data. - */ -static status_t set_responder_data(private_connect_manager_t *this, - chunk_t connect_id, chunk_t key, - linked_list_t *endpoints) -{ - check_list_t *checklist; - - this->mutex->lock(this->mutex); - - if (get_checklist_by_id(this, connect_id, &checklist) != SUCCESS) - { - DBG1(DBG_IKE, "checklist with id '%#B' not found", - &connect_id); - this->mutex->unlock(this->mutex); - return NOT_FOUND; - } - - checklist->responder.key = chunk_clone(key); - checklist->responder.endpoints = endpoints->clone_offset(endpoints, - offsetof(endpoint_notify_t, clone)); - checklist->state = CHECK_WAITING; - - build_pairs(checklist); - - /* send the first check immediately */ - schedule_checks(this, checklist, 0); - - this->mutex->unlock(this->mutex); - - return SUCCESS; -} - -/** - * Implementation of connect_manager_t.stop_checks. - */ -static status_t stop_checks(private_connect_manager_t *this, chunk_t connect_id) -{ - check_list_t *checklist; - - this->mutex->lock(this->mutex); - - if (get_checklist_by_id(this, connect_id, &checklist) != SUCCESS) - { - DBG1(DBG_IKE, "checklist with id '%#B' not found", - &connect_id); - this->mutex->unlock(this->mutex); - return NOT_FOUND; - } - - DBG1(DBG_IKE, "removing checklist with id '%#B'", &connect_id); - - remove_checklist(this, checklist); - check_list_destroy(checklist); - - this->mutex->unlock(this->mutex); - - return SUCCESS; -} - -/** - * Implementation of connect_manager_t.destroy. - */ -static void destroy(private_connect_manager_t *this) -{ - this->mutex->lock(this->mutex); - - this->hasher->destroy(this->hasher); - this->checklists->destroy_function(this->checklists, (void*)check_list_destroy); - this->initiated->destroy_function(this->initiated, (void*)initiated_destroy); - - this->mutex->unlock(this->mutex); - this->mutex->destroy(this->mutex); - free(this); -} - -/* - * Described in header. - */ -connect_manager_t *connect_manager_create() -{ - private_connect_manager_t *this = malloc_thing(private_connect_manager_t); - - this->public.destroy = (void(*)(connect_manager_t*))destroy; - this->public.check_and_register = (bool(*)(connect_manager_t*,identification_t*,identification_t*,ike_sa_id_t*))check_and_register; - this->public.check_and_initiate = (void(*)(connect_manager_t*,ike_sa_id_t*,identification_t*,identification_t*))check_and_initiate; - this->public.set_initiator_data = (status_t(*)(connect_manager_t*,identification_t*,identification_t*,chunk_t,chunk_t,linked_list_t*,bool))set_initiator_data; - this->public.set_responder_data = (status_t(*)(connect_manager_t*,chunk_t,chunk_t,linked_list_t*))set_responder_data; - this->public.process_check = (void(*)(connect_manager_t*,message_t*))process_check; - this->public.stop_checks = (status_t(*)(connect_manager_t*,chunk_t))stop_checks; - - this->hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA1); - if (this->hasher == NULL) - { - DBG1(DBG_IKE, "unable to create connect manager, SHA1 not supported"); - free(this); - return NULL; - } - - this->checklists = linked_list_create(); - this->initiated = linked_list_create(); - - this->mutex = mutex_create(MUTEX_TYPE_DEFAULT); - - return (connect_manager_t*)this; -} |