/* * 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 . * * 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 #include #include #include #include #include #include #include #include /* 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, &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); hydra->processor->queue_job(hydra->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) != 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); hydra->processor->queue_job(hydra->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; }