diff options
author | Rene Mayrhofer <rene@mayrhofer.eu.org> | 2007-10-26 14:10:02 +0000 |
---|---|---|
committer | Rene Mayrhofer <rene@mayrhofer.eu.org> | 2007-10-26 14:10:02 +0000 |
commit | 49104abddf3d71d5abf5cf75dc7f95fa6c55fa63 (patch) | |
tree | 28f7a72e5dec4abf908fd7874bdab776281310bc /src/charon/sa | |
parent | 7b0305f59ddab9ea026b202a8c569912e5bf9a90 (diff) | |
download | vyos-strongswan-49104abddf3d71d5abf5cf75dc7f95fa6c55fa63.tar.gz vyos-strongswan-49104abddf3d71d5abf5cf75dc7f95fa6c55fa63.zip |
[svn-upgrade] Integrating new upstream version, strongswan (4.1.8)
Diffstat (limited to 'src/charon/sa')
23 files changed, 4009 insertions, 237 deletions
diff --git a/src/charon/sa/authenticators/eap_authenticator.h b/src/charon/sa/authenticators/eap_authenticator.h index ffa162343..64a3267d7 100644 --- a/src/charon/sa/authenticators/eap_authenticator.h +++ b/src/charon/sa/authenticators/eap_authenticator.h @@ -121,7 +121,7 @@ struct eap_authenticator_t { * After receiving an EAP message "in", the peer/server processes * the payload and creates a reply/subsequent request. * The server side always returns NEED_MORE if another EAP message - * is excepted from the client, SUCCESS if EAP exchange completed and + * is expected from the client, SUCCESS if EAP exchange completed and * "out" is EAP_SUCCES, or FAILED if the EAP exchange failed with * a EAP_FAILURE payload in "out". Anyway, a payload in "out" is always * created. diff --git a/src/charon/sa/authenticators/psk_authenticator.c b/src/charon/sa/authenticators/psk_authenticator.c index 37465d029..6b76088bb 100644 --- a/src/charon/sa/authenticators/psk_authenticator.c +++ b/src/charon/sa/authenticators/psk_authenticator.c @@ -124,7 +124,7 @@ static status_t verify(private_psk_authenticator_t *this, chunk_t ike_sa_init, auth_data = build_shared_key_signature(ike_sa_init, my_nonce, shared_key, other_id, this->ike_sa->get_skp_verify(this->ike_sa), this->ike_sa->get_prf(this->ike_sa)); - chunk_free(&shared_key); + chunk_free_randomized(&shared_key); recv_auth_data = auth_payload->get_data(auth_payload); if (auth_data.len != recv_auth_data.len || @@ -168,7 +168,7 @@ static status_t build(private_psk_authenticator_t *this, chunk_t ike_sa_init, my_id, this->ike_sa->get_skp_build(this->ike_sa), this->ike_sa->get_prf(this->ike_sa)); DBG2(DBG_IKE, "successfully created shared key MAC"); - chunk_free(&shared_key); + chunk_free_randomized(&shared_key); *auth_payload = auth_payload_create(); (*auth_payload)->set_auth_method(*auth_payload, AUTH_PSK); (*auth_payload)->set_data(*auth_payload, auth_data); diff --git a/src/charon/sa/authenticators/rsa_authenticator.c b/src/charon/sa/authenticators/rsa_authenticator.c index e5c5cd60e..ba0fad1e3 100644 --- a/src/charon/sa/authenticators/rsa_authenticator.c +++ b/src/charon/sa/authenticators/rsa_authenticator.c @@ -93,19 +93,16 @@ static status_t verify(private_rsa_authenticator_t *this, chunk_t ike_sa_init, static status_t build(private_rsa_authenticator_t *this, chunk_t ike_sa_init, chunk_t other_nonce, auth_payload_t **auth_payload) { - chunk_t chunk; - chunk_t octets; - chunk_t auth_data; + chunk_t octets, auth_data; status_t status; rsa_public_key_t *my_pubkey; - rsa_private_key_t *my_key; identification_t *my_id; prf_t *prf; my_id = this->ike_sa->get_my_id(this->ike_sa); DBG1(DBG_IKE, "authentication of '%D' (myself) with %N", my_id, auth_method_names, AUTH_RSA); - DBG2(DBG_IKE, "looking for RSA public key belonging to '%D'", my_id); + DBG2(DBG_IKE, "looking for RSA public key belonging to '%D'...", my_id); my_pubkey = charon->credentials->get_rsa_public_key(charon->credentials, my_id); if (my_pubkey == NULL) @@ -113,28 +110,18 @@ static status_t build(private_rsa_authenticator_t *this, chunk_t ike_sa_init, DBG1(DBG_IKE, "no RSA public key found for '%D'", my_id); return NOT_FOUND; } - DBG2(DBG_IKE, "matching RSA public key found"); - chunk = my_pubkey->get_keyid(my_pubkey); - DBG2(DBG_IKE, "looking for RSA private key with keyid %#B", &chunk); - my_key = charon->credentials->get_rsa_private_key(charon->credentials, my_pubkey); - if (my_key == NULL) - { - DBG1(DBG_IKE, "no RSA private key found with for %D with keyid %#B", - my_id, &chunk); - return NOT_FOUND; - } - DBG2(DBG_IKE, "matching RSA private key found"); + DBG2(DBG_IKE, " matching RSA public key found"); prf = this->ike_sa->get_prf(this->ike_sa); prf->set_key(prf, this->ike_sa->get_skp_build(this->ike_sa)); octets = build_tbs_octets(ike_sa_init, other_nonce, my_id, prf); - status = my_key->build_emsa_pkcs1_signature(my_key, HASH_SHA1, octets, &auth_data); + status = charon->credentials->rsa_signature(charon->credentials, + my_pubkey, HASH_SHA1, octets, &auth_data); chunk_free(&octets); if (status != SUCCESS) { - my_key->destroy(my_key); - DBG1(DBG_IKE, "build signature of SHA1 hash failed"); + DBG1(DBG_IKE, "building RSA signature with SHA-1 hash failed"); return status; } DBG2(DBG_IKE, "successfully signed with RSA private key"); @@ -142,8 +129,6 @@ static status_t build(private_rsa_authenticator_t *this, chunk_t ike_sa_init, *auth_payload = auth_payload_create(); (*auth_payload)->set_auth_method(*auth_payload, AUTH_RSA); (*auth_payload)->set_data(*auth_payload, auth_data); - - my_key->destroy(my_key); chunk_free(&auth_data); return SUCCESS; } diff --git a/src/charon/sa/child_sa.c b/src/charon/sa/child_sa.c index 118af3b30..44f0298d5 100644 --- a/src/charon/sa/child_sa.c +++ b/src/charon/sa/child_sa.c @@ -832,6 +832,16 @@ static status_t update_hosts(private_child_sa_t *this, { policy->other_ts->set_address(policy->other_ts, other); } + + /* we reinstall the virtual IP to handle interface romaing + * correctly */ + if (this->virtual_ip) + { + charon->kernel_interface->del_ip(charon->kernel_interface, + this->virtual_ip); + charon->kernel_interface->add_ip(charon->kernel_interface, + this->virtual_ip, me); + } /* reinstall updated policies */ charon->kernel_interface->add_policy(charon->kernel_interface, diff --git a/src/charon/sa/connect_manager.c b/src/charon/sa/connect_manager.c new file mode 100644 index 000000000..d583e01bb --- /dev/null +++ b/src/charon/sa/connect_manager.c @@ -0,0 +1,1615 @@ +/** + * @file connect_manager.c + * + * @brief Implementation of connect_manager_t. + * + */ + +/* + * Copyright (C) 2007 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 <pthread.h> +#include <math.h> + +#include <daemon.h> +#include <utils/linked_list.h> + +#include <processing/jobs/callback_job.h> +#include <processing/jobs/initiate_mediation_job.h> +#include <encoding/payloads/endpoint_notify.h> + +// base timeout +// the sending interval is P2P_INTERVAL * active checklists (N) +// retransmission timeout is P2P_INTERVAL * N * checks in waiting state (NW) +#define P2P_INTERVAL 20 // 20 ms +// min retransmission timeout (RTO is P2P_INTERVAL * N * checks in waiting state) +#define P2P_RTO_MIN 100 // 100 ms +// max number of retransmissions (+ the initial check) +#define P2P_MAX_RETRANS 2 + + +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. + */ + pthread_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 session 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; + + /** session id */ + chunk_t session_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; + +}; + +/** + * 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->session_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); + DESTROY_IF(this->triggered); // this list contains some of the same elements as contained in this->pairs + + free(this); +} + +/** + * Creates a new checklist + */ +static check_list_t *check_list_create(identification_t *initiator, identification_t *responder, + chunk_t session_id, chunk_t initiator_key, linked_list_t *initiator_endpoints, + bool is_initiator) +{ + check_list_t *this = malloc_thing(check_list_t); + + this->session_id = chunk_clone(session_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; + + return this; +} + + +typedef struct waiting_sa_t waiting_sa_t; + +/** + * For an initiator, the data stored about a waiting mediated sa + */ +struct waiting_sa_t { + /** ike sa id */ + ike_sa_id_t *ike_sa_id; + + /** list of child_cfg_t */ + linked_list_t *childs; +}; + +/** + * Destroys a queued mediated sa + */ +static void waiting_sa_destroy(waiting_sa_t *this) +{ + DESTROY_IF(this->ike_sa_id); + this->childs->destroy_offset(this->childs, offsetof(child_cfg_t, destroy)); + free(this); +} + +/** + * Creates a new mediated sa object + */ +static waiting_sa_t *waiting_sa_create(ike_sa_id_t *ike_sa_id) +{ + waiting_sa_t *this = malloc_thing(waiting_sa_t); + + this->ike_sa_id = ike_sa_id->clone(ike_sa_id); + this->childs = linked_list_create(); + + 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_function(this->mediated, (void*)waiting_sa_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; + + /** session id */ + chunk_t session_id; + + /** endpoint */ + endpoint_notify_t *endpoint; + + /** raw endpoint payload (to verify the signature) */ + chunk_t endpoint_raw; + + /** cookie */ + chunk_t cookie; +}; + +/** + * Destroys a connectivity check + */ +static void check_destroy(check_t *this) +{ + chunk_free(&this->session_id); + chunk_free(&this->endpoint_raw); + chunk_free(&this->cookie); + DESTROY_IF(this->endpoint); + free(this); +} + +/** + * Creates a new connectivity check + */ +static check_t *check_create() +{ + check_t *this = malloc_thing(check_t); + + this->session_id = chunk_empty; + this->cookie = chunk_empty; + this->endpoint_raw = chunk_empty; + this->endpoint = NULL; + + this->mid = 0; + + return this; +} + +typedef struct sender_data_t sender_data_t; + +/** + * Data required by the sender + */ +struct sender_data_t { + /** connect manager */ + private_connect_manager_t *connect_manager; + + /** session id */ + chunk_t session_id; +}; + +/** + * Destroys a sender data object + */ +static void sender_data_destroy(sender_data_t *this) +{ + chunk_free(&this->session_id); + free(this); +} + +/** + * Creates a new sender data object + */ +static sender_data_t *sender_data_create(private_connect_manager_t *connect_manager, chunk_t session_id) +{ + sender_data_t *this = malloc_thing(sender_data_t); + this->connect_manager = connect_manager; + this->session_id = session_id; + return this; +} + +typedef struct retransmit_data_t retransmit_data_t; + +/** + * Data required by the retransmission job + */ +struct retransmit_data_t { + /** connect manager */ + private_connect_manager_t *connect_manager; + + /** session id */ + chunk_t session_id; + + /** message (pair) id */ + u_int32_t mid; +}; + +/** + * Destroys a retransmission data object + */ +static void retransmit_data_destroy(retransmit_data_t *this) +{ + chunk_free(&this->session_id); + free(this); +} + +/** + * Creates a new retransmission data object + */ +static retransmit_data_t *retransmit_data_create(private_connect_manager_t *connect_manager, + chunk_t session_id, u_int32_t mid) +{ + retransmit_data_t *this = malloc_thing(retransmit_data_t); + + this->connect_manager = connect_manager; + this->session_id = session_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 status_t get_initiated_by_ids(private_connect_manager_t *this, + identification_t *id, identification_t *peer_id, initiated_t **initiated) +{ + iterator_t *iterator; + initiated_t *current; + status_t status = NOT_FOUND; + + iterator = this->initiated->create_iterator(this->initiated, TRUE); + while (iterator->iterate(iterator, (void**)¤t)) + { + if (id->equals(id, current->id) && peer_id->equals(peer_id, current->peer_id)) + { + if (initiated) + { + *initiated = current; + } + status = SUCCESS; + break; + } + } + iterator->destroy(iterator); + + return status; +} + +/** + * 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); +} + +/** + * Finds a waiting sa + */ +static status_t get_waiting_sa(initiated_t *initiated, ike_sa_id_t *ike_sa_id, waiting_sa_t **waiting_sa) +{ + iterator_t *iterator; + waiting_sa_t *current; + status_t status = NOT_FOUND; + + iterator = initiated->mediated->create_iterator(initiated->mediated, TRUE); + while (iterator->iterate(iterator, (void**)¤t)) + { + if (ike_sa_id->equals(ike_sa_id, current->ike_sa_id)) + { + if (waiting_sa) + { + *waiting_sa = current; + } + status = SUCCESS; + break; + } + } + iterator->destroy(iterator); + + return status; +} + +/** + * Find the checklist with a specific session ID + */ +static status_t get_checklist_by_id(private_connect_manager_t *this, + chunk_t session_id, check_list_t **check_list) +{ + iterator_t *iterator; + check_list_t *current; + status_t status = NOT_FOUND; + + iterator = this->checklists->create_iterator(this->checklists, TRUE); + while (iterator->iterate(iterator, (void**)¤t)) + { + if (chunk_equals(session_id, current->session_id)) + { + if (check_list) + { + *check_list = current; + } + status = SUCCESS; + break; + } + } + iterator->destroy(iterator); + + return status; +} + +/** + * 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 status_t endpoints_contain(linked_list_t *endpoints, host_t *host, endpoint_notify_t **endpoint) +{ + iterator_t *iterator; + endpoint_notify_t *current; + status_t status = NOT_FOUND; + + iterator = endpoints->create_iterator(endpoints, TRUE); + while (iterator->iterate(iterator, (void**)¤t)) + { + if (host->equals(host, current->get_host(current))) + { + if (endpoint) + { + *endpoint = current; + } + status = SUCCESS; + break; + } + } + iterator->destroy(iterator); + + return status; +} + +// ----------------------------------------------------------------------------- + + +/** + * Updates the state of the whole checklist + */ +static void update_checklist_state(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; + } + } + iterator->destroy(iterator); + + if (in_progress) + { + checklist->state = CHECK_IN_PROGRESS; + } + else if (succeeded) + { + checklist->state = CHECK_SUCCEEDED; + } + else + { + checklist->state = CHECK_FAILED; + } +} + +/** + * Inserts an endpoint pair into the 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 status_t get_pair_by_hosts(linked_list_t *pairs, host_t *local, host_t *remote, endpoint_pair_t **pair) +{ + iterator_t *iterator; + endpoint_pair_t *current; + status_t status = NOT_FOUND; + + iterator = pairs->create_iterator(pairs, TRUE); + while (iterator->iterate(iterator, (void**)¤t)) + { + if (local->equals(local, current->local) && + remote->equals(remote, current->remote)) + { + if (pair) + { + *pair = current; + } + status = SUCCESS; + break; + } + } + iterator->destroy(iterator); + + return status; +} + +/** + * 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) +{ + iterator_t *iterator; + endpoint_pair_t *current; + status_t status = NOT_FOUND; + + iterator = checklist->pairs->create_iterator(checklist->pairs, TRUE); + while (iterator->iterate(iterator, (void**)¤t)) + { + if (current->id == id) + { + if (pair) + { + *pair = current; + } + status = SUCCESS; + break; + } + } + iterator->destroy(iterator); + + return status; +} + +/** + * 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) +{ + iterator_t *iterator; + endpoint_pair_t *current; + status_t status = NOT_FOUND; + + iterator = checklist->pairs->create_iterator(checklist->pairs, TRUE); + while (iterator->iterate(iterator, (void**)¤t)) + { + if (current->state == CHECK_SUCCEEDED) + { + if (pair) + { + *pair = current; + } + status = SUCCESS; + break; + } + } + iterator->destroy(iterator); + + return status; +} + +/** + * 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; +} + +/** + * 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; + bool inserted = FALSE; + 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) +{ + 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); + + 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) +{ + iterator_t *iterator; + payload_t *payload; + + iterator = message->get_payload_iterator(message); + while (iterator->iterate(iterator, (void**)&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 P2P_ENDPOINT: + { + if (check->endpoint) + { + DBG1(DBG_IKE, "connectivity check contains multiple P2P_ENDPOINT notifies"); + break; + } + + endpoint_notify_t *endpoint = endpoint_notify_create_from_payload(notify); + if (!endpoint) + { + DBG1(DBG_IKE, "received invalid P2P_ENDPOINT notify"); + break; + } + check->endpoint = endpoint; + check->endpoint_raw = chunk_clone(notify->get_notification_data(notify)); + DBG3(DBG_IKE, "received P2P_ENDPOINT notify"); + break; + } + case P2P_SESSIONID: + { + if (check->session_id.ptr) + { + DBG1(DBG_IKE, "connectivity check contains multiple P2P_SESSIONID notifies"); + break; + } + check->session_id = chunk_clone(notify->get_notification_data(notify)); + DBG3(DBG_IKE, "received p2p_sessionid %B", &check->session_id); + break; + } + case COOKIE: + { + if (check->cookie.ptr) + { + DBG1(DBG_IKE, "connectivity check contains multiple COOKIE notifies"); + break; + } + check->cookie = chunk_clone(notify->get_notification_data(notify)); + DBG3(DBG_IKE, "received cookie %B", &check->cookie); + break; + } + default: + break; + } + } + iterator->destroy(iterator); + + if (!check->session_id.ptr || !check->endpoint || !check->cookie.ptr) + { + DBG1(DBG_IKE, "at least one 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) +{ + chunk_t mid_chunk, key_chunk, sig_chunk; + chunk_t sig_hash; + + mid_chunk = chunk_from_thing(check->mid); + + key_chunk = (checklist->is_initiator && outbound) || (!checklist->is_initiator && !outbound) + ? checklist->initiator.key : checklist->responder.key; + + /* signature = SHA1( MID | P2P_SESSIONID | P2P_ENDPOINT | P2P_SESSIONKEY ) */ + sig_chunk = chunk_cat("cccc", mid_chunk, check->session_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; +} + +// ----------------------------------------------------------------------------- + +// forward declarations +static void queue_retransmission(private_connect_manager_t *this, chunk_t session_id, u_int32_t mid); +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); + +/** + * This function is triggered for each sent check after a specific timeout + */ +static job_requeue_t retransmit(retransmit_data_t *data) +{ + private_connect_manager_t *this = data->connect_manager; + + pthread_mutex_lock(&(this->mutex)); + + check_list_t *checklist; + if (get_checklist_by_id(this, data->session_id, &checklist) != SUCCESS) + { + DBG1(DBG_IKE, "checklist with id '%B' not found, can't retransmit connectivity check", + &data->session_id); + pthread_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 >= P2P_MAX_RETRANS) + { + DBG2(DBG_IKE, "pair with id '%d' failed after %d tries", + data->mid, pair->retransmitted); + pair->state = CHECK_FAILED; + goto retransmit_end; + } + + charon->sender->send(charon->sender, pair->packet->clone(pair->packet)); + + queue_retransmission(this, checklist->session_id, pair->id); + +retransmit_end: + update_checklist_state(checklist); + + switch(checklist->state) + { + case CHECK_SUCCEEDED: + case CHECK_FAILED: + finish_checks(this, checklist); + break; + } + + pthread_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, chunk_t session_id, u_int32_t mid) +{ + retransmit_data_t *data = retransmit_data_create(this, chunk_clone(session_id), mid); + job_t *job = (job_t*)callback_job_create((callback_job_cb_t)retransmit, data, (callback_job_cleanup_t)retransmit_data_destroy, NULL); + charon->scheduler->schedule_job(charon->scheduler, (job_t*)job, P2P_RTO_MIN); +} + +/** + * 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)); + + message->set_ike_sa_id(message, ike_sa_id_create(0, 0, request)); + + message->add_notify(message, FALSE, P2P_SESSIONID, check->session_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); + + check->cookie = build_signature(this, checklist, check, TRUE); + message->add_notify(message, FALSE, COOKIE, check->cookie); + + 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; + queue_retransmission(this, checklist->session_id, pair->id); + } + else + { + packet->destroy(packet); + } + } +} + +/** + * Queues a triggered check + */ +static void queue_triggered_check(check_list_t *checklist, endpoint_pair_t *pair) +{ + pair->state = CHECK_WAITING; + checklist->triggered->insert_last(checklist->triggered, pair); +} + +/** + * This function is triggered for each checklist at a specific interval + */ +static job_requeue_t sender(sender_data_t *data) +{ + private_connect_manager_t *this = data->connect_manager; + + pthread_mutex_lock(&(this->mutex)); + + check_list_t *checklist; + if (get_checklist_by_id(this, data->session_id, &checklist) != SUCCESS) + { + DBG1(DBG_IKE, "checklist with id '%B' not found, can't send connectivity check", + &data->session_id); + pthread_mutex_unlock(&(this->mutex)); + return JOB_REQUEUE_NONE; + } + + endpoint_pair_t *pair; + if (get_triggered_pair(checklist, &pair) != SUCCESS) + { + DBG1(DBG_IKE, "no triggered check queued, sending an ordinary check"); + + iterator_t *iterator; + bool found_one = FALSE; + + iterator = checklist->pairs->create_iterator(checklist->pairs, TRUE); + while (iterator->iterate(iterator, (void**)&pair)) + { + if (pair->state == CHECK_WAITING) + { + found_one = TRUE; + break; + } + } + iterator->destroy(iterator); + + if (!found_one) + { + pthread_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->session_id = chunk_clone(checklist->session_id); + check->endpoint = endpoint_notify_create(); + + pair->state = CHECK_IN_PROGRESS; + + send_check(this, checklist, check, pair, TRUE); + + check_destroy(check); + + // schedule this job again + u_int32_t N = this->checklists->get_count(this->checklists); + schedule_checks(this, checklist, P2P_INTERVAL * N); + + pthread_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) +{ + chunk_t session_id = chunk_clone(checklist->session_id); + sender_data_t *data = sender_data_create(this, session_id); + job_t *job = (job_t*)callback_job_create((callback_job_cb_t)sender, data, (callback_job_cleanup_t)sender_data_destroy, NULL); + charon->scheduler->schedule_job(charon->scheduler, job, 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) + { + waiting_sa_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->ike_sa_id); + if (sa->initiate_mediated(sa, pair->local, pair->remote, waiting_sa->childs) != SUCCESS) + { + SIG(IKE_UP_FAILED, "establishing the 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 + } +} + +/** + * 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 '%D' " + "and '%D'", checklist->initiator.id, checklist->responder.id); + } + } + + //remove_checklist(this, checklist); + //check_list_destroy(checklist); + // FIXME: we should do this ^^^ after a specific timeout on the responder side +} + +/** + * 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(checklist); + + switch(checklist->state) + { + case CHECK_SUCCEEDED: + case CHECK_FAILED: + finish_checks(this, checklist); + 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: + pair->retransmitted = P2P_MAX_RETRANS; // prevent retransmissions + // FIXME: we should wait to the next rto to send the triggered check + // fall-through + case CHECK_WAITING: + case CHECK_FAILED: + queue_triggered_check(checklist, pair); + break; + case CHECK_SUCCEEDED: + default: + // do nothing + 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(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->session_id = chunk_clone(check->session_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->dst = message->get_destination(message); + + if (process_payloads(message, check) != SUCCESS) + { + DBG1(DBG_IKE, "invalid connectivity check %s received", + message->get_request(message) ? "request" : "response"); + check_destroy(check); + return; + } + + pthread_mutex_lock(&(this->mutex)); + + check_list_t *checklist; + if (get_checklist_by_id(this, check->session_id, &checklist) != SUCCESS) + { + DBG1(DBG_IKE, "checklist with id '%B' not found", + &check->session_id); + check_destroy(check); + pthread_mutex_unlock(&(this->mutex)); + return; + } + + chunk_t sig = build_signature(this, checklist, check, FALSE); + if (!chunk_equals(sig, check->cookie)) + { + DBG1(DBG_IKE, "connectivity check verification failed"); + check_destroy(check); + chunk_free(&sig); + pthread_mutex_unlock(&(this->mutex)); + return; + } + chunk_free(&sig); + + if (message->get_request(message)) + { + process_request(this, check, checklist); + } + else + { + process_response(this, check, checklist); + } + + pthread_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, child_cfg_t *child) +{ + initiated_t *initiated; + bool already_there = TRUE; + + pthread_mutex_lock(&(this->mutex)); + + if (get_initiated_by_ids(this, id, peer_id, &initiated) != SUCCESS) + { + DBG2(DBG_IKE, "registered waiting mediated connection with '%D'", peer_id); + initiated = initiated_create(id, peer_id); + this->initiated->insert_last(this->initiated, initiated); + already_there = FALSE; + } + + waiting_sa_t *waiting_sa; + if (get_waiting_sa(initiated, mediated_sa, &waiting_sa) != SUCCESS) + { + waiting_sa = waiting_sa_create(mediated_sa); + initiated->mediated->insert_last(initiated->mediated, waiting_sa); + } + + child->get_ref(child); + waiting_sa->childs->insert_last(waiting_sa->childs, child); + + pthread_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; + + pthread_mutex_lock(&(this->mutex)); + + if (get_initiated_by_ids(this, id, peer_id, &initiated) != SUCCESS) + { + DBG2(DBG_IKE, "no waiting mediated connections with '%D'", peer_id); + pthread_mutex_unlock(&(this->mutex)); + return; + } + + waiting_sa_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->ike_sa_id); + charon->processor->queue_job(charon->processor, job); + } + + pthread_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 session_id, chunk_t key, linked_list_t *endpoints, bool is_initiator) +{ + check_list_t *checklist; + + pthread_mutex_lock(&(this->mutex)); + + if (get_checklist_by_id(this, session_id, NULL) == SUCCESS) + { + DBG1(DBG_IKE, "checklist with id '%B' already exists, aborting", + &session_id); + pthread_mutex_unlock(&(this->mutex)); + return FAILED; + } + + checklist = check_list_create(initiator, responder, session_id, key, endpoints, is_initiator); + this->checklists->insert_last(this->checklists, checklist); + + pthread_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 session_id, chunk_t key, linked_list_t *endpoints) +{ + check_list_t *checklist; + + pthread_mutex_lock(&(this->mutex)); + + if (get_checklist_by_id(this, session_id, &checklist) != SUCCESS) + { + DBG1(DBG_IKE, "checklist with id '%B' not found", + &session_id); + pthread_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); + + schedule_checks(this, checklist, 0); // send the first check immediately + + pthread_mutex_unlock(&(this->mutex)); + + return SUCCESS; +} + +/** + * Implementation of connect_manager_t.destroy. + */ +static void destroy(private_connect_manager_t *this) +{ + pthread_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); + + pthread_mutex_unlock(&(this->mutex)); + pthread_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*,child_cfg_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->hasher = hasher_create(HASH_SHA1); + this->checklists = linked_list_create(); + this->initiated = linked_list_create(); + + pthread_mutex_init(&(this->mutex), NULL); + + return (connect_manager_t*)this; +} diff --git a/src/charon/sa/connect_manager.h b/src/charon/sa/connect_manager.h new file mode 100644 index 000000000..2f3e9109b --- /dev/null +++ b/src/charon/sa/connect_manager.h @@ -0,0 +1,131 @@ +/** + * @file connect_manager.h + * + * @brief Interface of connect_manager_t. + * + */ + +/* + * Copyright (C) 2007 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. + */ + +#ifndef CONNECT_MANAGER_H_ +#define CONNECT_MANAGER_H_ + +typedef struct connect_manager_t connect_manager_t; + +#include <encoding/message.h> +#include <config/child_cfg.h> +#include <sa/ike_sa_id.h> +#include <utils/identification.h> + +/** + * @brief The connection manager is responsible for establishing a direct + * connection with another peer. + * + * @b Constructors: + * - connect_manager_create() + * + * @ingroup sa + */ +struct connect_manager_t { + + /** + * @brief Checks if a there is already a mediated connection registered + * between two peers. + * + * @param this the manager object + * @param id my id + * @param peer_id the other peer's id + * @param mediated_sa the IKE_SA ID of the mediated connection + * @param child the CHILD_SA config of the mediated connection + * @returns + * - TRUE, if there was already a mediated connection registered + * - FALSE, otherwise + */ + bool (*check_and_register) (connect_manager_t *this, + identification_t *id, identification_t *peer_id, + ike_sa_id_t *mediated_sa, child_cfg_t *child); + + /** + * @brief Checks if there are waiting connections with a specific peer. + * If so, reinitiate them. + * + * @param this the manager object + * @param id my id + * @param peer_id the other peer's id + */ + void (*check_and_initiate) (connect_manager_t *this, ike_sa_id_t *mediation_sa, + identification_t *id, identification_t *peer_id); + + /** + * @brief Creates a checklist and sets the initiator's data. + * + * @param this the manager object + * @param initiator ID of the initiator + * @param responder ID of the responder + * @param session_id the session ID provided by the initiator + * @param key the initiator's key + * @param endpoints the initiator's endpoints + * @param is_initiator TRUE, if the caller of this method is the initiator + * FALSE, otherwise + * @returns + * SUCCESS + */ + status_t (*set_initiator_data) (connect_manager_t *this, + identification_t *initiator, identification_t *responder, + chunk_t session_id, chunk_t key, linked_list_t *endpoints, bool is_initiator); + + /** + * @brief Updates a checklist and sets the responder's data. The checklist's + * state is advanced to WAITING which means that checks will be sent. + * + * @param this the manager object + * @param session_id the session ID + * @param chunk_t the responder's key + * @param endpoints the responder's endpoints + * @returns + * - NOT_FOUND, if the checklist has not been found + * - SUCCESS, otherwise + */ + status_t (*set_responder_data) (connect_manager_t *this, + chunk_t session_id, chunk_t key, linked_list_t *endpoints); + + + /** + * @brief Processes a connectivity check + * + * @param this the manager object + * @param message the received message + */ + void (*process_check) (connect_manager_t *this, message_t *message); + + /** + * @brief Destroys the manager with all data. + * + * @param this the manager object + */ + void (*destroy) (connect_manager_t *this); +}; + +/** + * @brief Create a manager. + * + * @returns connect_manager_t object + * + * @ingroup sa + */ +connect_manager_t *connect_manager_create(void); + +#endif /*CONNECT_MANAGER_H_*/ diff --git a/src/charon/sa/ike_sa.c b/src/charon/sa/ike_sa.c index 0a996329d..9d7a17e89 100644 --- a/src/charon/sa/ike_sa.c +++ b/src/charon/sa/ike_sa.c @@ -6,7 +6,8 @@ */ /* - * Copyright (C) 2006 Tobias Brunner, Daniel Roethlisberger + * Copyright (C) 2006-2007 Tobias Brunner + * Copyright (C) 2006 Daniel Roethlisberger * Copyright (C) 2005-2006 Martin Willi * Copyright (C) 2005 Jan Hutter * Hochschule fuer Technik Rapperswil @@ -65,6 +66,9 @@ #include <processing/jobs/send_keepalive_job.h> #include <processing/jobs/rekey_ike_sa_job.h> +#ifdef P2P +#include <sa/tasks/ike_p2p.h> +#endif #ifndef RESOLV_CONF #define RESOLV_CONF "/etc/resolv.conf" @@ -130,6 +134,13 @@ struct private_ike_sa_t { */ host_t *other_host; +#ifdef P2P + /** + * Server reflexive host + */ + host_t *server_reflexive_host; +#endif /* P2P */ + /** * Identification used for us */ @@ -495,6 +506,10 @@ static void set_condition(private_ike_sa_t *this, ike_condition_t condition, DBG1(DBG_IKE, "remote host is behind NAT"); this->conditions |= COND_NAT_ANY; break; + case COND_NAT_FAKE: + DBG1(DBG_IKE, "faking NAT situation to enforce UDP encapsulation"); + this->conditions |= COND_NAT_ANY; + break; default: break; } @@ -508,10 +523,12 @@ static void set_condition(private_ike_sa_t *this, ike_condition_t condition, DBG1(DBG_IKE, "new route to %H found", this->other_host); break; case COND_NAT_HERE: + case COND_NAT_FAKE: case COND_NAT_THERE: set_condition(this, COND_NAT_ANY, has_condition(this, COND_NAT_HERE) || - has_condition(this, COND_NAT_THERE)); + has_condition(this, COND_NAT_THERE) || + has_condition(this, COND_NAT_FAKE)); break; default: break; @@ -581,7 +598,8 @@ static ike_sa_state_t get_state(private_ike_sa_t *this) */ static void set_state(private_ike_sa_t *this, ike_sa_state_t state) { - DBG1(DBG_IKE, "IKE_SA state change: %N => %N", + DBG1(DBG_IKE, "IKE_SA '%s' state change: %N => %N", + get_name(this), ike_sa_state_names, this->state, ike_sa_state_names, state); @@ -663,14 +681,14 @@ static void set_virtual_ip(private_ike_sa_t *this, bool local, host_t *ip) { if (local) { - DBG1(DBG_IKE, "installing new virtual IP %H", ip); if (this->my_virtual_ip) - { + { DBG1(DBG_IKE, "removing old virtual IP %H", this->my_virtual_ip); charon->kernel_interface->del_ip(charon->kernel_interface, this->my_virtual_ip); this->my_virtual_ip->destroy(this->my_virtual_ip); } + DBG1(DBG_IKE, "installing new virtual IP %H", ip); if (charon->kernel_interface->add_ip(charon->kernel_interface, ip, this->my_host) == SUCCESS) { @@ -812,8 +830,6 @@ static status_t generate_message(private_ike_sa_t *this, message_t *message, { this->time.outbound = time(NULL); message->set_ike_sa_id(message, this->ike_sa_id); - message->set_destination(message, this->other_host->clone(this->other_host)); - message->set_source(message, this->my_host->clone(this->my_host)); return message->generate(message, this->crypter_out, this->signer_out, packet); } @@ -850,102 +866,91 @@ static void send_notify_response(private_ike_sa_t *this, message_t *request, response->destroy(response); } +#ifdef P2P /** - * Implementation of ike_sa_t.process_message. + * Implementation of ike_sa_t.get_server_reflexive_host. */ -static status_t process_message(private_ike_sa_t *this, message_t *message) +static host_t *get_server_reflexive_host(private_ike_sa_t *this) { - status_t status; - bool is_request; - - is_request = message->get_request(message); + return this->server_reflexive_host; +} + +/** + * Implementation of ike_sa_t.set_server_reflexive_host. + */ +static void set_server_reflexive_host(private_ike_sa_t *this, host_t *host) +{ + DESTROY_IF(this->server_reflexive_host); + this->server_reflexive_host = host; +} + +/** + * Implementation of ike_sa_t.respond + */ +static status_t respond(private_ike_sa_t *this, identification_t *peer_id, + chunk_t session_id) +{ + ike_p2p_t *task = ike_p2p_create(&this->public, TRUE); + task->respond(task, peer_id, session_id); + this->task_manager->queue_task(this->task_manager, (task_t*)task); + return this->task_manager->initiate(this->task_manager); +} + +/** + * Implementation of ike_sa_t.callback + */ +static status_t callback(private_ike_sa_t *this, identification_t *peer_id) +{ + ike_p2p_t *task = ike_p2p_create(&this->public, TRUE); + task->callback(task, peer_id); + this->task_manager->queue_task(this->task_manager, (task_t*)task); + return this->task_manager->initiate(this->task_manager); +} + +/** + * Implementation of ike_sa_t.relay + */ +static status_t relay(private_ike_sa_t *this, identification_t *requester, + chunk_t session_id, chunk_t session_key, linked_list_t *endpoints, bool response) +{ + ike_p2p_t *task = ike_p2p_create(&this->public, TRUE); + task->relay(task, requester, session_id, session_key, endpoints, response); + this->task_manager->queue_task(this->task_manager, (task_t*)task); + return this->task_manager->initiate(this->task_manager); +} + +/** + * Implementation of ike_sa_t.initiate_mediation + */ +static status_t initiate_mediation(private_ike_sa_t *this, peer_cfg_t *mediated_cfg) +{ + ike_p2p_t *task = ike_p2p_create(&this->public, TRUE); + task->connect(task, mediated_cfg->get_peer_id(mediated_cfg)); + this->task_manager->queue_task(this->task_manager, (task_t*)task); + return this->task_manager->initiate(this->task_manager); +} + +/** + * Implementation of ike_sa_t.initiate_mediated + */ +static status_t initiate_mediated(private_ike_sa_t *this, host_t *me, host_t *other, + linked_list_t *childs) +{ + this->my_host = me->clone(me); + this->other_host = other->clone(other); - status = message->parse_body(message, this->crypter_in, this->signer_in); - if (status != SUCCESS) - { - - if (is_request) - { - switch (status) - { - case NOT_SUPPORTED: - DBG1(DBG_IKE, "ciritcal unknown payloads found"); - if (is_request) - { - send_notify_response(this, message, UNSUPPORTED_CRITICAL_PAYLOAD); - } - break; - case PARSE_ERROR: - DBG1(DBG_IKE, "message parsing failed"); - if (is_request) - { - send_notify_response(this, message, INVALID_SYNTAX); - } - break; - case VERIFY_ERROR: - DBG1(DBG_IKE, "message verification failed"); - if (is_request) - { - send_notify_response(this, message, INVALID_SYNTAX); - } - break; - case FAILED: - DBG1(DBG_IKE, "integrity check failed"); - /* ignored */ - break; - case INVALID_STATE: - DBG1(DBG_IKE, "found encrypted message, but no keys available"); - if (is_request) - { - send_notify_response(this, message, INVALID_SYNTAX); - } - default: - break; - } - } - 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 status; - } - else + task_t *task; + child_cfg_t *child_cfg; + iterator_t *iterator = childs->create_iterator(childs, TRUE); + while (iterator->iterate(iterator, (void**)&child_cfg)) { - host_t *me, *other; - - me = message->get_destination(message); - other = message->get_source(message); - - /* if this IKE_SA is virgin, we check for a config */ - if (this->ike_cfg == NULL) - { - job_t *job; - this->ike_cfg = charon->backends->get_ike_cfg(charon->backends, - me, other); - if (this->ike_cfg == NULL) - { - /* no config found for these hosts, destroy */ - DBG1(DBG_IKE, "no IKE config found for %H...%H, sending %N", - me, other, notify_type_names, NO_PROPOSAL_CHOSEN); - send_notify_response(this, message, NO_PROPOSAL_CHOSEN); - return DESTROY_ME; - } - /* add a timeout if peer does not establish it completely */ - job = (job_t*)delete_ike_sa_job_create(this->ike_sa_id, FALSE); - charon->scheduler->schedule_job(charon->scheduler, job, - HALF_OPEN_IKE_SA_TIMEOUT); - } - - /* check if message is trustworthy, and update host information */ - if (this->state == IKE_CREATED || this->state == IKE_CONNECTING || - message->get_exchange_type(message) != IKE_SA_INIT) - { - update_hosts(this, me, other); - this->time.inbound = time(NULL); - } - return this->task_manager->process_message(this->task_manager, message); + task = (task_t*)child_create_create(&this->public, child_cfg); + this->task_manager->queue_task(this->task_manager, task); } + iterator->destroy(iterator); + return this->task_manager->initiate(this->task_manager); } +#endif /* P2P */ /** * Implementation of ike_sa_t.initiate. @@ -956,8 +961,11 @@ static status_t initiate(private_ike_sa_t *this, child_cfg_t *child_cfg) if (this->state == IKE_CREATED) { - - if (this->other_host->is_anyaddr(this->other_host)) + if (this->other_host->is_anyaddr(this->other_host) +#ifdef P2P + && !this->peer_cfg->get_mediated_by(this->peer_cfg) +#endif /* P2P */ + ) { child_cfg->destroy(child_cfg); SIG(IKE_UP_START, "initiating IKE_SA"); @@ -975,13 +983,41 @@ static status_t initiate(private_ike_sa_t *this, child_cfg_t *child_cfg) this->task_manager->queue_task(this->task_manager, task); task = (task_t*)ike_config_create(&this->public, TRUE); this->task_manager->queue_task(this->task_manager, task); - task = (task_t*)ike_mobike_create(&this->public, TRUE); + if (this->peer_cfg->use_mobike(this->peer_cfg)) + { + task = (task_t*)ike_mobike_create(&this->public, TRUE); + this->task_manager->queue_task(this->task_manager, task); + } +#ifdef P2P + task = (task_t*)ike_p2p_create(&this->public, TRUE); + this->task_manager->queue_task(this->task_manager, task); +#endif /* P2P */ + } + +#ifdef P2P + if (this->peer_cfg->get_mediated_by(this->peer_cfg)) + { + // mediated connection, initiate mediation process + job_t *job = (job_t*)initiate_mediation_job_create(this->ike_sa_id, child_cfg); + child_cfg->destroy(child_cfg); + charon->processor->queue_job(charon->processor, job); + return SUCCESS; + } + else if (this->peer_cfg->is_mediation(this->peer_cfg)) + { + if (this->state == IKE_ESTABLISHED) + {// FIXME: we should try to find a better solution to this + SIG(CHILD_UP_SUCCESS, "mediation connection is already up and running"); + } + } + else +#endif /* P2P */ + { + // normal IKE_SA with CHILD_SA + task = (task_t*)child_create_create(&this->public, child_cfg); + child_cfg->destroy(child_cfg); this->task_manager->queue_task(this->task_manager, task); } - - task = (task_t*)child_create_create(&this->public, child_cfg); - child_cfg->destroy(child_cfg); - this->task_manager->queue_task(this->task_manager, task); return this->task_manager->initiate(this->task_manager); } @@ -990,7 +1026,7 @@ static status_t initiate(private_ike_sa_t *this, child_cfg_t *child_cfg) * Implementation of ike_sa_t.acquire. */ static status_t acquire(private_ike_sa_t *this, u_int32_t reqid) -{ +{// FIXME: P2P-NAT-T child_cfg_t *child_cfg; iterator_t *iterator; child_sa_t *current, *child_sa = NULL; @@ -1037,8 +1073,11 @@ static status_t acquire(private_ike_sa_t *this, u_int32_t reqid) this->task_manager->queue_task(this->task_manager, task); task = (task_t*)ike_config_create(&this->public, TRUE); this->task_manager->queue_task(this->task_manager, task); - task = (task_t*)ike_mobike_create(&this->public, TRUE); - this->task_manager->queue_task(this->task_manager, task); + if (this->peer_cfg->use_mobike(this->peer_cfg)) + { + task = (task_t*)ike_mobike_create(&this->public, TRUE); + this->task_manager->queue_task(this->task_manager, task); + } } child_cfg = child_sa->get_config(child_sa); @@ -1162,12 +1201,156 @@ static status_t unroute(private_ike_sa_t *this, u_int32_t reqid) } return SUCCESS; } +/** + * Implementation of ike_sa_t.process_message. + */ +static status_t process_message(private_ike_sa_t *this, message_t *message) +{ + status_t status; + bool is_request; + + is_request = message->get_request(message); + + status = message->parse_body(message, this->crypter_in, this->signer_in); + if (status != SUCCESS) + { + + if (is_request) + { + switch (status) + { + case NOT_SUPPORTED: + DBG1(DBG_IKE, "ciritcal unknown payloads found"); + if (is_request) + { + send_notify_response(this, message, UNSUPPORTED_CRITICAL_PAYLOAD); + } + break; + case PARSE_ERROR: + DBG1(DBG_IKE, "message parsing failed"); + if (is_request) + { + send_notify_response(this, message, INVALID_SYNTAX); + } + break; + case VERIFY_ERROR: + DBG1(DBG_IKE, "message verification failed"); + if (is_request) + { + send_notify_response(this, message, INVALID_SYNTAX); + } + break; + case FAILED: + DBG1(DBG_IKE, "integrity check failed"); + /* ignored */ + break; + case INVALID_STATE: + DBG1(DBG_IKE, "found encrypted message, but no keys available"); + if (is_request) + { + send_notify_response(this, message, INVALID_SYNTAX); + } + default: + break; + } + } + 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 status; + } + else + { + host_t *me, *other; + private_ike_sa_t *new; + iterator_t *iterator; + child_sa_t *child; + bool has_routed = FALSE; + + me = message->get_destination(message); + other = message->get_source(message); + + /* if this IKE_SA is virgin, we check for a config */ + if (this->ike_cfg == NULL) + { + job_t *job; + this->ike_cfg = charon->backends->get_ike_cfg(charon->backends, + me, other); + if (this->ike_cfg == NULL) + { + /* no config found for these hosts, destroy */ + DBG1(DBG_IKE, "no IKE config found for %H...%H, sending %N", + me, other, notify_type_names, NO_PROPOSAL_CHOSEN); + send_notify_response(this, message, NO_PROPOSAL_CHOSEN); + return DESTROY_ME; + } + /* add a timeout if peer does not establish it completely */ + job = (job_t*)delete_ike_sa_job_create(this->ike_sa_id, FALSE); + charon->scheduler->schedule_job(charon->scheduler, job, + HALF_OPEN_IKE_SA_TIMEOUT); + } + + /* check if message is trustworthy, and update host information */ + if (this->state == IKE_CREATED || this->state == IKE_CONNECTING || + message->get_exchange_type(message) != IKE_SA_INIT) + { + update_hosts(this, me, other); + this->time.inbound = time(NULL); + } + status = this->task_manager->process_message(this->task_manager, message); + if (status != DESTROY_ME) + { + return status; + } + /* if IKE_SA gets closed for any reasons, reroute routed children */ + iterator = this->child_sas->create_iterator(this->child_sas, TRUE); + while (iterator->iterate(iterator, (void**)&child)) + { + if (child->get_state(child) == CHILD_ROUTED) + { + has_routed = TRUE; + break; + } + } + iterator->destroy(iterator); + if (!has_routed) + { + return status; + } + /* move routed children to a new IKE_SA, apply connection info */ + new = (private_ike_sa_t*)charon->ike_sa_manager->checkout_new( + charon->ike_sa_manager, TRUE); + set_peer_cfg(new, this->peer_cfg); + new->other_host->destroy(new->other_host); + new->other_host = this->other_host->clone(this->other_host); + if (!has_condition(this, COND_NAT_THERE)) + { + new->other_host->set_port(new->other_host, IKEV2_UDP_PORT); + } + if (this->my_virtual_ip) + { + set_virtual_ip(new, TRUE, this->my_virtual_ip); + } + iterator = this->child_sas->create_iterator(this->child_sas, TRUE); + while (iterator->iterate(iterator, (void**)&child)) + { + if (child->get_state(child) == CHILD_ROUTED) + { + route(new, child->get_config(child)); + } + } + iterator->destroy(iterator); + charon->ike_sa_manager->checkin(charon->ike_sa_manager, &new->public); + return status; + } +} /** * Implementation of ike_sa_t.retransmit. */ static status_t retransmit(private_ike_sa_t *this, u_int32_t message_id) -{ +{// FIXME: P2P-NAT-T this->time.outbound = time(NULL); if (this->task_manager->retransmit(this->task_manager, message_id) != SUCCESS) { @@ -1283,9 +1466,12 @@ static status_t retransmit(private_ike_sa_t *this, u_int32_t message_id) { task = (task_t*)child_create_create(&new->public, child_cfg); new->task_manager->queue_task(new->task_manager, task); + } + if (this->peer_cfg->use_mobike(this->peer_cfg)) + { + task = (task_t*)ike_mobike_create(&new->public, TRUE); + new->task_manager->queue_task(new->task_manager, task); } - task = (task_t*)ike_mobike_create(&new->public, TRUE); - new->task_manager->queue_task(new->task_manager, task); new->task_manager->initiate(new->task_manager); } charon->ike_sa_manager->checkin(charon->ike_sa_manager, &new->public); @@ -1684,9 +1870,12 @@ static status_t delete_(private_ike_sa_t *this) ike_delete = ike_delete_create(&this->public, TRUE); this->task_manager->queue_task(this->task_manager, &ike_delete->task); return this->task_manager->initiate(this->task_manager); + case IKE_CREATED: + SIG(IKE_DOWN_SUCCESS, "deleting unestablished IKE_SA"); + break; default: - DBG1(DBG_IKE, "destroying IKE_SA in state %N without notification", - ike_sa_state_names, this->state); + SIG(IKE_DOWN_SUCCESS, "destroying IKE_SA in state %N " + "without notification", ike_sa_state_names, this->state); break; } return DESTROY_ME; @@ -1743,30 +1932,19 @@ static status_t roam(private_ike_sa_t *this, bool address) other = this->other_host; me = charon->kernel_interface->get_source_addr(charon->kernel_interface, other); - - /* TODO: find a better path using additional addresses of peer */ - - if (!me) - { - /* no route found to host, set to stale, wait for a new route */ - set_condition(this, COND_STALE, TRUE); - return FAILED; - } set_condition(this, COND_STALE, FALSE); - if (me->ip_equals(me, this->my_host) && - other->ip_equals(other, this->other_host)) + if (me) { - DBG2(DBG_IKE, "%H still reached through %H, no update needed", - this->other_host, me); + if (me->ip_equals(me, this->my_host) && + other->ip_equals(other, this->other_host)) + { + DBG2(DBG_IKE, "keeping connection path %H - %H", this->other_host, me); + me->destroy(me); + return SUCCESS; + } me->destroy(me); - return SUCCESS; } - me->set_port(me, this->my_host->get_port(this->my_host)); - other = other->clone(other); - other->set_port(other, this->other_host->get_port(this->other_host)); - set_my_host(this, me); - set_other_host(this, other); /* update addresses with mobike, if supported ... */ if (supports_extension(this, EXT_MOBIKE)) @@ -1995,6 +2173,15 @@ static void destroy(private_ike_sa_t *this) offsetof(host_t, destroy)); this->additional_addresses->destroy_offset(this->additional_addresses, offsetof(host_t, destroy)); +#ifdef P2P + if (this->peer_cfg && this->peer_cfg->is_mediation(this->peer_cfg) && + !this->ike_sa_id->is_initiator(this->ike_sa_id)) + { + // mediation server + charon->mediation_manager->remove(charon->mediation_manager, this->ike_sa_id); + } + DESTROY_IF(this->server_reflexive_host); +#endif /* P2P */ DESTROY_IF(this->my_host); DESTROY_IF(this->other_host); @@ -2077,6 +2264,15 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id) this->public.set_virtual_ip = (void (*)(ike_sa_t*,bool,host_t*))set_virtual_ip; this->public.get_virtual_ip = (host_t* (*)(ike_sa_t*,bool))get_virtual_ip; this->public.add_dns_server = (void (*)(ike_sa_t*,host_t*))add_dns_server; +#ifdef P2P + this->public.get_server_reflexive_host = (host_t* (*)(ike_sa_t*)) get_server_reflexive_host; + this->public.set_server_reflexive_host = (void (*)(ike_sa_t*,host_t*)) set_server_reflexive_host; + this->public.initiate_mediation = (status_t (*)(ike_sa_t*,peer_cfg_t*)) initiate_mediation; + this->public.initiate_mediated = (status_t (*)(ike_sa_t*,host_t*,host_t*,linked_list_t*)) initiate_mediated; + this->public.relay = (status_t (*)(ike_sa_t*,identification_t*,chunk_t,chunk_t,linked_list_t*,bool)) relay; + this->public.callback = (status_t (*)(ike_sa_t*,identification_t*)) callback; + this->public.respond = (status_t (*)(ike_sa_t*,identification_t*,chunk_t)) respond; +#endif /* P2P */ /* initialize private fields */ this->ike_sa_id = ike_sa_id->clone(ike_sa_id); @@ -2111,6 +2307,9 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id) this->additional_addresses = linked_list_create(); this->pending_updates = 0; this->keyingtry = 0; +#ifdef P2P + this->server_reflexive_host = NULL; +#endif /* P2P */ return &this->public; } diff --git a/src/charon/sa/ike_sa.h b/src/charon/sa/ike_sa.h index ba189577c..99f09e98a 100644 --- a/src/charon/sa/ike_sa.h +++ b/src/charon/sa/ike_sa.h @@ -6,7 +6,8 @@ */ /* - * Copyright (C) 2006 Tobias Brunner, Daniel Roethlisberger + * Copyright (C) 2006-2007 Tobias Brunner + * Copyright (C) 2006 Daniel Roethlisberger * Copyright (C) 2005-2006 Martin Willi * Copyright (C) 2005 Jan Hutter * Hochschule fuer Technik Rapperswil @@ -94,7 +95,7 @@ enum ike_extension_t { enum ike_condition_t { /** - * Connection is natted somewhere + * Connection is natted (or faked) somewhere */ COND_NAT_ANY = (1<<0), @@ -107,11 +108,16 @@ enum ike_condition_t { * other is behind NAT */ COND_NAT_THERE = (1<<2), + + /** + * Faking NAT to enforce UDP encapsulation + */ + COND_NAT_FAKE = (1<<3), /** * peer is currently not reachable (due missing route, ...) */ - COND_STALE = (1<<3), + COND_STALE = (1<<4), }; /** @@ -447,6 +453,96 @@ struct ike_sa_t { * @param updates number of pending updates */ void (*set_pending_updates)(ike_sa_t *this, u_int32_t updates); + +#ifdef P2P + /** + * @brief Get the server reflexive host. + * + * @param this calling object + * @return server reflexive host + */ + host_t* (*get_server_reflexive_host) (ike_sa_t *this); + + /** + * @brief Set the server reflexive host. + * + * @param this calling object + * @param host server reflexive host + */ + void (*set_server_reflexive_host) (ike_sa_t *this, host_t *host); + + /** + * @brief Initiate the mediation of a mediated connection (i.e. initiate a + * P2P_CONNECT exchange). + * + * @param this calling object + * @param mediated_cfg peer_cfg of the mediated connection + * @return + * - SUCCESS if initialization started + * - DESTROY_ME if initialization failed + */ + status_t (*initiate_mediation) (ike_sa_t *this, peer_cfg_t *mediated_cfg); + + /** + * @brief Initiate the mediated connection + * + * @param this calling object + * @param me local endpoint (gets cloned) + * @param other remote endpoint (gets cloned) + * @param childs linked list of child_cfg_t of CHILD_SAs (gets cloned) + * @return + * - SUCCESS if initialization started + * - DESTROY_ME if initialization failed + */ + status_t (*initiate_mediated) (ike_sa_t *this, host_t *me, host_t *other, + linked_list_t *childs); + + /** + * @brief Relay data from one peer to another (i.e. initiate a + * P2P_CONNECT exchange). + * + * Data is cloned. + * + * @param this calling object + * @param requester ID of the requesting peer + * @param session_id data of the P2P_SESSIONID payload + * @param session_key data of the P2P_SESSIONKEY payload + * @param endpoints endpoints + * @param response TRUE if this is a response + * @return + * - SUCCESS if relay started + * - DESTROY_ME if relay failed + */ + status_t (*relay) (ike_sa_t *this, identification_t *requester, chunk_t session_id, + chunk_t session_key, linked_list_t *endpoints, bool response); + + /** + * @brief Send a callback to a peer. + * + * Data is cloned. + * + * @param this calling object + * @param peer_id ID of the other peer + * @return + * - SUCCESS if response started + * - DESTROY_ME if response failed + */ + status_t (*callback) (ike_sa_t *this, identification_t *peer_id); + + /** + * @brief Respond to a P2P_CONNECT request. + * + * Data is cloned. + * + * @param this calling object + * @param peer_id ID of the other peer + * @param session_id the session ID supplied by the initiator + * @return + * - SUCCESS if response started + * - DESTROY_ME if response failed + */ + status_t (*respond) (ike_sa_t *this, identification_t *peer_id, chunk_t session_id); +#endif /* P2P */ /** * @brief Initiate a new connection. diff --git a/src/charon/sa/ike_sa_manager.c b/src/charon/sa/ike_sa_manager.c index 56b865891..5014ea0e2 100644 --- a/src/charon/sa/ike_sa_manager.c +++ b/src/charon/sa/ike_sa_manager.c @@ -368,7 +368,7 @@ static ike_sa_t *checkout_new(private_ike_sa_manager_t* this, bool initiator) } /** - * Implementation of of ike_sa_manager.checkout_by_id. + * Implementation of of ike_sa_manager.checkout_by_message. */ static ike_sa_t* checkout_by_message(private_ike_sa_manager_t* this, message_t *message) @@ -483,7 +483,7 @@ static ike_sa_t* checkout_by_message(private_ike_sa_manager_t* this, } /** - * Implementation of of ike_sa_manager.checkout_by_id. + * Implementation of of ike_sa_manager.checkout_by_peer. */ static ike_sa_t* checkout_by_peer(private_ike_sa_manager_t *this, host_t *my_host, host_t *other_host, @@ -542,6 +542,7 @@ static ike_sa_t* checkout_by_peer(private_ike_sa_manager_t *this, my_host, my_id, other_host, other_id); entry->checked_out = TRUE; ike_sa = entry->ike_sa; + break; } } iterator->destroy(iterator); diff --git a/src/charon/sa/mediation_manager.c b/src/charon/sa/mediation_manager.c new file mode 100644 index 000000000..fca53a940 --- /dev/null +++ b/src/charon/sa/mediation_manager.c @@ -0,0 +1,343 @@ +/** + * @file mediation_manager.c + * + * @brief Implementation of mediation_manager_t. + * + */ + +/* + * Copyright (C) 2007 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 "mediation_manager.h" + +#include <pthread.h> +#include <daemon.h> +#include <utils/linked_list.h> +#include <processing/jobs/mediation_job.h> + + +typedef struct peer_t peer_t; + +/** + * An entry in the linked list. + */ +struct peer_t { + /** id of the peer */ + identification_t *id; + + /** sa id of the peer, NULL if offline */ + ike_sa_id_t *ike_sa_id; + + /** list of peer ids that reuested this peer */ + linked_list_t *requested_by; +}; + +/** + * Implementation of peer_t.destroy. + */ +static void peer_destroy(peer_t *this) +{ + DESTROY_IF(this->id); + DESTROY_IF(this->ike_sa_id); + this->requested_by->destroy_offset(this->requested_by, offsetof(identification_t, destroy)); + free(this); +} + +/** + * Creates a new entry for the list. + */ +static peer_t *peer_create(identification_t *id, ike_sa_id_t* ike_sa_id) +{ + peer_t *this = malloc_thing(peer_t); + + /* clone everything */ + this->id = id->clone(id); + this->ike_sa_id = ike_sa_id ? ike_sa_id->clone(ike_sa_id) : NULL; + this->requested_by = linked_list_create(); + + return this; +} + + +typedef struct private_mediation_manager_t private_mediation_manager_t; + +/** + * Additional private members of mediation_manager_t. + */ +struct private_mediation_manager_t { + /** + * Public interface of mediation_manager_t. + */ + mediation_manager_t public; + + /** + * Lock for exclusivly accessing the manager. + */ + pthread_mutex_t mutex; + + /** + * Linked list with state entries. + */ + linked_list_t *peers; +}; + +/** + * Registers a peer's ID at another peer, if it is not yet registered + */ +static void register_peer(peer_t *peer, identification_t *peer_id) +{ + iterator_t *iterator; + identification_t *current; + + iterator = peer->requested_by->create_iterator(peer->requested_by, TRUE); + while (iterator->iterate(iterator, (void**)¤t)) + { + if (peer_id->equals(peer_id, current)) + { + iterator->destroy(iterator); + return; + } + } + iterator->destroy(iterator); + + peer->requested_by->insert_last(peer->requested_by, peer_id->clone(peer_id)); +} + +/** + * Get a peer_t object by a peer's id + */ +static status_t get_peer_by_id(private_mediation_manager_t *this, + identification_t *id, peer_t **peer) +{ + iterator_t *iterator; + peer_t *current; + status_t status = NOT_FOUND; + + iterator = this->peers->create_iterator(this->peers, TRUE); + while (iterator->iterate(iterator, (void**)¤t)) + { + if (id->equals(id, current->id)) + { + if (peer) + { + *peer = current; + } + status = SUCCESS; + break; + } + } + iterator->destroy(iterator); + + return status; +} + +/** + * Check if a given peer is registered at other peers. If so, remove it there + * and then remove peers completely that are not online and have no registered + * peers. + */ +static void unregister_peer(private_mediation_manager_t *this, identification_t *peer_id) +{ + iterator_t *iterator, *iterator_r; + peer_t *peer; + identification_t *registered; + + iterator = this->peers->create_iterator(this->peers, TRUE); + while (iterator->iterate(iterator, (void**)&peer)) + { + iterator_r = peer->requested_by->create_iterator(peer->requested_by, TRUE); + while (iterator_r->iterate(iterator_r, (void**)®istered)) + { + if (peer_id->equals(peer_id, registered)) + { + iterator_r->remove(iterator_r); + registered->destroy(registered); + break; + } + } + iterator_r->destroy(iterator_r); + + if (!peer->ike_sa_id && !peer->requested_by->get_count(peer->requested_by)) + { + iterator->remove(iterator); + peer_destroy(peer); + break; + } + } + iterator->destroy(iterator); +} + +/** + * Implementation of mediation_manager_t.remove + */ +static void remove_sa(private_mediation_manager_t *this, ike_sa_id_t *ike_sa_id) +{ + iterator_t *iterator; + peer_t *peer; + + pthread_mutex_lock(&(this->mutex)); + + iterator = this->peers->create_iterator(this->peers, TRUE); + while (iterator->iterate(iterator, (void**)&peer)) + { + if (ike_sa_id->equals(ike_sa_id, peer->ike_sa_id)) + { + iterator->remove(iterator); + + unregister_peer(this, peer->id); + + peer_destroy(peer); + break; + } + } + iterator->destroy(iterator); + + pthread_mutex_unlock(&(this->mutex)); +} + +/** + * Implementation of mediation_manager_t.update_sa_id + */ +static void update_sa_id(private_mediation_manager_t *this, identification_t *peer_id, ike_sa_id_t *ike_sa_id) +{ + iterator_t *iterator; + peer_t *peer; + bool found = FALSE; + + pthread_mutex_lock(&(this->mutex)); + + iterator = this->peers->create_iterator(this->peers, TRUE); + while (iterator->iterate(iterator, (void**)&peer)) + { + if (peer_id->equals(peer_id, peer->id)) + { + DESTROY_IF(peer->ike_sa_id); + found = TRUE; + break; + } + } + iterator->destroy(iterator); + + if (!found) + { + DBG2(DBG_IKE, "adding peer '%D'", peer_id); + peer = peer_create(peer_id, NULL); + this->peers->insert_last(this->peers, peer); + } + + DBG2(DBG_IKE, "changing registered IKE_SA ID of peer '%D'", peer_id); + peer->ike_sa_id = ike_sa_id ? ike_sa_id->clone(ike_sa_id) : NULL; + + // send callbacks to registered peers + identification_t *requester; + while(peer->requested_by->remove_last(peer->requested_by, (void**)&requester) == SUCCESS) + { + job_t *job = (job_t*)mediation_callback_job_create(requester, peer_id); + charon->processor->queue_job(charon->processor, job); + } + + pthread_mutex_unlock(&(this->mutex)); +} + +/** + * Implementation of mediation_manager_t.check. + */ +static ike_sa_id_t *check(private_mediation_manager_t *this, + identification_t *peer_id) +{ + peer_t *peer; + ike_sa_id_t *ike_sa_id; + + pthread_mutex_lock(&(this->mutex)); + + if (get_peer_by_id(this, peer_id, &peer) != SUCCESS) + { + pthread_mutex_unlock(&(this->mutex)); + return NULL; + } + + ike_sa_id = peer->ike_sa_id; + + pthread_mutex_unlock(&(this->mutex)); + + return ike_sa_id; +} + +/** + * Implementation of mediation_manager_t.check_and_register. + */ +static ike_sa_id_t *check_and_register(private_mediation_manager_t *this, + identification_t *peer_id, identification_t *requester) +{ + peer_t *peer; + ike_sa_id_t *ike_sa_id; + + pthread_mutex_lock(&(this->mutex)); + + if (get_peer_by_id(this, peer_id, &peer) != SUCCESS) + { + DBG2(DBG_IKE, "adding peer %D", peer_id); + peer = peer_create(peer_id, NULL); + this->peers->insert_last(this->peers, peer); + } + + if (!peer->ike_sa_id) + { + // the peer is not online + DBG2(DBG_IKE, "requested peer '%D' is offline, registering peer '%D'", peer_id, requester); + register_peer(peer, requester); + pthread_mutex_unlock(&(this->mutex)); + return NULL; + } + + ike_sa_id = peer->ike_sa_id; + + pthread_mutex_unlock(&(this->mutex)); + + return ike_sa_id; +} + +/** + * Implementation of mediation_manager_t.destroy. + */ +static void destroy(private_mediation_manager_t *this) +{ + pthread_mutex_lock(&(this->mutex)); + + this->peers->destroy_function(this->peers, (void*)peer_destroy); + + pthread_mutex_unlock(&(this->mutex)); + pthread_mutex_destroy(&(this->mutex)); + free(this); +} + +/* + * Described in header. + */ +mediation_manager_t *mediation_manager_create() +{ + private_mediation_manager_t *this = malloc_thing(private_mediation_manager_t); + + this->public.destroy = (void(*)(mediation_manager_t*))destroy; + this->public.remove = (void(*)(mediation_manager_t*,ike_sa_id_t*))remove_sa; + this->public.update_sa_id = (void(*)(mediation_manager_t*,identification_t*,ike_sa_id_t*))update_sa_id; + this->public.check = (ike_sa_id_t*(*)(mediation_manager_t*,identification_t*))check; + this->public.check_and_register = (ike_sa_id_t*(*)(mediation_manager_t*,identification_t*,identification_t*))check_and_register; + + this->peers = linked_list_create(); + pthread_mutex_init(&(this->mutex), NULL); + + return (mediation_manager_t*)this; +} diff --git a/src/charon/sa/mediation_manager.h b/src/charon/sa/mediation_manager.h new file mode 100644 index 000000000..74acc4d41 --- /dev/null +++ b/src/charon/sa/mediation_manager.h @@ -0,0 +1,104 @@ +/** + * @file mediation_manager.h + * + * @brief Interface of mediation_manager_t. + * + */ + +/* + * Copyright (C) 2007 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. + */ + +#ifndef MEDIATION_MANAGER_H_ +#define MEDIATION_MANAGER_H_ + +typedef struct mediation_manager_t mediation_manager_t; + +#include <sa/ike_sa_id.h> +#include <utils/identification.h> + +/** + * @brief The mediation manager is responsible for managing currently online + * peers and registered requests for offline peers on the mediation server. + * + * @b Constructors: + * - mediation_manager_create() + * + * @ingroup sa + */ +struct mediation_manager_t { + + /** + * @brief Remove the IKE_SA of a peer. + * + * @param this the manager object + * @param ike_sa_id the IKE_SA ID of the peer's SA + */ + void (*remove) (mediation_manager_t* this, ike_sa_id_t *ike_sa_id); + + /** + * @brief Update the ike_sa_id that is assigned to a peer's ID. If the peer + * is new, it gets a new record assigned. + * + * @param this the manager object + * @param peer_id the peer's ID + * @param ike_sa_id the IKE_SA ID of the peer's SA + */ + void (*update_sa_id) (mediation_manager_t* this, identification_t *peer_id, + ike_sa_id_t *ike_sa_id); + + /** + * @brief Checks if a specific peer is online. + * + * @param this the manager object + * @param peer_id the peer's ID + * @returns + * - IKE_SA ID of the peer's SA. + * - NULL, if the peer is not online. + */ + ike_sa_id_t* (*check) (mediation_manager_t* this, + identification_t *peer_id); + + /** + * @brief Checks if a specific peer is online and registers the requesting + * peer if it is not. + * + * @param this the manager object + * @param peer_id the peer's ID + * @param requester the requesters ID + * @returns + * - IKE_SA ID of the peer's SA. + * - NULL, if the peer is not online. + */ + ike_sa_id_t* (*check_and_register) (mediation_manager_t* this, + identification_t *peer_id, identification_t *requester); + + /** + * @brief Destroys the manager with all data. + * + * @param this the manager object + */ + void (*destroy) (mediation_manager_t *this); +}; + +/** + * @brief Create a manager. + * + * @returns mediation_manager_t object + * + * @ingroup sa + */ +mediation_manager_t *mediation_manager_create(void); + +#endif /*MEDIATION_MANAGER_H_*/ diff --git a/src/charon/sa/task_manager.c b/src/charon/sa/task_manager.c index 55592f437..f4484774e 100644 --- a/src/charon/sa/task_manager.c +++ b/src/charon/sa/task_manager.c @@ -6,6 +6,7 @@ */ /* + * Copyright (C) 2007 Tobias Brunner * Copyright (C) 2007 Martin Willi * Hochschule fuer Technik Rapperswil * @@ -40,6 +41,10 @@ #include <encoding/payloads/delete_payload.h> #include <processing/jobs/retransmit_job.h> +#ifdef P2P +#include <sa/tasks/ike_p2p.h> +#endif + typedef struct exchange_t exchange_t; /** @@ -217,28 +222,73 @@ static status_t retransmit(private_task_manager_t *this, u_int32_t message_id) { u_int32_t timeout; job_t *job; + iterator_t *iterator; + packet_t *packet; + task_t *task; + ike_mobike_t *mobike = NULL; + + /* check if we are retransmitting a MOBIKE routability check */ + iterator = this->active_tasks->create_iterator(this->active_tasks, TRUE); + while (iterator->iterate(iterator, (void*)&task)) + { + if (task->get_type(task) == IKE_MOBIKE) + { + mobike = (ike_mobike_t*)task; + if (!mobike->is_probing(mobike)) + { + mobike = NULL; + } + break; + } + } + iterator->destroy(iterator); - if (this->initiating.retransmitted <= RETRANSMIT_TRIES) + if (mobike == NULL) { - timeout = (u_int32_t)(RETRANSMIT_TIMEOUT * - pow(RETRANSMIT_BASE, this->initiating.retransmitted)); + if (this->initiating.retransmitted <= RETRANSMIT_TRIES) + { + timeout = (u_int32_t)(RETRANSMIT_TIMEOUT * + pow(RETRANSMIT_BASE, this->initiating.retransmitted)); + } + else + { + DBG1(DBG_IKE, "giving up after %d retransmits", + this->initiating.retransmitted - 1); + return DESTROY_ME; + } + + if (this->initiating.retransmitted) + { + DBG1(DBG_IKE, "retransmit %d of request with message ID %d", + this->initiating.retransmitted, message_id); + } + packet = this->initiating.packet->clone(this->initiating.packet); } else - { - DBG1(DBG_IKE, "giving up after %d retransmits", - this->initiating.retransmitted - 1); - return DESTROY_ME; + { /* for routeability checks, we use a more aggressive behavior */ + if (this->initiating.retransmitted <= ROUTEABILITY_CHECK_TRIES) + { + timeout = ROUTEABILITY_CHECK_INTERVAL; + } + else + { + DBG1(DBG_IKE, "giving up after %d path probings", + this->initiating.retransmitted - 1); + return DESTROY_ME; + } + + if (this->initiating.retransmitted) + { + DBG1(DBG_IKE, "path probing attempt %d", + this->initiating.retransmitted); + } + packet = this->initiating.packet->clone(this->initiating.packet); + mobike->transmit(mobike, packet); } - if (this->initiating.retransmitted) - { - DBG1(DBG_IKE, "retransmit %d of request with message ID %d", - this->initiating.retransmitted, message_id); - } - this->initiating.retransmitted++; + charon->sender->send(charon->sender, packet); - charon->sender->send(charon->sender, - this->initiating.packet->clone(this->initiating.packet)); + this->initiating.retransmitted++; job = (job_t*)retransmit_job_create(this->initiating.mid, this->ike_sa->get_id(this->ike_sa)); charon->scheduler->schedule_job(charon->scheduler, job, timeout); @@ -255,6 +305,7 @@ static status_t build_request(private_task_manager_t *this) iterator_t *iterator; task_t *task; message_t *message; + host_t *me, *other; status_t status; exchange_type_t exchange = 0; @@ -277,6 +328,13 @@ static status_t build_request(private_task_manager_t *this) exchange = IKE_SA_INIT; activate_task(this, IKE_NATD); activate_task(this, IKE_CERT); +#ifdef P2P + /* this task has to be activated before the IKE_AUTHENTICATE + * task, because that task pregenerates the packet after + * which no payloads can be added to the message anymore. + */ + activate_task(this, IKE_P2P); +#endif /* P2P */ activate_task(this, IKE_AUTHENTICATE); activate_task(this, IKE_CONFIG); activate_task(this, CHILD_CREATE); @@ -324,6 +382,13 @@ static status_t build_request(private_task_manager_t *this) exchange = INFORMATIONAL; break; } +#ifdef P2P + if (activate_task(this, IKE_P2P)) + { + exchange = P2P_CONNECT; + break; + } +#endif /* P2P */ case IKE_REKEYING: if (activate_task(this, IKE_DELETE)) { @@ -372,8 +437,13 @@ static status_t build_request(private_task_manager_t *this) return SUCCESS; } + me = this->ike_sa->get_my_host(this->ike_sa); + other = this->ike_sa->get_other_host(this->ike_sa); + message = message_create(); message->set_message_id(message, this->initiating.mid); + message->set_source(message, me->clone(me)); + message->set_destination(message, other->clone(other)); message->set_exchange_type(message, exchange); this->initiating.type = exchange; this->initiating.retransmitted = 0; @@ -412,7 +482,7 @@ static status_t build_request(private_task_manager_t *this) * close the SA */ flush(this); return DESTROY_ME; - } + } return retransmit(this, this->initiating.mid); } @@ -523,17 +593,23 @@ static void handle_collisions(private_task_manager_t *this, task_t *task) /** * build a response depending on the "passive" task list */ -static status_t build_response(private_task_manager_t *this, - exchange_type_t exchange) +static status_t build_response(private_task_manager_t *this, message_t *request) { iterator_t *iterator; task_t *task; message_t *message; + host_t *me, *other; bool delete = FALSE; status_t status; + me = request->get_destination(request); + other = request->get_source(request); + message = message_create(); - message->set_exchange_type(message, exchange); + message->set_exchange_type(message, request->get_exchange_type(request)); + /* send response along the path the request came in */ + message->set_source(message, me->clone(me)); + message->set_destination(message, other->clone(other)); message->set_message_id(message, this->responding.mid); message->set_request(message, FALSE); @@ -563,7 +639,7 @@ static status_t build_response(private_task_manager_t *this, iterator->destroy(iterator); /* remove resonder SPI if IKE_SA_INIT failed */ - if (delete && exchange == IKE_SA_INIT) + if (delete && request->get_exchange_type(request) == IKE_SA_INIT) { ike_sa_id_t *id = this->ike_sa->get_id(this->ike_sa); id->set_responder_spi(id, 0); @@ -596,15 +672,12 @@ static status_t process_request(private_task_manager_t *this, { iterator_t *iterator; task_t *task = NULL; - exchange_type_t exchange; payload_t *payload; notify_payload_t *notify; delete_payload_t *delete; - exchange = message->get_exchange_type(message); - /* create tasks depending on request type */ - switch (exchange) + switch (message->get_exchange_type(message)) { case IKE_SA_INIT: { @@ -614,6 +687,10 @@ static status_t process_request(private_task_manager_t *this, this->passive_tasks->insert_last(this->passive_tasks, task); task = (task_t*)ike_cert_create(this->ike_sa, FALSE); this->passive_tasks->insert_last(this->passive_tasks, task); +#ifdef P2P + task = (task_t*)ike_p2p_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); +#endif /* P2P */ task = (task_t*)ike_auth_create(this->ike_sa, FALSE); this->passive_tasks->insert_last(this->passive_tasks, task); task = (task_t*)ike_config_create(this->ike_sa, FALSE); @@ -625,7 +702,7 @@ static status_t process_request(private_task_manager_t *this, break; } case CREATE_CHILD_SA: - { + {//FIXME: we should prevent this on mediation connections bool notify_found = FALSE, ts_found = FALSE; iterator = message->get_payload_iterator(message); while (iterator->iterate(iterator, (void**)&payload)) @@ -733,6 +810,13 @@ static status_t process_request(private_task_manager_t *this, this->passive_tasks->insert_last(this->passive_tasks, task); break; } +#ifdef P2P + case P2P_CONNECT: + { + task = (task_t*)ike_p2p_create(this->ike_sa, FALSE); + this->passive_tasks->insert_last(this->passive_tasks, task); + } +#endif /* P2P */ default: break; } @@ -760,7 +844,7 @@ static status_t process_request(private_task_manager_t *this, } iterator->destroy(iterator); - return build_response(this, exchange); + return build_response(this, message); } /** @@ -783,14 +867,21 @@ static status_t process_message(private_task_manager_t *this, message_t *msg) } else if ((mid == this->responding.mid - 1) && this->responding.packet) { + packet_t *clone; + host_t *me, *other; + DBG1(DBG_IKE, "received retransmit of request with ID %d, " "retransmitting response", mid); - charon->sender->send(charon->sender, - this->responding.packet->clone(this->responding.packet)); + clone = this->responding.packet->clone(this->responding.packet); + me = msg->get_destination(msg); + other = msg->get_source(msg); + clone->set_source(clone, me->clone(me)); + clone->set_destination(clone, other->clone(other)); + charon->sender->send(charon->sender, clone); } else { - DBG1(DBG_IKE, "received message ID %d, excepted %d. Ignored", + DBG1(DBG_IKE, "received message ID %d, expected %d. Ignored", mid, this->responding.mid); } } @@ -806,7 +897,7 @@ static status_t process_message(private_task_manager_t *this, message_t *msg) } else { - DBG1(DBG_IKE, "received message ID %d, excepted %d. Ignored", + DBG1(DBG_IKE, "received message ID %d, expected %d. Ignored", mid, this->initiating.mid); return SUCCESS; } @@ -819,6 +910,23 @@ static status_t process_message(private_task_manager_t *this, message_t *msg) */ static void queue_task(private_task_manager_t *this, task_t *task) { + if (task->get_type(task) == IKE_MOBIKE) + { /* there is no need to queue more than one mobike task */ + iterator_t *iterator; + task_t *current; + + iterator = this->queued_tasks->create_iterator(this->queued_tasks, TRUE); + while (iterator->iterate(iterator, (void**)¤t)) + { + if (current->get_type(current) == IKE_MOBIKE) + { + iterator->destroy(iterator); + task->destroy(task); + return; + } + } + iterator->destroy(iterator); + } DBG2(DBG_IKE, "queueing %N task", task_type_names, task->get_type(task)); this->queued_tasks->insert_last(this->queued_tasks, task); } diff --git a/src/charon/sa/task_manager.h b/src/charon/sa/task_manager.h index fb34aab6a..38c63c1a9 100644 --- a/src/charon/sa/task_manager.h +++ b/src/charon/sa/task_manager.h @@ -51,6 +51,20 @@ typedef struct task_manager_t task_manager_t; */ #define RETRANSMIT_TRIES 5 +/** + * Interval for mobike routability checks in ms. + * + * @ingroup sa + */ +#define ROUTEABILITY_CHECK_INTERVAL 2500 + +/** + * Number of routability checks before giving up + * + * @ingroup sa + */ +#define ROUTEABILITY_CHECK_TRIES 10 + /** * @brief The task manager, juggles task and handles message exchanges. diff --git a/src/charon/sa/tasks/child_create.c b/src/charon/sa/tasks/child_create.c index 42f34a94b..3947a84d1 100644 --- a/src/charon/sa/tasks/child_create.c +++ b/src/charon/sa/tasks/child_create.c @@ -722,7 +722,8 @@ static status_t build_r(private_child_create_t *this, message_t *message) build_payloads(this, message); - SIG(CHILD_UP_SUCCESS, "established CHILD_SA successfully"); + SIG(CHILD_UP_SUCCESS, "CHILD_SA '%s' established successfully", + this->child_sa->get_name(this->child_sa)); return SUCCESS; } @@ -807,7 +808,8 @@ static status_t process_i(private_child_create_t *this, message_t *message) if (select_and_install(this, no_dh) == SUCCESS) { - SIG(CHILD_UP_SUCCESS, "established CHILD_SA successfully"); + SIG(CHILD_UP_SUCCESS, "CHILD_SA '%s' established successfully", + this->child_sa->get_name(this->child_sa)); } return SUCCESS; } diff --git a/src/charon/sa/tasks/ike_auth.c b/src/charon/sa/tasks/ike_auth.c index c1c0cd5a2..a3cd6a2bc 100644 --- a/src/charon/sa/tasks/ike_auth.c +++ b/src/charon/sa/tasks/ike_auth.c @@ -157,13 +157,13 @@ static status_t build_id(private_ike_auth_t *this, message_t *message) this->ike_sa->set_my_id(this->ike_sa, me->clone(me)); } - id = id_payload_create_from_identification(this->initiator, me); + id = id_payload_create_from_identification(this->initiator ? ID_INITIATOR : ID_RESPONDER, me); message->add_payload(message, (payload_t*)id); /* as initiator, include other ID if it does not contain wildcards */ if (this->initiator && !other->contains_wildcards(other)) { - id = id_payload_create_from_identification(FALSE, other); + id = id_payload_create_from_identification(ID_RESPONDER, other); message->add_payload(message, (payload_t*)id); } return SUCCESS; @@ -320,7 +320,8 @@ static status_t build_auth_eap(private_ike_auth_t *this, message_t *message) if (!this->initiator) { this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED); - SIG(IKE_UP_SUCCESS, "IKE_SA established between %D[%H]...[%H]%D", + SIG(IKE_UP_SUCCESS, "IKE_SA '%s' established between %D[%H]...[%H]%D", + this->ike_sa->get_name(this->ike_sa), this->ike_sa->get_my_id(this->ike_sa), this->ike_sa->get_my_host(this->ike_sa), this->ike_sa->get_other_host(this->ike_sa), @@ -365,7 +366,8 @@ static status_t process_auth_eap(private_ike_auth_t *this, message_t *message) if (this->initiator) { this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED); - SIG(IKE_UP_SUCCESS, "IKE_SA established between %D[%H]...[%H]%D", + SIG(IKE_UP_SUCCESS, "IKE_SA '%s' established between %D[%H]...[%H]%D", + this->ike_sa->get_name(this->ike_sa), this->ike_sa->get_my_id(this->ike_sa), this->ike_sa->get_my_host(this->ike_sa), this->ike_sa->get_other_host(this->ike_sa), @@ -573,7 +575,8 @@ static status_t build_r(private_ike_auth_t *this, message_t *message) if (this->peer_authenticated) { this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED); - SIG(IKE_UP_SUCCESS, "IKE_SA established between %D[%H]...[%H]%D", + SIG(IKE_UP_SUCCESS, "IKE_SA '%s' established between %D[%H]...[%H]%D", + this->ike_sa->get_name(this->ike_sa), this->ike_sa->get_my_id(this->ike_sa), this->ike_sa->get_my_host(this->ike_sa), this->ike_sa->get_other_host(this->ike_sa), @@ -675,7 +678,8 @@ static status_t process_i(private_ike_auth_t *this, message_t *message) } this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED); - SIG(IKE_UP_SUCCESS, "IKE_SA established between %D[%H]...[%H]%D", + SIG(IKE_UP_SUCCESS, "IKE_SA '%s' established between %D[%H]...[%H]%D", + this->ike_sa->get_name(this->ike_sa), this->ike_sa->get_my_id(this->ike_sa), this->ike_sa->get_my_host(this->ike_sa), this->ike_sa->get_other_host(this->ike_sa), diff --git a/src/charon/sa/tasks/ike_init.c b/src/charon/sa/tasks/ike_init.c index f78b5dd66..42b47a82f 100644 --- a/src/charon/sa/tasks/ike_init.c +++ b/src/charon/sa/tasks/ike_init.c @@ -149,10 +149,18 @@ static void build_payloads(private_ike_init_t *this, message_t *message) nonce_payload = nonce_payload_create(); nonce_payload->set_nonce(nonce_payload, this->my_nonce); - message->add_payload(message, (payload_t*)nonce_payload); - ke_payload = ke_payload_create_from_diffie_hellman(this->dh); - message->add_payload(message, (payload_t*)ke_payload); + + if (this->old_sa) + { /* payload order differs if we are rekeying */ + message->add_payload(message, (payload_t*)nonce_payload); + message->add_payload(message, (payload_t*)ke_payload); + } + else + { + message->add_payload(message, (payload_t*)ke_payload); + message->add_payload(message, (payload_t*)nonce_payload); + } } /** @@ -218,7 +226,8 @@ static status_t build_i(private_ike_init_t *this, message_t *message) status_t status; this->config = this->ike_sa->get_ike_cfg(this->ike_sa); - SIG(IKE_UP_START, "initiating IKE_SA to %H", + SIG(IKE_UP_START, "initiating IKE_SA '%s' to %H", + this->ike_sa->get_name(this->ike_sa), this->config->get_other_host(this->config)); this->ike_sa->set_state(this->ike_sa, IKE_CONNECTING); diff --git a/src/charon/sa/tasks/ike_mobike.c b/src/charon/sa/tasks/ike_mobike.c index 8d4dce36c..d1fc8c695 100644 --- a/src/charon/sa/tasks/ike_mobike.c +++ b/src/charon/sa/tasks/ike_mobike.c @@ -64,7 +64,12 @@ struct private_ike_mobike_t { /** * use task to update addresses */ - bool roam; + bool update; + + /** + * do routability check + */ + bool check; /** * include address list update @@ -140,7 +145,7 @@ static void process_payloads(private_ike_mobike_t *this, message_t *message) } case UPDATE_SA_ADDRESSES: { - this->roam = TRUE; + this->update = TRUE; break; } case NO_ADDITIONAL_ADDRESSES: @@ -225,6 +230,58 @@ static void update_children(private_ike_mobike_t *this) } /** + * Implementation of ike_mobike_t.transmit + */ +static void transmit(private_ike_mobike_t *this, packet_t *packet) +{ + host_t *me, *other, *me_old, *other_old; + iterator_t *iterator; + packet_t *copy; + + if (!this->check) + { + return; + } + + me_old = this->ike_sa->get_my_host(this->ike_sa); + other_old = this->ike_sa->get_other_host(this->ike_sa); + + me = charon->kernel_interface->get_source_addr( + charon->kernel_interface, other_old); + if (me) + { + me->set_port(me, me->ip_equals(me, me_old) ? + me_old->get_port(me_old) : IKEV2_NATT_PORT); + packet->set_source(packet, me); + } + + iterator = this->ike_sa->create_additional_address_iterator(this->ike_sa); + while (iterator->iterate(iterator, (void**)&other)) + { + me = charon->kernel_interface->get_source_addr( + charon->kernel_interface, other); + if (me) + { + /* reuse port for an active address, 4500 otherwise */ + me->set_port(me, me->ip_equals(me, me_old) ? + me_old->get_port(me_old) : IKEV2_NATT_PORT); + other = other->clone(other); + other->set_port(other, other->ip_equals(other, other_old) ? + other_old->get_port(other_old) : IKEV2_NATT_PORT); + DBG1(DBG_IKE, "checking path %#H - %#H", me, other); + copy = packet->clone(packet); + copy->set_source(copy, me); + copy->set_destination(copy, other); + charon->sender->send(charon->sender, copy); + } + } + iterator->destroy(iterator); + me = packet->get_source(packet); + other = packet->get_destination(packet); + DBG1(DBG_IKE, "checking path %#H - %#H", me, other); +} + +/** * Implementation of task_t.process for initiator */ static status_t build_i(private_ike_mobike_t *this, message_t *message) @@ -235,22 +292,22 @@ static status_t build_i(private_ike_mobike_t *this, message_t *message) message->add_notify(message, FALSE, MOBIKE_SUPPORTED, chunk_empty); build_address_list(this, message); } - else + else if (message->get_exchange_type(message) == INFORMATIONAL) { - if (this->roam) + if (this->update) { message->add_notify(message, FALSE, UPDATE_SA_ADDRESSES, chunk_empty); + update_children(this); } if (this->address) { build_address_list(this, message); } - - this->natd = ike_natd_create(this->ike_sa, this->initiator); - this->natd->task.build(&this->natd->task, message); - update_children(this); + if (this->natd) + { + this->natd->task.build(&this->natd->task, message); + } } - return NEED_MORE; } @@ -267,7 +324,7 @@ static status_t process_r(private_ike_mobike_t *this, message_t *message) else if (message->get_exchange_type(message) == INFORMATIONAL) { process_payloads(this, message); - if (this->roam) + if (this->update) { host_t *me, *other; @@ -306,7 +363,7 @@ static status_t build_r(private_ike_mobike_t *this, message_t *message) { this->natd->task.build(&this->natd->task, message); } - if (this->roam) + if (this->update) { update_children(this); } @@ -324,7 +381,6 @@ static status_t process_i(private_ike_mobike_t *this, message_t *message) message->get_payload(message, SECURITY_ASSOCIATION)) { process_payloads(this, message); - return SUCCESS; } else if (message->get_exchange_type(message) == INFORMATIONAL) @@ -341,11 +397,40 @@ static status_t process_i(private_ike_mobike_t *this, message_t *message) { this->natd->task.process(&this->natd->task, message); } - if (this->roam) + if (this->update) { /* update again, as NAT state may have changed */ update_children(this); } + if (this->check) + { + host_t *me_new, *me_old, *other_new, *other_old; + + me_new = message->get_destination(message); + other_new = message->get_source(message); + me_old = this->ike_sa->get_my_host(this->ike_sa); + other_old = this->ike_sa->get_other_host(this->ike_sa); + + if (!me_new->equals(me_new, me_old)) + { + this->update = TRUE; + this->ike_sa->set_my_host(this->ike_sa, me_new->clone(me_new)); + } + if (!other_new->equals(other_new, other_old)) + { + this->update = TRUE; + this->ike_sa->set_other_host(this->ike_sa, other_new->clone(other_new)); + } + if (this->update) + { + /* start the update with the same task */ + this->check = FALSE; + this->address = FALSE; + this->natd = ike_natd_create(this->ike_sa, this->initiator); + this->ike_sa->set_pending_updates(this->ike_sa, 1); + return NEED_MORE; + } + } return SUCCESS; } return NEED_MORE; @@ -356,13 +441,21 @@ static status_t process_i(private_ike_mobike_t *this, message_t *message) */ static void roam(private_ike_mobike_t *this, bool address) { - this->roam = TRUE; + this->check = TRUE; this->address = address; this->ike_sa->set_pending_updates(this->ike_sa, this->ike_sa->get_pending_updates(this->ike_sa) + 1); } /** + * Implementation of ike_mobike_t.is_probing. + */ +static bool is_probing(private_ike_mobike_t *this) +{ + return this->check; +} + +/** * Implementation of task_t.get_type */ static task_type_t get_type(private_ike_mobike_t *this) @@ -404,6 +497,8 @@ ike_mobike_t *ike_mobike_create(ike_sa_t *ike_sa, bool initiator) private_ike_mobike_t *this = malloc_thing(private_ike_mobike_t); this->public.roam = (void(*)(ike_mobike_t*,bool))roam; + this->public.transmit = (void(*)(ike_mobike_t*,packet_t*))transmit; + this->public.is_probing = (bool(*)(ike_mobike_t*))is_probing; this->public.task.get_type = (task_type_t(*)(task_t*))get_type; this->public.task.migrate = (void(*)(task_t*,ike_sa_t*))migrate; this->public.task.destroy = (void(*)(task_t*))destroy; @@ -421,7 +516,8 @@ ike_mobike_t *ike_mobike_create(ike_sa_t *ike_sa, bool initiator) this->ike_sa = ike_sa; this->initiator = initiator; - this->roam = FALSE; + this->update = FALSE; + this->check = FALSE; this->address = TRUE; this->cookie2 = chunk_empty; this->natd = NULL; diff --git a/src/charon/sa/tasks/ike_mobike.h b/src/charon/sa/tasks/ike_mobike.h index db493c459..bb5150723 100644 --- a/src/charon/sa/tasks/ike_mobike.h +++ b/src/charon/sa/tasks/ike_mobike.h @@ -28,6 +28,7 @@ typedef struct ike_mobike_t ike_mobike_t; #include <library.h> #include <sa/ike_sa.h> #include <sa/tasks/task.h> +#include <network/packet.h> /** * @brief Task of type ike_mobike, detects and handles MOBIKE extension. @@ -58,6 +59,26 @@ struct ike_mobike_t { * @param address TRUE to include address list update */ void (*roam)(ike_mobike_t *this, bool address); + + /** + * @brief Transmision hook, called by task manager. + * + * The task manager calls this hook whenever it transmits a packet. It + * allows the mobike task to send the packet on multiple paths to do path + * probing. + * + * @param this calling object + * @param packet the packet to transmit + */ + void (*transmit)(ike_mobike_t *this, packet_t *packet); + + /** + * @brief Check if this task is probing for routability. + * + * @param this calling object + * @return TRUE if task is probing + */ + bool (*is_probing)(ike_mobike_t *this); }; /** diff --git a/src/charon/sa/tasks/ike_natd.c b/src/charon/sa/tasks/ike_natd.c index 84a28d024..4c64ff8ba 100644 --- a/src/charon/sa/tasks/ike_natd.c +++ b/src/charon/sa/tasks/ike_natd.c @@ -26,6 +26,7 @@ #include <string.h> #include <daemon.h> +#include <config/peer_cfg.h> #include <crypto/hashers/hasher.h> #include <encoding/payloads/notify_payload.h> @@ -90,7 +91,7 @@ static chunk_t generate_natd_hash(private_ike_natd_t *this, u_int64_t spi_i, spi_r; u_int16_t port; - /* prepare all requred chunks */ + /* prepare all required chunks */ spi_i = ike_sa_id->get_initiator_spi(ike_sa_id); spi_r = ike_sa_id->get_responder_spi(ike_sa_id); spi_i_chunk.ptr = (void*)&spi_i; @@ -113,6 +114,25 @@ static chunk_t generate_natd_hash(private_ike_natd_t *this, } /** + * build a faked NATD payload to enforce UDP encap + */ +static chunk_t generate_natd_hash_faked(private_ike_natd_t *this) +{ + randomizer_t *randomizer; + chunk_t chunk; + + randomizer = randomizer_create(); + if (randomizer->allocate_pseudo_random_bytes(randomizer, HASH_SIZE_SHA1, + &chunk) != SUCCESS) + { + DBG1(DBG_IKE, "unable to get random bytes for NATD fake"); + chunk = chunk_empty; + } + randomizer->destroy(randomizer); + return chunk; +} + +/** * Build a NAT detection notify payload. */ static notify_payload_t *build_natd_payload(private_ike_natd_t *this, @@ -120,12 +140,21 @@ static notify_payload_t *build_natd_payload(private_ike_natd_t *this, { chunk_t hash; notify_payload_t *notify; - ike_sa_id_t *ike_sa_id; + ike_sa_id_t *ike_sa_id; + ike_cfg_t *config; ike_sa_id = this->ike_sa->get_id(this->ike_sa); + config = this->ike_sa->get_ike_cfg(this->ike_sa); + if (config->force_encap(config) && type == NAT_DETECTION_SOURCE_IP) + { + hash = generate_natd_hash_faked(this); + } + else + { + hash = generate_natd_hash(this, ike_sa_id, host); + } notify = notify_payload_create(); notify->set_notify_type(notify, type); - hash = generate_natd_hash(this, ike_sa_id, host); notify->set_notification_data(notify, hash); chunk_free(&hash); @@ -143,11 +172,12 @@ static void process_payloads(private_ike_natd_t *this, message_t *message) chunk_t hash, src_hash, dst_hash; ike_sa_id_t *ike_sa_id; host_t *me, *other; + ike_cfg_t *config; /* Precompute NAT-D hashes for incoming NAT notify comparison */ ike_sa_id = message->get_ike_sa_id(message); - me = this->ike_sa->get_my_host(this->ike_sa); - other = this->ike_sa->get_other_host(this->ike_sa); + me = message->get_destination(message); + other = message->get_source(message); dst_hash = generate_natd_hash(this, ike_sa_id, me); src_hash = generate_natd_hash(this, ike_sa_id, other); @@ -208,7 +238,13 @@ static void process_payloads(private_ike_natd_t *this, message_t *message) this->ike_sa->set_condition(this->ike_sa, COND_NAT_HERE, !this->dst_matched); this->ike_sa->set_condition(this->ike_sa, COND_NAT_THERE, - !this->src_matched); + !this->src_matched); + config = this->ike_sa->get_ike_cfg(this->ike_sa); + if (this->dst_matched && this->src_matched && + config->force_encap(config)) + { + this->ike_sa->set_condition(this->ike_sa, COND_NAT_FAKE, TRUE); + } } } @@ -218,18 +254,46 @@ static void process_payloads(private_ike_natd_t *this, message_t *message) static status_t process_i(private_ike_natd_t *this, message_t *message) { process_payloads(this, message); - - /* if peer supports NAT-T, we switch to port 4500 even - * if no NAT is detected. MOBIKE requires this. */ - if (message->get_exchange_type(message) == IKE_SA_INIT && - this->ike_sa->supports_extension(this->ike_sa, EXT_NATT)) - { - host_t *me, *other; - me = this->ike_sa->get_my_host(this->ike_sa); - me->set_port(me, IKEV2_NATT_PORT); - other = this->ike_sa->get_other_host(this->ike_sa); - other->set_port(other, IKEV2_NATT_PORT); + if (message->get_exchange_type(message) == IKE_SA_INIT) + { + peer_cfg_t *peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + +#ifdef P2P + /* if we are on a mediated connection we have already switched to + * port 4500 and the correct destination port is already configured, + * therefore we must not switch again */ + if (peer_cfg->get_mediated_by(peer_cfg)) + { + return SUCCESS; + } +#endif /* P2P */ + + if (this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY) || +#ifdef P2P + /* if we are on a mediation connection we swith to port 4500 even + * if no NAT is detected. */ + peer_cfg->is_mediation(peer_cfg) || +#endif /* P2P */ + /* if peer supports NAT-T, we switch to port 4500 even + * if no NAT is detected. MOBIKE requires this. */ + (peer_cfg->use_mobike(peer_cfg) && + this->ike_sa->supports_extension(this->ike_sa, EXT_NATT))) + { + host_t *me, *other; + + /* do not switch if we have a custom port from mobike/NAT */ + me = this->ike_sa->get_my_host(this->ike_sa); + if (me->get_port(me) == IKEV2_UDP_PORT) + { + me->set_port(me, IKEV2_NATT_PORT); + } + other = this->ike_sa->get_other_host(this->ike_sa); + if (other->get_port(other) == IKEV2_UDP_PORT) + { + other->set_port(other, IKEV2_NATT_PORT); + } + } } return SUCCESS; @@ -245,7 +309,7 @@ static status_t build_i(private_ike_natd_t *this, message_t *message) host_t *host; /* destination is always set */ - host = this->ike_sa->get_other_host(this->ike_sa); + host = message->get_destination(message); notify = build_natd_payload(this, NAT_DETECTION_DESTINATION_IP, host); message->add_payload(message, (payload_t*)notify); @@ -254,7 +318,7 @@ static status_t build_i(private_ike_natd_t *this, message_t *message) * 2. We do a routing lookup in the kernel interface * 3. Include all possbile addresses */ - host = this->ike_sa->get_my_host(this->ike_sa); + host = message->get_source(message); if (!host->is_anyaddr(host)) { /* 1. */ notify = build_natd_payload(this, NAT_DETECTION_SOURCE_IP, host); @@ -305,11 +369,11 @@ static status_t build_r(private_ike_natd_t *this, message_t *message) if (this->src_seen && this->dst_seen) { /* initiator seems to support NAT detection, add response */ - me = this->ike_sa->get_my_host(this->ike_sa); + me = message->get_source(message); notify = build_natd_payload(this, NAT_DETECTION_SOURCE_IP, me); message->add_payload(message, (payload_t*)notify); - other = this->ike_sa->get_other_host(this->ike_sa); + other = message->get_destination(message); notify = build_natd_payload(this, NAT_DETECTION_DESTINATION_IP, other); message->add_payload(message, (payload_t*)notify); } diff --git a/src/charon/sa/tasks/ike_p2p.c b/src/charon/sa/tasks/ike_p2p.c new file mode 100644 index 000000000..de5a2e30e --- /dev/null +++ b/src/charon/sa/tasks/ike_p2p.c @@ -0,0 +1,851 @@ +/** + * @file ike_p2p.c + * + * @brief Implementation of the ike_p2p task. + * + */ + +/* + * Copyright (C) 2007 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 "ike_p2p.h" + +#include <string.h> + +#include <daemon.h> +#include <config/peer_cfg.h> +#include <encoding/payloads/id_payload.h> +#include <encoding/payloads/notify_payload.h> +#include <encoding/payloads/endpoint_notify.h> +#include <processing/jobs/mediation_job.h> + +#define P2P_SESSIONID_LEN 8 +#define P2P_SESSIONKEY_LEN 16 + +// FIXME: proposed values +#define P2P_SESSIONID_MIN_LEN 4 +#define P2P_SESSIONID_MAX_LEN 16 +#define P2P_SESSIONKEY_MIN_LEN 8 +#define P2P_SESSIONKEY_MAX_LEN 64 + + +typedef struct private_ike_p2p_t private_ike_p2p_t; + +/** + * Private members of a ike_p2p_t task. + */ +struct private_ike_p2p_t { + + /** + * Public methods and task_t interface. + */ + ike_p2p_t public; + + /** + * Assigned IKE_SA. + */ + ike_sa_t *ike_sa; + + /** + * Are we the initiator? + */ + bool initiator; + + /** + * Is this a mediation connection? + */ + bool mediation; + + /** + * Is this the response from another peer? + */ + bool response; + + /** + * Gathered endpoints + */ + linked_list_t *local_endpoints; + + /** + * Parsed endpoints + */ + linked_list_t *remote_endpoints; + + /** + * Did the peer request a callback? + */ + bool callback; + + /** + * Did the connect fail? + */ + bool failed; + + /** + * Was there anything wrong with the payloads? + */ + bool invalid_syntax; + + /** + * The requested peer + */ + identification_t *peer_id; + /** + * Received ID used for connectivity checks + */ + chunk_t session_id; + + /** + * Received key used for connectivity checks + */ + chunk_t session_key; + + /** + * Peer config of the mediated connection + */ + peer_cfg_t *mediated_cfg; + +}; + +// ----------------------------------------------------------------------------- + +/** + * Adds a list of endpoints as notifies to a given message + */ +static void add_endpoints_to_message(message_t *message, linked_list_t *endpoints) +{ + iterator_t *iterator; + endpoint_notify_t *endpoint; + + iterator = endpoints->create_iterator(endpoints, TRUE); + while (iterator->iterate(iterator, (void**)&endpoint)) + { + message->add_payload(message, (payload_t*)endpoint->build_notify(endpoint)); + } + iterator->destroy(iterator); +} + +/** + * Gathers endpoints and adds them to the current message + */ +static void gather_and_add_endpoints(private_ike_p2p_t *this, message_t *message) +{ + iterator_t *iterator; + host_t *addr, *host; + u_int16_t port; + + // get the port that is used to communicate with the ms + host = this->ike_sa->get_my_host(this->ike_sa); + port = host->get_port(host); + + iterator = charon->kernel_interface->create_address_iterator( + charon->kernel_interface); + while (iterator->iterate(iterator, (void**)&addr)) + { + host = addr->clone(addr); + host->set_port(host, port); + + this->local_endpoints->insert_last(this->local_endpoints, + endpoint_notify_create_from_host(HOST, host, NULL)); + + host->destroy(host); + } + iterator->destroy(iterator); + + host = this->ike_sa->get_server_reflexive_host(this->ike_sa); + if (host) + { + this->local_endpoints->insert_last(this->local_endpoints, + endpoint_notify_create_from_host(SERVER_REFLEXIVE, host, + this->ike_sa->get_my_host(this->ike_sa))); + } + + add_endpoints_to_message(message, this->local_endpoints); +} + +/** + * read notifys from message and evaluate them + */ +static void process_payloads(private_ike_p2p_t *this, message_t *message) +{ + iterator_t *iterator; + payload_t *payload; + + iterator = message->get_payload_iterator(message); + while (iterator->iterate(iterator, (void**)&payload)) + { + if (payload->get_type(payload) != NOTIFY) + { + continue; + } + + notify_payload_t *notify = (notify_payload_t*)payload; + + switch (notify->get_notify_type(notify)) + { + case P2P_CONNECT_FAILED: + { + DBG2(DBG_IKE, "received P2P_CONNECT_FAILED notify"); + this->failed = TRUE; + break; + } + case P2P_MEDIATION: + { + DBG2(DBG_IKE, "received P2P_MEDIATION notify"); + this->mediation = TRUE; + break; + } + case P2P_ENDPOINT: + { + endpoint_notify_t *endpoint = endpoint_notify_create_from_payload(notify); + if (!endpoint) + { + DBG1(DBG_IKE, "received invalid P2P_ENDPOINT notify"); + break; + } + DBG2(DBG_IKE, "received P2P_ENDPOINT notify"); + + this->remote_endpoints->insert_last(this->remote_endpoints, endpoint); + break; + } + case P2P_CALLBACK: + { + DBG2(DBG_IKE, "received P2P_CALLBACK notify"); + this->callback = TRUE; + break; + } + case P2P_SESSIONID: + { + chunk_free(&this->session_id); + this->session_id = chunk_clone(notify->get_notification_data(notify)); + DBG3(DBG_IKE, "received p2p_sessionid %B", &this->session_id); + break; + } + case P2P_SESSIONKEY: + { + chunk_free(&this->session_key); + this->session_key = chunk_clone(notify->get_notification_data(notify)); + DBG4(DBG_IKE, "received p2p_sessionkey %B", &this->session_key); + break; + } + case P2P_RESPONSE: + { + DBG2(DBG_IKE, "received P2P_RESPONSE notify"); + this->response = TRUE; + break; + } + default: + break; + } + } + iterator->destroy(iterator); +} + +// ----------------------------------------------------------------------------- + +/** + * Implementation of task_t.process for initiator + */ +static status_t build_i(private_ike_p2p_t *this, message_t *message) +{ + switch(message->get_exchange_type(message)) + { + case IKE_SA_INIT: + { + peer_cfg_t *peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa); + if (peer_cfg->is_mediation(peer_cfg)) + { + DBG2(DBG_IKE, "adding P2P_MEDIATION"); + message->add_notify(message, FALSE, P2P_MEDIATION, chunk_empty); + } + else + { + return SUCCESS; + } + break; + } + case IKE_AUTH: + { + if (this->ike_sa->has_condition(this->ike_sa, COND_NAT_HERE)) + { + endpoint_notify_t *endpoint = endpoint_notify_create_from_host(SERVER_REFLEXIVE, NULL, NULL); + message->add_payload(message, (payload_t*)endpoint->build_notify(endpoint)); + endpoint->destroy(endpoint); + } + break; + } + case P2P_CONNECT: + { + id_payload_t *id_payload; + randomizer_t *rand = randomizer_create(); + + id_payload = id_payload_create_from_identification(ID_PEER, this->peer_id); + message->add_payload(message, (payload_t*)id_payload); + + if (!this->response) + { + // only the initiator creates a session ID. the responder returns + // the session ID that it received from the initiator + if (rand->allocate_pseudo_random_bytes(rand, + P2P_SESSIONID_LEN, &this->session_id) != SUCCESS) + { + DBG1(DBG_IKE, "unable to generate session ID for P2P_CONNECT"); + rand->destroy(rand); + return FAILED; + } + } + + if (rand->allocate_pseudo_random_bytes(rand, + P2P_SESSIONKEY_LEN, &this->session_key) != SUCCESS) + { + DBG1(DBG_IKE, "unable to generate session key for P2P_CONNECT"); + rand->destroy(rand); + return FAILED; + } + + rand->destroy(rand); + + message->add_notify(message, FALSE, P2P_SESSIONID, this->session_id); + message->add_notify(message, FALSE, P2P_SESSIONKEY, this->session_key); + + if (this->response) + { + message->add_notify(message, FALSE, P2P_RESPONSE, chunk_empty); + } + else + { + // FIXME: should we make that configurable + message->add_notify(message, FALSE, P2P_CALLBACK, chunk_empty); + } + + gather_and_add_endpoints(this, message); + + break; + } + } + + return NEED_MORE; +} + +/** + * Implementation of task_t.process for responder + */ +static status_t process_r(private_ike_p2p_t *this, message_t *message) +{ + switch(message->get_exchange_type(message)) + { + case P2P_CONNECT: + { + id_payload_t *id_payload; + id_payload = (id_payload_t*)message->get_payload(message, ID_PEER); + if (!id_payload) + { + DBG1(DBG_IKE, "received P2P_CONNECT without ID_PEER payload, aborting"); + break; + } + this->peer_id = id_payload->get_identification(id_payload); + + process_payloads(this, message); + + if (this->callback) + { + DBG1(DBG_IKE, "received P2P_CALLBACK for '%D'", this->peer_id); + break; + } + + if (!this->session_id.ptr) + { + DBG1(DBG_IKE, "received P2P_CONNECT without P2P_SESSIONID notify, aborting"); + this->invalid_syntax = TRUE; + break; + } + + if (!this->session_key.ptr) + { + DBG1(DBG_IKE, "received P2P_CONNECT without P2P_SESSIONKEY notify, aborting"); + this->invalid_syntax = TRUE; + break; + } + + if (!this->remote_endpoints->get_count(this->remote_endpoints)) + { + DBG1(DBG_IKE, "received P2P_CONNECT without any P2P_ENDPOINT payloads, aborting"); + this->invalid_syntax = TRUE; + break; + } + + DBG1(DBG_IKE, "received P2P_CONNECT"); + + break; + } + } + + return NEED_MORE; +} + +/** + * Implementation of task_t.build for responder + */ +static status_t build_r(private_ike_p2p_t *this, message_t *message) +{ + switch(message->get_exchange_type(message)) + { + case P2P_CONNECT: + { + if (this->invalid_syntax) + { + message->add_notify(message, TRUE, INVALID_SYNTAX, chunk_empty); + break; + } + + if (this->callback) + { + charon->connect_manager->check_and_initiate(charon->connect_manager, + this->ike_sa->get_id(this->ike_sa), + this->ike_sa->get_my_id(this->ike_sa), this->peer_id); + return SUCCESS; + } + + if (this->response) + { + // FIXME: handle result of set_responder_data + // as initiator, upon receiving a response from another peer, + // update the checklist and start sending checks + charon->connect_manager->set_responder_data(charon->connect_manager, + this->session_id, this->session_key, this->remote_endpoints); + } + else + { + // FIXME: handle result of set_initiator_data + // as responder, create a checklist with the initiator's data + charon->connect_manager->set_initiator_data(charon->connect_manager, + this->peer_id, this->ike_sa->get_my_id(this->ike_sa), + this->session_id, this->session_key, this->remote_endpoints, + FALSE); + if (this->ike_sa->respond(this->ike_sa, this->peer_id, + this->session_id) != SUCCESS) + { + return FAILED; + } + } + + break; + } + } + return SUCCESS; +} + +/** + * Implementation of task_t.process for initiator + */ +static status_t process_i(private_ike_p2p_t *this, message_t *message) +{ + switch(message->get_exchange_type(message)) + { + case IKE_SA_INIT: + { + process_payloads(this, message); + + if (!this->mediation) + { + DBG1(DBG_IKE, "server did not return a P2P_MEDIATION, aborting"); + return FAILED; + } + + return NEED_MORE; + } + case IKE_AUTH: + { + process_payloads(this, message); + + //FIXME: we should update the server reflexive endpoint somehow, if mobike notices a change + + endpoint_notify_t *reflexive; + if (this->remote_endpoints->get_first(this->remote_endpoints, (void**)&reflexive) == SUCCESS && + reflexive->get_type(reflexive) == SERVER_REFLEXIVE) + {//FIXME: should we accept this endpoint even if we did not send a request? + host_t *endpoint = reflexive->get_host(reflexive); + DBG2(DBG_IKE, "received server reflexive endpoint %#H", endpoint); + + this->ike_sa->set_server_reflexive_host(this->ike_sa, endpoint->clone(endpoint)); + } + + // FIXME: what if it failed? e.g. AUTH failure + SIG(CHILD_UP_SUCCESS, "established mediation connection without CHILD_SA successfully"); + + break; + } + case P2P_CONNECT: + { + process_payloads(this, message); + + if (this->failed) + { + DBG1(DBG_IKE, "peer '%D' is not online", this->peer_id); + // FIXME: notify the mediated connection (job?) + // FIXME: probably delete the created checklist, at least as responder + } + else + { + if (this->response) + { + // FIXME: handle result of set_responder_data + // as responder, we update the checklist and start sending checks + charon->connect_manager->set_responder_data(charon->connect_manager, + this->session_id, this->session_key, this->local_endpoints); + } + else + { + // FIXME: handle result of set_initiator_data + // as initiator, we create a checklist and set the initiator's data + charon->connect_manager->set_initiator_data(charon->connect_manager, + this->ike_sa->get_my_id(this->ike_sa), this->peer_id, + this->session_id, this->session_key, this->local_endpoints, + TRUE); + } + } + break; + } + } + return SUCCESS; +} + +// ----------------------------------------------------------------------------- + +/** + * Implementation of task_t.process for initiator (mediation server) + */ +static status_t build_i_ms(private_ike_p2p_t *this, message_t *message) +{ + switch(message->get_exchange_type(message)) + { + case P2P_CONNECT: + { + id_payload_t *id_payload = id_payload_create_from_identification(ID_PEER, this->peer_id); + message->add_payload(message, (payload_t*)id_payload); + + if (this->callback) + { + message->add_notify(message, FALSE, P2P_CALLBACK, chunk_empty); + } + else + { + notify_payload_t *notify; + + if (this->response) + { + message->add_notify(message, FALSE, P2P_RESPONSE, chunk_empty); + } + + message->add_notify(message, FALSE, P2P_SESSIONID, this->session_id); + message->add_notify(message, FALSE, P2P_SESSIONKEY, this->session_key); + + add_endpoints_to_message(message, this->remote_endpoints); + } + + break; + } + } + + return NEED_MORE; +} + +/** + * Implementation of task_t.process for responder (mediation server) + */ +static status_t process_r_ms(private_ike_p2p_t *this, message_t *message) +{ + switch(message->get_exchange_type(message)) + { + case IKE_SA_INIT: + { + process_payloads(this, message); + return this->mediation ? NEED_MORE : SUCCESS; + } + case IKE_AUTH: + { + process_payloads(this, message); + break; + } + case P2P_CONNECT: + { + id_payload_t *id_payload; + id_payload = (id_payload_t*)message->get_payload(message, ID_PEER); + if (!id_payload) + { + DBG1(DBG_IKE, "received P2P_CONNECT without ID_PEER payload, aborting"); + this->invalid_syntax = TRUE; + break; + } + + this->peer_id = id_payload->get_identification(id_payload); + + process_payloads(this, message); + + if (!this->session_id.ptr) + { + DBG1(DBG_IKE, "received P2P_CONNECT without P2P_SESSIONID notify, aborting"); + this->invalid_syntax = TRUE; + break; + } + + if (!this->session_key.ptr) + { + DBG1(DBG_IKE, "received P2P_CONNECT without P2P_SESSIONKEY notify, aborting"); + this->invalid_syntax = TRUE; + break; + } + + if (!this->remote_endpoints->get_count(this->remote_endpoints)) + { + DBG1(DBG_IKE, "received P2P_CONNECT without any P2P_ENDPOINT payloads, aborting"); + this->invalid_syntax = TRUE; + break; + } + + break; + } + } + + return NEED_MORE; +} + +/** + * Implementation of task_t.build for responder (mediation server) + */ +static status_t build_r_ms(private_ike_p2p_t *this, message_t *message) +{ + switch(message->get_exchange_type(message)) + { + case IKE_SA_INIT: + { + message->add_notify(message, FALSE, P2P_MEDIATION, chunk_empty); + return NEED_MORE; + } + case IKE_AUTH: + { + endpoint_notify_t *endpoint; + if (this->remote_endpoints->get_first(this->remote_endpoints, (void**)&endpoint) == SUCCESS && + endpoint->get_type(endpoint) == SERVER_REFLEXIVE) + { + host_t *host = this->ike_sa->get_other_host(this->ike_sa); + + DBG2(DBG_IKE, "received request for a server reflexive endpoint " + "sending: %#H", host); + + endpoint = endpoint_notify_create_from_host(SERVER_REFLEXIVE, host, NULL); + message->add_payload(message, (payload_t*)endpoint->build_notify(endpoint)); + } + + charon->mediation_manager->update_sa_id(charon->mediation_manager, + this->ike_sa->get_other_id(this->ike_sa), + this->ike_sa->get_id(this->ike_sa)); + + SIG(CHILD_UP_SUCCESS, "established mediation connection without CHILD_SA successfully"); + + break; + } + case P2P_CONNECT: + { + if (this->invalid_syntax) + { + message->add_notify(message, TRUE, INVALID_SYNTAX, chunk_empty); + break; + } + + ike_sa_id_t *peer_sa; + if (this->callback) + { + peer_sa = charon->mediation_manager->check_and_register(charon->mediation_manager, + this->peer_id, this->ike_sa->get_other_id(this->ike_sa)); + } + else + { + peer_sa = charon->mediation_manager->check(charon->mediation_manager, + this->peer_id); + } + + if (!peer_sa) + { + // the peer is not online + message->add_notify(message, TRUE, P2P_CONNECT_FAILED, chunk_empty); + break; + } + + job_t *job = (job_t*)mediation_job_create(this->peer_id, + this->ike_sa->get_other_id(this->ike_sa), this->session_id, + this->session_key, this->remote_endpoints, this->response); + charon->processor->queue_job(charon->processor, job); + + break; + } + } + return SUCCESS; +} + +/** + * Implementation of task_t.process for initiator (mediation server) + */ +static status_t process_i_ms(private_ike_p2p_t *this, message_t *message) +{ + switch(message->get_exchange_type(message)) + { + case P2P_CONNECT: + { + break; + } + } + return SUCCESS; +} + +// ----------------------------------------------------------------------------- + +/** + * Implementation of ike_p2p.connect + */ +static void p2p_connect(private_ike_p2p_t *this, identification_t *peer_id) +{ + this->peer_id = peer_id->clone(peer_id); +} + +/** + * Implementation of ike_p2p.respond + */ +static void p2p_respond(private_ike_p2p_t *this, identification_t *peer_id, + chunk_t session_id) +{ + this->peer_id = peer_id->clone(peer_id); + this->session_id = chunk_clone(session_id); + this->response = TRUE; +} + +/** + * Implementation of ike_p2p.callback + */ +static void p2p_callback(private_ike_p2p_t *this, identification_t *peer_id) +{ + this->peer_id = peer_id->clone(peer_id); + this->callback = TRUE; +} + +/** + * Implementation of ike_p2p.relay + */ +static void relay(private_ike_p2p_t *this, identification_t *requester, chunk_t session_id, + chunk_t session_key, linked_list_t *endpoints, bool response) +{ + this->peer_id = requester->clone(requester); + this->session_id = chunk_clone(session_id); + this->session_key = chunk_clone(session_key); + this->remote_endpoints = endpoints->clone_offset(endpoints, offsetof(endpoint_notify_t, clone)); + this->response = response; +} + +/** + * Implementation of task_t.get_type + */ +static task_type_t get_type(private_ike_p2p_t *this) +{ + return IKE_P2P; +} + +/** + * Implementation of task_t.migrate + */ +static void migrate(private_ike_p2p_t *this, ike_sa_t *ike_sa) +{ + this->ike_sa = ike_sa; +} + +/** + * Implementation of task_t.destroy + */ +static void destroy(private_ike_p2p_t *this) +{ + DESTROY_IF(this->peer_id); + + chunk_free(&this->session_id); + chunk_free(&this->session_key); + + this->local_endpoints->destroy_offset(this->local_endpoints, offsetof(endpoint_notify_t, destroy)); + this->remote_endpoints->destroy_offset(this->remote_endpoints, offsetof(endpoint_notify_t, destroy)); + + DESTROY_IF(this->mediated_cfg); + free(this); +} + +/* + * Described in header. + */ +ike_p2p_t *ike_p2p_create(ike_sa_t *ike_sa, bool initiator) +{ + private_ike_p2p_t *this = malloc_thing(private_ike_p2p_t); + + this->public.task.get_type = (task_type_t(*)(task_t*))get_type; + this->public.task.migrate = (void(*)(task_t*,ike_sa_t*))migrate; + this->public.task.destroy = (void(*)(task_t*))destroy; + + ike_sa_id_t *id = ike_sa->get_id(ike_sa); + if (id->is_initiator(id)) + { + if (initiator) + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_i; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_i; + } + else + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_r; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_r; + } + } + else + { + // mediation server + if (initiator) + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_i_ms; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_i_ms; + } + else + { + this->public.task.build = (status_t(*)(task_t*,message_t*))build_r_ms; + this->public.task.process = (status_t(*)(task_t*,message_t*))process_r_ms; + } + } + + this->public.connect = (void(*)(ike_p2p_t*,identification_t*))p2p_connect; + this->public.respond = (void(*)(ike_p2p_t*,identification_t*,chunk_t))p2p_respond; + this->public.callback = (void(*)(ike_p2p_t*,identification_t*))p2p_callback; + this->public.relay = (void(*)(ike_p2p_t*,identification_t*,chunk_t,chunk_t,linked_list_t*,bool))relay; + + this->ike_sa = ike_sa; + this->initiator = initiator; + + this->peer_id = NULL; + this->session_id = chunk_empty; + this->session_key = chunk_empty; + this->local_endpoints = linked_list_create(); + this->remote_endpoints = linked_list_create(); + this->mediation = FALSE; + this->response = FALSE; + this->callback = FALSE; + this->failed = FALSE; + this->invalid_syntax = FALSE; + + this->mediated_cfg = NULL; + + return &this->public; +} diff --git a/src/charon/sa/tasks/ike_p2p.h b/src/charon/sa/tasks/ike_p2p.h new file mode 100644 index 000000000..327ac49d8 --- /dev/null +++ b/src/charon/sa/tasks/ike_p2p.h @@ -0,0 +1,110 @@ +/** + * @file ike_p2p.h + * + * @brief Interface ike_p2p_t. + * + */ + +/* + * Copyright (C) 2007 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. + */ + +#ifndef IKE_P2P_H_ +#define IKE_P2P_H_ + +typedef struct ike_p2p_t ike_p2p_t; + +#include <library.h> +#include <sa/ike_sa.h> +#include <sa/tasks/task.h> + +/** + * @brief Task of type IKE_P2P, detects and handles P2P-NAT-T extensions. + * + * This tasks handles the P2P_MEDIATION notify exchange to setup a mediation + * connection, allows to initiate mediated connections using P2P_CONNECT + * exchanges and to request reflexive addresses from the mediation server using + * P2P_ENDPOINT notifies. + * + * @note This task has to be activated before the IKE_AUTH task, because that + * task generates the IKE_SA_INIT message so that no more payloads can be added + * to it afterwards. + * + * @b Constructors: + * - ike_p2p_create() + * + * @ingroup tasks + */ +struct ike_p2p_t { + + /** + * Implements the task_t interface + */ + task_t task; + + /** + * @brief Initiates a connection with another peer (i.e. sends a P2P_CONNECT + * to the mediation server) + * + * @param this object + * @param peer_id ID of the other peer (gets cloned) + */ + void (*connect)(ike_p2p_t *this, identification_t *peer_id); + + /** + * @brief Responds to a P2P_CONNECT from another peer (i.e. sends a P2P_CONNECT + * to the mediation server) + * + * @param this object + * @param peer_id ID of the other peer (gets cloned) + * @param session_id the session ID as provided by the initiator (gets cloned) + */ + void (*respond)(ike_p2p_t *this, identification_t *peer_id, chunk_t session_id); + + /** + * @brief Sends a P2P_CALLBACK to a peer that previously requested another peer. + * + * @param this object + * @param peer_id ID of the other peer (gets cloned) + */ + void (*callback)(ike_p2p_t *this, identification_t *peer_id); + + /** + * @brief Relays data to another peer (i.e. sends a P2P_CONNECT to the peer) + * + * Data gets cloned. + * + * @param this object + * @param requester ID of the requesting peer + * @param session_id content of the P2P_SESSIONID notify + * @param session_key content of the P2P_SESSIONKEY notify + * @param endpoints endpoints + * @param response TRUE if this is a response + */ + void (*relay)(ike_p2p_t *this, identification_t *requester, chunk_t session_id, + chunk_t session_key, linked_list_t *endpoints, bool response); + +}; + +/** + * @brief Create a new ike_p2p task. + * + * @param ike_sa IKE_SA this task works for + * @param initiator TRUE if taks is initiated by us + * @return ike_p2p task to handle by the task_manager + */ +ike_p2p_t *ike_p2p_create(ike_sa_t *ike_sa, bool initiator); + + +#endif /*IKE_P2P_H_*/ diff --git a/src/charon/sa/tasks/task.c b/src/charon/sa/tasks/task.c index 713403d47..e9d0c4da1 100644 --- a/src/charon/sa/tasks/task.c +++ b/src/charon/sa/tasks/task.c @@ -6,6 +6,7 @@ */ /* + * Copyright (C) 2007 Tobias Brunner * Copyright (C) 2007 Martin Willi * Hochschule fuer Technik Rapperswil * @@ -33,6 +34,9 @@ ENUM(task_type_names, IKE_INIT, CHILD_REKEY, "IKE_REAUTH", "IKE_DELETE", "IKE_DPD", +#ifdef P2P + "IKE_P2P", +#endif /* P2P */ "CHILD_CREATE", "CHILD_DELETE", "CHILD_REKEY", diff --git a/src/charon/sa/tasks/task.h b/src/charon/sa/tasks/task.h index ff60ea816..dd2bb8a83 100644 --- a/src/charon/sa/tasks/task.h +++ b/src/charon/sa/tasks/task.h @@ -6,6 +6,7 @@ */ /* + * Copyright (C) 2007 Tobias Brunner * Copyright (C) 2006 Martin Willi * Hochschule fuer Technik Rapperswil * @@ -56,6 +57,10 @@ enum task_type_t { IKE_DELETE, /** liveness check */ IKE_DPD, +#ifdef P2P + /** handle P2P-NAT-T stuff */ + IKE_P2P, +#endif /* P2P */ /** establish a CHILD_SA within an IKE_SA */ CHILD_CREATE, /** delete an established CHILD_SA */ |