| /* |
| * Copyright (C) 2010 Martin Willi |
| * Copyright (C) 2010 revosec AG |
| * |
| * 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 "ha_cache.h" |
| |
| #include <collections/hashtable.h> |
| #include <collections/linked_list.h> |
| #include <threading/mutex.h> |
| #include <processing/jobs/callback_job.h> |
| |
| typedef struct private_ha_cache_t private_ha_cache_t; |
| |
| /** |
| * Private data of an ha_cache_t object. |
| */ |
| struct private_ha_cache_t { |
| |
| /** |
| * Public ha_cache_t interface. |
| */ |
| ha_cache_t public; |
| |
| /** |
| * Kernel helper functions |
| */ |
| ha_kernel_t *kernel; |
| |
| /** |
| * Socket to send sync messages over |
| */ |
| ha_socket_t *socket; |
| |
| /** |
| * Tunnel securing sync messages |
| */ |
| ha_tunnel_t *tunnel; |
| |
| /** |
| * Total number of segments |
| */ |
| u_int count; |
| |
| /** |
| * cached entries (ike_sa_t, entry_t) |
| */ |
| hashtable_t *cache; |
| |
| /** |
| * Mutex to lock cache |
| */ |
| mutex_t *mutex; |
| }; |
| |
| /** |
| * Cache entry for an IKE_SA |
| */ |
| typedef struct { |
| /* segment this entry is associate to */ |
| u_int segment; |
| /* ADD message */ |
| ha_message_t *add; |
| /* list of updates UPDATE message */ |
| linked_list_t *updates; |
| /* last initiator mid */ |
| ha_message_t *midi; |
| /* last responder mid */ |
| ha_message_t *midr; |
| /* last IV update */ |
| ha_message_t *iv; |
| } entry_t; |
| |
| /** |
| * Create a entry with an add message |
| */ |
| static entry_t *entry_create(ha_message_t *add) |
| { |
| entry_t *entry; |
| |
| INIT(entry, |
| .add = add, |
| .updates = linked_list_create(), |
| ); |
| return entry; |
| } |
| |
| /** |
| * clean up a entry |
| */ |
| static void entry_destroy(entry_t *entry) |
| { |
| entry->updates->destroy_offset(entry->updates, |
| offsetof(ha_message_t, destroy)); |
| entry->add->destroy(entry->add); |
| DESTROY_IF(entry->midi); |
| DESTROY_IF(entry->midr); |
| DESTROY_IF(entry->iv); |
| free(entry); |
| } |
| |
| METHOD(ha_cache_t, cache, void, |
| private_ha_cache_t *this, ike_sa_t *ike_sa, ha_message_t *message) |
| { |
| entry_t *entry; |
| |
| this->mutex->lock(this->mutex); |
| switch (message->get_type(message)) |
| { |
| case HA_IKE_ADD: |
| entry = entry_create(message); |
| entry = this->cache->put(this->cache, ike_sa, entry); |
| if (entry) |
| { |
| entry_destroy(entry); |
| } |
| break; |
| case HA_IKE_UPDATE: |
| entry = this->cache->get(this->cache, ike_sa); |
| if (entry) |
| { |
| entry->segment = this->kernel->get_segment(this->kernel, |
| ike_sa->get_other_host(ike_sa)); |
| entry->updates->insert_last(entry->updates, message); |
| break; |
| } |
| message->destroy(message); |
| break; |
| case HA_IKE_MID_INITIATOR: |
| entry = this->cache->get(this->cache, ike_sa); |
| if (entry) |
| { |
| DESTROY_IF(entry->midi); |
| entry->midi = message; |
| break; |
| } |
| message->destroy(message); |
| break; |
| case HA_IKE_MID_RESPONDER: |
| entry = this->cache->get(this->cache, ike_sa); |
| if (entry) |
| { |
| DESTROY_IF(entry->midr); |
| entry->midr = message; |
| break; |
| } |
| message->destroy(message); |
| break; |
| case HA_IKE_IV: |
| entry = this->cache->get(this->cache, ike_sa); |
| if (entry) |
| { |
| DESTROY_IF(entry->iv); |
| entry->iv = message; |
| break; |
| } |
| message->destroy(message); |
| break; |
| case HA_IKE_DELETE: |
| entry = this->cache->remove(this->cache, ike_sa); |
| if (entry) |
| { |
| entry_destroy(entry); |
| } |
| message->destroy(message); |
| break; |
| default: |
| message->destroy(message); |
| break; |
| } |
| this->mutex->unlock(this->mutex); |
| } |
| |
| METHOD(ha_cache_t, delete_, void, |
| private_ha_cache_t *this, ike_sa_t *ike_sa) |
| { |
| entry_t *entry; |
| |
| this->mutex->lock(this->mutex); |
| entry = this->cache->remove(this->cache, ike_sa); |
| if (entry) |
| { |
| entry_destroy(entry); |
| } |
| this->mutex->unlock(this->mutex); |
| } |
| |
| /** |
| * Rekey all children of an IKE_SA |
| */ |
| static status_t rekey_children(ike_sa_t *ike_sa) |
| { |
| enumerator_t *enumerator; |
| child_sa_t *child_sa; |
| status_t status = SUCCESS; |
| linked_list_t *children; |
| struct { |
| protocol_id_t protocol; |
| uint32_t spi; |
| } *info; |
| |
| children = linked_list_create(); |
| enumerator = ike_sa->create_child_sa_enumerator(ike_sa); |
| while (enumerator->enumerate(enumerator, &child_sa)) |
| { |
| INIT(info, |
| .protocol = child_sa->get_protocol(child_sa), |
| .spi = child_sa->get_spi(child_sa, TRUE), |
| ); |
| children->insert_last(children, info); |
| } |
| enumerator->destroy(enumerator); |
| |
| enumerator = children->create_enumerator(children); |
| while (enumerator->enumerate(enumerator, &info)) |
| { |
| if (ike_sa->supports_extension(ike_sa, EXT_MS_WINDOWS) && |
| ike_sa->has_condition(ike_sa, COND_NAT_THERE)) |
| { |
| /* NATed Windows clients don't accept CHILD_SA rekeying, but fail |
| * with an "invalid situation" error. We just close the CHILD_SA, |
| * Windows will reestablish it immediately if required. */ |
| DBG1(DBG_CFG, "resyncing CHILD_SA using a delete"); |
| status = ike_sa->delete_child_sa(ike_sa, info->protocol, info->spi, |
| FALSE); |
| } |
| else |
| { |
| DBG1(DBG_CFG, "resyncing CHILD_SA using a rekey"); |
| status = ike_sa->rekey_child_sa(ike_sa, info->protocol, info->spi); |
| } |
| if (status == DESTROY_ME) |
| { |
| break; |
| } |
| } |
| enumerator->destroy(enumerator); |
| children->destroy_function(children, free); |
| |
| return status; |
| } |
| |
| /** |
| * Trigger rekeying of CHILD_SA in segment |
| */ |
| static void rekey_segment(private_ha_cache_t *this, u_int segment) |
| { |
| ike_sa_t *ike_sa; |
| enumerator_t *enumerator; |
| linked_list_t *list; |
| ike_sa_id_t *id; |
| |
| list = linked_list_create(); |
| |
| enumerator = charon->ike_sa_manager->create_enumerator( |
| charon->ike_sa_manager, TRUE); |
| while (enumerator->enumerate(enumerator, &ike_sa)) |
| { |
| if (this->tunnel && this->tunnel->is_sa(this->tunnel, ike_sa)) |
| { |
| continue; |
| } |
| if (ike_sa->get_state(ike_sa) == IKE_ESTABLISHED && |
| this->kernel->get_segment(this->kernel, |
| ike_sa->get_other_host(ike_sa)) == segment) |
| { |
| id = ike_sa->get_id(ike_sa); |
| list->insert_last(list, id->clone(id)); |
| } |
| } |
| enumerator->destroy(enumerator); |
| |
| while (list->remove_last(list, (void**)&id) == SUCCESS) |
| { |
| ike_sa = charon->ike_sa_manager->checkout(charon->ike_sa_manager, id); |
| if (ike_sa) |
| { |
| if (rekey_children(ike_sa) != DESTROY_ME) |
| { |
| charon->ike_sa_manager->checkin( |
| charon->ike_sa_manager, ike_sa); |
| } |
| else |
| { |
| charon->ike_sa_manager->checkin_and_destroy( |
| charon->ike_sa_manager, ike_sa); |
| } |
| } |
| id->destroy(id); |
| } |
| list->destroy(list); |
| } |
| |
| METHOD(ha_cache_t, resync, void, |
| private_ha_cache_t *this, u_int segment) |
| { |
| enumerator_t *enumerator, *updates; |
| ike_sa_t *ike_sa; |
| entry_t *entry; |
| ha_message_t *message; |
| |
| DBG1(DBG_CFG, "resyncing HA segment %d", segment); |
| |
| this->mutex->lock(this->mutex); |
| enumerator = this->cache->create_enumerator(this->cache); |
| while (enumerator->enumerate(enumerator, &ike_sa, &entry)) |
| { |
| if (entry->segment == segment) |
| { |
| this->socket->push(this->socket, entry->add); |
| updates = entry->updates->create_enumerator(entry->updates); |
| while (updates->enumerate(updates, &message)) |
| { |
| this->socket->push(this->socket, message); |
| } |
| updates->destroy(updates); |
| if (entry->midi) |
| { |
| this->socket->push(this->socket, entry->midi); |
| } |
| if (entry->midr) |
| { |
| this->socket->push(this->socket, entry->midr); |
| } |
| if (entry->iv) |
| { |
| this->socket->push(this->socket, entry->iv); |
| } |
| } |
| } |
| enumerator->destroy(enumerator); |
| this->mutex->unlock(this->mutex); |
| |
| rekey_segment(this, segment); |
| } |
| |
| /** |
| * Request a resync of all segments |
| */ |
| static job_requeue_t request_resync(private_ha_cache_t *this) |
| { |
| ha_message_t *message; |
| int i; |
| |
| DBG1(DBG_CFG, "requesting HA resynchronization"); |
| |
| message = ha_message_create(HA_RESYNC); |
| for (i = 1; i <= this->count; i++) |
| { |
| message->add_attribute(message, HA_SEGMENT, i); |
| } |
| this->socket->push(this->socket, message); |
| message->destroy(message); |
| return JOB_REQUEUE_NONE; |
| } |
| |
| METHOD(ha_cache_t, destroy, void, |
| private_ha_cache_t *this) |
| { |
| this->cache->destroy(this->cache); |
| this->mutex->destroy(this->mutex); |
| free(this); |
| } |
| |
| /** |
| * See header |
| */ |
| ha_cache_t *ha_cache_create(ha_kernel_t *kernel, ha_socket_t *socket, |
| ha_tunnel_t *tunnel, bool sync, u_int count) |
| { |
| private_ha_cache_t *this; |
| |
| INIT(this, |
| .public = { |
| .cache = _cache, |
| .delete = _delete_, |
| .resync = _resync, |
| .destroy = _destroy, |
| }, |
| .count = count, |
| .kernel = kernel, |
| .socket = socket, |
| .tunnel = tunnel, |
| .cache = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 8), |
| .mutex = mutex_create(MUTEX_TYPE_DEFAULT), |
| ); |
| |
| if (sync) |
| { |
| /* request a resync as soon as we are up */ |
| lib->scheduler->schedule_job(lib->scheduler, (job_t*) |
| callback_job_create_with_prio((callback_job_cb_t)request_resync, |
| this, NULL, NULL, JOB_PRIO_CRITICAL), 1); |
| } |
| return &this->public; |
| } |