If the original responder updates its list of additional addresses we check if the remote endpoint changed and update the IPsec SAs if it did, as we assume the original address became unavailable and the responder already updated the SAs on its side.
663 lines
15 KiB
C
663 lines
15 KiB
C
/*
|
|
* Copyright (C) 2007 Martin Willi
|
|
* 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_mobike.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include <hydra.h>
|
|
#include <daemon.h>
|
|
#include <sa/tasks/ike_natd.h>
|
|
#include <encoding/payloads/notify_payload.h>
|
|
|
|
#define COOKIE2_SIZE 16
|
|
#define MAX_ADDITIONAL_ADDRS 8
|
|
|
|
typedef struct private_ike_mobike_t private_ike_mobike_t;
|
|
|
|
/**
|
|
* Private members of a ike_mobike_t task.
|
|
*/
|
|
struct private_ike_mobike_t {
|
|
|
|
/**
|
|
* Public methods and task_t interface.
|
|
*/
|
|
ike_mobike_t public;
|
|
|
|
/**
|
|
* Assigned IKE_SA.
|
|
*/
|
|
ike_sa_t *ike_sa;
|
|
|
|
/**
|
|
* Are we the initiator?
|
|
*/
|
|
bool initiator;
|
|
|
|
/**
|
|
* cookie2 value to verify new addresses
|
|
*/
|
|
chunk_t cookie2;
|
|
|
|
/**
|
|
* NAT discovery reusing the IKE_NATD task
|
|
*/
|
|
ike_natd_t *natd;
|
|
|
|
/**
|
|
* use task to update addresses
|
|
*/
|
|
bool update;
|
|
|
|
/**
|
|
* do routability check
|
|
*/
|
|
bool check;
|
|
|
|
/**
|
|
* include address list update
|
|
*/
|
|
bool address;
|
|
|
|
/**
|
|
* additional addresses got updated
|
|
*/
|
|
bool addresses_updated;
|
|
};
|
|
|
|
/**
|
|
* flush the IKE_SAs list of additional addresses
|
|
*/
|
|
static void flush_additional_addresses(private_ike_mobike_t *this)
|
|
{
|
|
iterator_t *iterator;
|
|
host_t *host;
|
|
|
|
iterator = this->ike_sa->create_additional_address_iterator(this->ike_sa);
|
|
while (iterator->iterate(iterator, (void**)&host))
|
|
{
|
|
iterator->remove(iterator);
|
|
host->destroy(host);
|
|
}
|
|
iterator->destroy(iterator);
|
|
}
|
|
|
|
|
|
/**
|
|
* read notifys from message and evaluate them
|
|
*/
|
|
static void process_payloads(private_ike_mobike_t *this, message_t *message)
|
|
{
|
|
enumerator_t *enumerator;
|
|
payload_t *payload;
|
|
bool first = TRUE;
|
|
|
|
enumerator = message->create_payload_enumerator(message);
|
|
while (enumerator->enumerate(enumerator, &payload))
|
|
{
|
|
int family = AF_INET;
|
|
notify_payload_t *notify;
|
|
chunk_t data;
|
|
host_t *host;
|
|
|
|
if (payload->get_type(payload) != NOTIFY)
|
|
{
|
|
continue;
|
|
}
|
|
notify = (notify_payload_t*)payload;
|
|
switch (notify->get_notify_type(notify))
|
|
{
|
|
case MOBIKE_SUPPORTED:
|
|
{
|
|
peer_cfg_t *peer_cfg;
|
|
|
|
peer_cfg = this->ike_sa->get_peer_cfg(this->ike_sa);
|
|
if (!this->initiator &&
|
|
peer_cfg && !peer_cfg->use_mobike(peer_cfg))
|
|
{
|
|
DBG1(DBG_IKE, "peer supports MOBIKE, but disabled in config");
|
|
}
|
|
else
|
|
{
|
|
DBG1(DBG_IKE, "peer supports MOBIKE");
|
|
this->ike_sa->enable_extension(this->ike_sa, EXT_MOBIKE);
|
|
}
|
|
break;
|
|
}
|
|
case COOKIE2:
|
|
{
|
|
chunk_free(&this->cookie2);
|
|
this->cookie2 = chunk_clone(notify->get_notification_data(notify));
|
|
break;
|
|
}
|
|
case ADDITIONAL_IP6_ADDRESS:
|
|
{
|
|
family = AF_INET6;
|
|
/* fall through */
|
|
}
|
|
case ADDITIONAL_IP4_ADDRESS:
|
|
{
|
|
if (first)
|
|
{ /* an ADDITIONAL_*_ADDRESS means replace, so flush once */
|
|
flush_additional_addresses(this);
|
|
first = FALSE;
|
|
}
|
|
data = notify->get_notification_data(notify);
|
|
host = host_create_from_chunk(family, data, 0);
|
|
DBG2(DBG_IKE, "got additional MOBIKE peer address: %H", host);
|
|
this->ike_sa->add_additional_address(this->ike_sa, host);
|
|
this->addresses_updated = TRUE;
|
|
break;
|
|
}
|
|
case UPDATE_SA_ADDRESSES:
|
|
{
|
|
this->update = TRUE;
|
|
break;
|
|
}
|
|
case NO_ADDITIONAL_ADDRESSES:
|
|
{
|
|
flush_additional_addresses(this);
|
|
this->addresses_updated = TRUE;
|
|
break;
|
|
}
|
|
case NAT_DETECTION_SOURCE_IP:
|
|
case NAT_DETECTION_DESTINATION_IP:
|
|
{
|
|
/* NAT check in this MOBIKE exchange, create subtask for it */
|
|
if (this->natd == NULL)
|
|
{
|
|
this->natd = ike_natd_create(this->ike_sa, this->initiator);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
enumerator->destroy(enumerator);
|
|
}
|
|
|
|
/**
|
|
* Add ADDITIONAL_*_ADDRESS notifys depending on our address list
|
|
*/
|
|
static void build_address_list(private_ike_mobike_t *this, message_t *message)
|
|
{
|
|
enumerator_t *enumerator;
|
|
host_t *host, *me;
|
|
notify_type_t type;
|
|
int added = 0;
|
|
|
|
me = this->ike_sa->get_my_host(this->ike_sa);
|
|
enumerator = hydra->kernel_interface->create_address_enumerator(
|
|
hydra->kernel_interface, FALSE, FALSE);
|
|
while (enumerator->enumerate(enumerator, (void**)&host))
|
|
{
|
|
if (me->ip_equals(me, host))
|
|
{ /* "ADDITIONAL" means do not include IKE_SAs host */
|
|
continue;
|
|
}
|
|
switch (host->get_family(host))
|
|
{
|
|
case AF_INET:
|
|
type = ADDITIONAL_IP4_ADDRESS;
|
|
break;
|
|
case AF_INET6:
|
|
type = ADDITIONAL_IP6_ADDRESS;
|
|
break;
|
|
default:
|
|
continue;
|
|
}
|
|
message->add_notify(message, FALSE, type, host->get_address(host));
|
|
if (++added >= MAX_ADDITIONAL_ADDRS)
|
|
{ /* limit number of notifys, some implementations do not like too
|
|
* many of them (f.e. strongSwan ;-) */
|
|
break;
|
|
}
|
|
}
|
|
if (!added)
|
|
{
|
|
message->add_notify(message, FALSE, NO_ADDITIONAL_ADDRESSES, chunk_empty);
|
|
}
|
|
enumerator->destroy(enumerator);
|
|
}
|
|
|
|
/**
|
|
* build a cookie and add it to the message
|
|
*/
|
|
static void build_cookie(private_ike_mobike_t *this, message_t *message)
|
|
{
|
|
rng_t *rng;
|
|
|
|
chunk_free(&this->cookie2);
|
|
rng = lib->crypto->create_rng(lib->crypto, RNG_STRONG);
|
|
if (rng)
|
|
{
|
|
rng->allocate_bytes(rng, COOKIE2_SIZE, &this->cookie2);
|
|
rng->destroy(rng);
|
|
message->add_notify(message, FALSE, COOKIE2, this->cookie2);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* update addresses of associated CHILD_SAs
|
|
*/
|
|
static void update_children(private_ike_mobike_t *this)
|
|
{
|
|
iterator_t *iterator;
|
|
child_sa_t *child_sa;
|
|
|
|
iterator = this->ike_sa->create_child_sa_iterator(this->ike_sa);
|
|
while (iterator->iterate(iterator, (void**)&child_sa))
|
|
{
|
|
if (child_sa->update(child_sa,
|
|
this->ike_sa->get_my_host(this->ike_sa),
|
|
this->ike_sa->get_other_host(this->ike_sa),
|
|
this->ike_sa->get_virtual_ip(this->ike_sa, TRUE),
|
|
this->ike_sa->has_condition(this->ike_sa, COND_NAT_ANY)) == NOT_SUPPORTED)
|
|
{
|
|
this->ike_sa->rekey_child_sa(this->ike_sa,
|
|
child_sa->get_protocol(child_sa),
|
|
child_sa->get_spi(child_sa, TRUE));
|
|
}
|
|
}
|
|
iterator->destroy(iterator);
|
|
}
|
|
|
|
/**
|
|
* Apply the port of the old host, if its ip equals the new, use port otherwise.
|
|
*/
|
|
static void apply_port(host_t *host, host_t *old, u_int16_t port)
|
|
{
|
|
if (host->ip_equals(host, old))
|
|
{
|
|
port = old->get_port(old);
|
|
}
|
|
else if (port == IKEV2_UDP_PORT)
|
|
{
|
|
port = IKEV2_NATT_PORT;
|
|
}
|
|
host->set_port(host, port);
|
|
}
|
|
|
|
METHOD(ike_mobike_t, transmit, void,
|
|
private_ike_mobike_t *this, packet_t *packet)
|
|
{
|
|
host_t *me, *other, *me_old, *other_old;
|
|
iterator_t *iterator;
|
|
ike_cfg_t *ike_cfg;
|
|
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);
|
|
ike_cfg = this->ike_sa->get_ike_cfg(this->ike_sa);
|
|
|
|
me = hydra->kernel_interface->get_source_addr(
|
|
hydra->kernel_interface, other_old, NULL);
|
|
if (me)
|
|
{
|
|
apply_port(me, me_old, ike_cfg->get_my_port(ike_cfg));
|
|
DBG1(DBG_IKE, "checking original path %#H - %#H", me, other_old);
|
|
copy = packet->clone(packet);
|
|
copy->set_source(copy, me);
|
|
charon->sender->send(charon->sender, copy);
|
|
}
|
|
|
|
iterator = this->ike_sa->create_additional_address_iterator(this->ike_sa);
|
|
while (iterator->iterate(iterator, (void**)&other))
|
|
{
|
|
me = hydra->kernel_interface->get_source_addr(
|
|
hydra->kernel_interface, other, NULL);
|
|
if (me)
|
|
{
|
|
if (me->get_family(me) != other->get_family(other))
|
|
{
|
|
me->destroy(me);
|
|
continue;
|
|
}
|
|
/* reuse port for an active address, 4500 otherwise */
|
|
apply_port(me, me_old, ike_cfg->get_my_port(ike_cfg));
|
|
other = other->clone(other);
|
|
apply_port(other, other_old, ike_cfg->get_other_port(ike_cfg));
|
|
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);
|
|
}
|
|
|
|
METHOD(task_t, build_i, status_t,
|
|
private_ike_mobike_t *this, message_t *message)
|
|
{
|
|
if (message->get_exchange_type(message) == IKE_AUTH &&
|
|
message->get_message_id(message) == 1)
|
|
{ /* only in first IKE_AUTH */
|
|
message->add_notify(message, FALSE, MOBIKE_SUPPORTED, chunk_empty);
|
|
build_address_list(this, message);
|
|
}
|
|
else if (message->get_exchange_type(message) == INFORMATIONAL)
|
|
{
|
|
host_t *old, *new;
|
|
|
|
/* we check if the existing address is still valid */
|
|
old = message->get_source(message);
|
|
new = hydra->kernel_interface->get_source_addr(hydra->kernel_interface,
|
|
message->get_destination(message), old);
|
|
if (new)
|
|
{
|
|
if (!new->ip_equals(new, old))
|
|
{
|
|
new->set_port(new, old->get_port(old));
|
|
message->set_source(message, new);
|
|
}
|
|
else
|
|
{
|
|
new->destroy(new);
|
|
}
|
|
}
|
|
if (this->update)
|
|
{
|
|
message->add_notify(message, FALSE, UPDATE_SA_ADDRESSES,
|
|
chunk_empty);
|
|
build_cookie(this, message);
|
|
update_children(this);
|
|
}
|
|
if (this->address)
|
|
{
|
|
build_address_list(this, message);
|
|
}
|
|
if (this->natd)
|
|
{
|
|
this->natd->task.build(&this->natd->task, message);
|
|
}
|
|
}
|
|
return NEED_MORE;
|
|
}
|
|
|
|
METHOD(task_t, process_r, status_t,
|
|
private_ike_mobike_t *this, message_t *message)
|
|
{
|
|
if (message->get_exchange_type(message) == IKE_AUTH &&
|
|
message->get_message_id(message) == 1)
|
|
{ /* only first IKE_AUTH */
|
|
process_payloads(this, message);
|
|
}
|
|
else if (message->get_exchange_type(message) == INFORMATIONAL)
|
|
{
|
|
process_payloads(this, message);
|
|
if (this->update)
|
|
{
|
|
host_t *me, *other;
|
|
|
|
me = message->get_destination(message);
|
|
other = message->get_source(message);
|
|
this->ike_sa->set_my_host(this->ike_sa, me->clone(me));
|
|
this->ike_sa->set_other_host(this->ike_sa, other->clone(other));
|
|
}
|
|
|
|
if (this->natd)
|
|
{
|
|
this->natd->task.process(&this->natd->task, message);
|
|
}
|
|
if (this->addresses_updated && this->ike_sa->has_condition(this->ike_sa,
|
|
COND_ORIGINAL_INITIATOR))
|
|
{
|
|
host_t *other = message->get_source(message);
|
|
host_t *other_old = this->ike_sa->get_other_host(this->ike_sa);
|
|
if (!other->equals(other, other_old))
|
|
{
|
|
DBG1(DBG_IKE, "remote address changed from %H to %H", other_old,
|
|
other);
|
|
this->ike_sa->set_other_host(this->ike_sa, other->clone(other));
|
|
this->update = TRUE;
|
|
}
|
|
}
|
|
}
|
|
return NEED_MORE;
|
|
}
|
|
|
|
METHOD(task_t, build_r, status_t,
|
|
private_ike_mobike_t *this, message_t *message)
|
|
{
|
|
if (message->get_exchange_type(message) == IKE_AUTH &&
|
|
this->ike_sa->get_state(this->ike_sa) == IKE_ESTABLISHED)
|
|
{
|
|
if (this->ike_sa->supports_extension(this->ike_sa, EXT_MOBIKE))
|
|
{
|
|
message->add_notify(message, FALSE, MOBIKE_SUPPORTED, chunk_empty);
|
|
build_address_list(this, message);
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
else if (message->get_exchange_type(message) == INFORMATIONAL)
|
|
{
|
|
if (this->natd)
|
|
{
|
|
this->natd->task.build(&this->natd->task, message);
|
|
}
|
|
if (this->cookie2.ptr)
|
|
{
|
|
message->add_notify(message, FALSE, COOKIE2, this->cookie2);
|
|
chunk_free(&this->cookie2);
|
|
}
|
|
if (this->update)
|
|
{
|
|
update_children(this);
|
|
}
|
|
return SUCCESS;
|
|
}
|
|
return NEED_MORE;
|
|
}
|
|
|
|
METHOD(task_t, process_i, status_t,
|
|
private_ike_mobike_t *this, message_t *message)
|
|
{
|
|
if (message->get_exchange_type(message) == IKE_AUTH &&
|
|
this->ike_sa->get_state(this->ike_sa) == IKE_ESTABLISHED)
|
|
{
|
|
process_payloads(this, message);
|
|
return SUCCESS;
|
|
}
|
|
else if (message->get_exchange_type(message) == INFORMATIONAL)
|
|
{
|
|
u_int32_t updates = this->ike_sa->get_pending_updates(this->ike_sa) - 1;
|
|
this->ike_sa->set_pending_updates(this->ike_sa, updates);
|
|
if (updates > 0)
|
|
{
|
|
/* newer update queued, ignore this one */
|
|
return SUCCESS;
|
|
}
|
|
if (this->cookie2.ptr)
|
|
{ /* check cookie if we included one */
|
|
chunk_t cookie2;
|
|
|
|
cookie2 = this->cookie2;
|
|
this->cookie2 = chunk_empty;
|
|
process_payloads(this, message);
|
|
if (!chunk_equals(cookie2, this->cookie2))
|
|
{
|
|
chunk_free(&cookie2);
|
|
DBG1(DBG_IKE, "COOKIE2 mismatch, closing IKE_SA");
|
|
return FAILED;
|
|
}
|
|
chunk_free(&cookie2);
|
|
}
|
|
else
|
|
{
|
|
process_payloads(this, message);
|
|
}
|
|
if (this->natd)
|
|
{
|
|
this->natd->task.process(&this->natd->task, message);
|
|
if (this->natd->has_mapping_changed(this->natd))
|
|
{
|
|
/* force an update if mappings have changed */
|
|
this->update = this->check = TRUE;
|
|
DBG1(DBG_IKE, "detected changes in NAT mappings, "
|
|
"initiating MOBIKE update");
|
|
}
|
|
}
|
|
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;
|
|
if (this->natd)
|
|
{
|
|
this->natd->task.destroy(&this->natd->task);
|
|
}
|
|
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;
|
|
}
|
|
|
|
METHOD(ike_mobike_t, addresses, void,
|
|
private_ike_mobike_t *this)
|
|
{
|
|
this->address = TRUE;
|
|
this->ike_sa->set_pending_updates(this->ike_sa,
|
|
this->ike_sa->get_pending_updates(this->ike_sa) + 1);
|
|
}
|
|
|
|
METHOD(ike_mobike_t, roam, void,
|
|
private_ike_mobike_t *this, bool address)
|
|
{
|
|
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);
|
|
}
|
|
|
|
METHOD(ike_mobike_t, dpd, void,
|
|
private_ike_mobike_t *this)
|
|
{
|
|
if (!this->natd)
|
|
{
|
|
this->natd = ike_natd_create(this->ike_sa, this->initiator);
|
|
}
|
|
this->ike_sa->set_pending_updates(this->ike_sa,
|
|
this->ike_sa->get_pending_updates(this->ike_sa) + 1);
|
|
}
|
|
|
|
METHOD(ike_mobike_t, is_probing, bool,
|
|
private_ike_mobike_t *this)
|
|
{
|
|
return this->check;
|
|
}
|
|
|
|
METHOD(task_t, get_type, task_type_t,
|
|
private_ike_mobike_t *this)
|
|
{
|
|
return IKE_MOBIKE;
|
|
}
|
|
|
|
METHOD(task_t, migrate, void,
|
|
private_ike_mobike_t *this, ike_sa_t *ike_sa)
|
|
{
|
|
chunk_free(&this->cookie2);
|
|
this->ike_sa = ike_sa;
|
|
if (this->natd)
|
|
{
|
|
this->natd->task.migrate(&this->natd->task, ike_sa);
|
|
}
|
|
}
|
|
|
|
METHOD(task_t, destroy, void,
|
|
private_ike_mobike_t *this)
|
|
{
|
|
chunk_free(&this->cookie2);
|
|
if (this->natd)
|
|
{
|
|
this->natd->task.destroy(&this->natd->task);
|
|
}
|
|
free(this);
|
|
}
|
|
|
|
/*
|
|
* Described in header.
|
|
*/
|
|
ike_mobike_t *ike_mobike_create(ike_sa_t *ike_sa, bool initiator)
|
|
{
|
|
private_ike_mobike_t *this;
|
|
|
|
INIT(this,
|
|
.public = {
|
|
.task = {
|
|
.get_type = _get_type,
|
|
.migrate = _migrate,
|
|
.destroy = _destroy,
|
|
},
|
|
.addresses = _addresses,
|
|
.roam = _roam,
|
|
.dpd = _dpd,
|
|
.transmit = _transmit,
|
|
.is_probing = _is_probing,
|
|
},
|
|
.ike_sa = ike_sa,
|
|
.initiator = initiator,
|
|
);
|
|
|
|
if (initiator)
|
|
{
|
|
this->public.task.build = _build_i;
|
|
this->public.task.process = _process_i;
|
|
}
|
|
else
|
|
{
|
|
this->public.task.build = _build_r;
|
|
this->public.task.process = _process_r;
|
|
}
|
|
|
|
return &this->public;
|
|
}
|
|
|