aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorTobias Brunner <tobias@strongswan.org>2016-03-04 16:03:07 +0100
committerTobias Brunner <tobias@strongswan.org>2016-03-04 16:03:07 +0100
commit765db8d2fe9949c2a38d5f393f13e4101dc25ce4 (patch)
treec7694752dd55cc98f921f42d2492679633b10644 /src
parentad82c95f0a8e2733f9579c8c644baf4685cb5a34 (diff)
parent47701e1178a91da363a61bd0478932352bfde2af (diff)
downloadstrongswan-765db8d2fe9949c2a38d5f393f13e4101dc25ce4.tar.bz2
strongswan-765db8d2fe9949c2a38d5f393f13e4101dc25ce4.tar.xz
Merge branch 'ike-redirect'
This adds support for IKEv2 redirection (RFC 5685). There is currently no default implementation of the redirect_provider_t interface provided. Plugins may implement the interface to decide if and when to redirect connecting clients. It is also possible to redirect established IKE_SAs via VICI/swanctl.
Diffstat (limited to 'src')
-rw-r--r--src/libcharon/Android.mk3
-rw-r--r--src/libcharon/Makefile.am3
-rw-r--r--src/libcharon/daemon.c2
-rw-r--r--src/libcharon/daemon.h6
-rw-r--r--src/libcharon/plugins/vici/README.md17
-rw-r--r--src/libcharon/plugins/vici/perl/Vici-Session/lib/Vici/Session.pm4
-rw-r--r--src/libcharon/plugins/vici/python/vici/session.py8
-rw-r--r--src/libcharon/plugins/vici/ruby/lib/vici.rb6
-rw-r--r--src/libcharon/plugins/vici/vici_control.c149
-rw-r--r--src/libcharon/processing/jobs/redirect_job.c106
-rw-r--r--src/libcharon/processing/jobs/redirect_job.h51
-rw-r--r--src/libcharon/sa/ike_sa.c373
-rw-r--r--src/libcharon/sa/ike_sa.h48
-rw-r--r--src/libcharon/sa/ikev2/task_manager_v2.c90
-rw-r--r--src/libcharon/sa/ikev2/tasks/child_create.c4
-rw-r--r--src/libcharon/sa/ikev2/tasks/ike_auth.c144
-rw-r--r--src/libcharon/sa/ikev2/tasks/ike_config.c5
-rw-r--r--src/libcharon/sa/ikev2/tasks/ike_init.c144
-rw-r--r--src/libcharon/sa/ikev2/tasks/ike_redirect.c150
-rw-r--r--src/libcharon/sa/ikev2/tasks/ike_redirect.h54
-rw-r--r--src/libcharon/sa/redirect_manager.c274
-rw-r--r--src/libcharon/sa/redirect_manager.h109
-rw-r--r--src/libcharon/sa/redirect_provider.h59
-rw-r--r--src/libcharon/sa/task.c1
-rw-r--r--src/libcharon/sa/task.h16
-rw-r--r--src/swanctl/Makefile.am1
-rw-r--r--src/swanctl/command.h2
-rw-r--r--src/swanctl/commands/redirect.c132
-rw-r--r--src/swanctl/swanctl.8.in4
29 files changed, 1843 insertions, 122 deletions
diff --git a/src/libcharon/Android.mk b/src/libcharon/Android.mk
index 844bbfd8e..0b09d100d 100644
--- a/src/libcharon/Android.mk
+++ b/src/libcharon/Android.mk
@@ -59,6 +59,7 @@ processing/jobs/delete_child_sa_job.c processing/jobs/delete_child_sa_job.h \
processing/jobs/delete_ike_sa_job.c processing/jobs/delete_ike_sa_job.h \
processing/jobs/migrate_job.c processing/jobs/migrate_job.h \
processing/jobs/process_message_job.c processing/jobs/process_message_job.h \
+processing/jobs/redirect_job.c processing/jobs/redirect_job.h \
processing/jobs/rekey_child_sa_job.c processing/jobs/rekey_child_sa_job.h \
processing/jobs/rekey_ike_sa_job.c processing/jobs/rekey_ike_sa_job.h \
processing/jobs/retransmit_job.c processing/jobs/retransmit_job.h \
@@ -84,6 +85,7 @@ sa/child_sa_manager.c sa/child_sa_manager.h \
sa/task_manager.h sa/task_manager.c \
sa/shunt_manager.c sa/shunt_manager.h \
sa/trap_manager.c sa/trap_manager.h \
+sa/redirect_provider.h sa/redirect_manager.c sa/redirect_manager.h \
sa/task.c sa/task.h
libcharon_la_SOURCES += \
@@ -107,6 +109,7 @@ sa/ikev2/tasks/ike_mobike.c sa/ikev2/tasks/ike_mobike.h \
sa/ikev2/tasks/ike_rekey.c sa/ikev2/tasks/ike_rekey.h \
sa/ikev2/tasks/ike_reauth.c sa/ikev2/tasks/ike_reauth.h \
sa/ikev2/tasks/ike_reauth_complete.c sa/ikev2/tasks/ike_reauth_complete.h \
+sa/ikev2/tasks/ike_redirect.c sa/ikev2/tasks/ike_redirect.h \
sa/ikev2/tasks/ike_auth_lifetime.c sa/ikev2/tasks/ike_auth_lifetime.h \
sa/ikev2/tasks/ike_vendor.c sa/ikev2/tasks/ike_vendor.h
diff --git a/src/libcharon/Makefile.am b/src/libcharon/Makefile.am
index 4de8faab9..062b96aff 100644
--- a/src/libcharon/Makefile.am
+++ b/src/libcharon/Makefile.am
@@ -58,6 +58,7 @@ processing/jobs/delete_child_sa_job.c processing/jobs/delete_child_sa_job.h \
processing/jobs/delete_ike_sa_job.c processing/jobs/delete_ike_sa_job.h \
processing/jobs/migrate_job.c processing/jobs/migrate_job.h \
processing/jobs/process_message_job.c processing/jobs/process_message_job.h \
+processing/jobs/redirect_job.c processing/jobs/redirect_job.h \
processing/jobs/rekey_child_sa_job.c processing/jobs/rekey_child_sa_job.h \
processing/jobs/rekey_ike_sa_job.c processing/jobs/rekey_ike_sa_job.h \
processing/jobs/retransmit_job.c processing/jobs/retransmit_job.h \
@@ -83,6 +84,7 @@ sa/child_sa_manager.c sa/child_sa_manager.h \
sa/task_manager.h sa/task_manager.c \
sa/shunt_manager.c sa/shunt_manager.h \
sa/trap_manager.c sa/trap_manager.h \
+sa/redirect_provider.h sa/redirect_manager.c sa/redirect_manager.h \
sa/task.c sa/task.h
if USE_IKEV2
@@ -107,6 +109,7 @@ sa/ikev2/tasks/ike_mobike.c sa/ikev2/tasks/ike_mobike.h \
sa/ikev2/tasks/ike_rekey.c sa/ikev2/tasks/ike_rekey.h \
sa/ikev2/tasks/ike_reauth.c sa/ikev2/tasks/ike_reauth.h \
sa/ikev2/tasks/ike_reauth_complete.c sa/ikev2/tasks/ike_reauth_complete.h \
+sa/ikev2/tasks/ike_redirect.c sa/ikev2/tasks/ike_redirect.h \
sa/ikev2/tasks/ike_auth_lifetime.c sa/ikev2/tasks/ike_auth_lifetime.h \
sa/ikev2/tasks/ike_vendor.c sa/ikev2/tasks/ike_vendor.h
endif
diff --git a/src/libcharon/daemon.c b/src/libcharon/daemon.c
index 799c3f6dc..cef8b8992 100644
--- a/src/libcharon/daemon.c
+++ b/src/libcharon/daemon.c
@@ -680,6 +680,7 @@ static void destroy(private_daemon_t *this)
DESTROY_IF(this->kernel_handler);
DESTROY_IF(this->public.traps);
DESTROY_IF(this->public.shunts);
+ DESTROY_IF(this->public.redirect);
DESTROY_IF(this->public.controller);
DESTROY_IF(this->public.eap);
DESTROY_IF(this->public.xauth);
@@ -872,6 +873,7 @@ private_daemon_t *daemon_create()
this->public.socket = socket_manager_create();
this->public.traps = trap_manager_create();
this->public.shunts = shunt_manager_create();
+ this->public.redirect = redirect_manager_create();
this->kernel_handler = kernel_handler_create();
return this;
diff --git a/src/libcharon/daemon.h b/src/libcharon/daemon.h
index 654e22a07..48b9c7ec3 100644
--- a/src/libcharon/daemon.h
+++ b/src/libcharon/daemon.h
@@ -190,6 +190,7 @@ typedef struct daemon_t daemon_t;
#include <sa/child_sa_manager.h>
#include <sa/trap_manager.h>
#include <sa/shunt_manager.h>
+#include <sa/redirect_manager.h>
#include <config/backend_manager.h>
#include <sa/eap/eap_manager.h>
#include <sa/xauth/xauth_manager.h>
@@ -265,6 +266,11 @@ struct daemon_t {
shunt_manager_t *shunts;
/**
+ * Manager for IKE redirect providers
+ */
+ redirect_manager_t *redirect;
+
+ /**
* Manager for the different configuration backends.
*/
backend_manager_t *backends;
diff --git a/src/libcharon/plugins/vici/README.md b/src/libcharon/plugins/vici/README.md
index 773ef1aa5..52929bd74 100644
--- a/src/libcharon/plugins/vici/README.md
+++ b/src/libcharon/plugins/vici/README.md
@@ -289,6 +289,23 @@ Terminates an SA while streaming _control-log_ events.
The default timeout of 0 waits indefinitely for a result, and a timeout value
of -1 returns a result immediately.
+### redirect() ###
+
+Redirect a client-initiated IKE_SA to another gateway. Only for IKEv2 and if
+supported by the peer.
+
+ {
+ ike = <redirect an IKE_SA by configuration name>
+ ike-id = <redirect an IKE_SA by its unique id>
+ peer-ip = <redirect an IKE_SA with matching peer IP, may also be a
+ subnet in CIDR notation or an IP range>
+ peer-id = <redirect an IKE_SA with matching peer identity, may contain
+ wildcards>
+ } => {
+ success = <yes or no>
+ errmsg = <error string on failure>
+ }
+
### install() ###
Install a trap, drop or bypass policy defined by a CHILD_SA config.
diff --git a/src/libcharon/plugins/vici/perl/Vici-Session/lib/Vici/Session.pm b/src/libcharon/plugins/vici/perl/Vici-Session/lib/Vici/Session.pm
index 5252296cf..78197136a 100644
--- a/src/libcharon/plugins/vici/perl/Vici-Session/lib/Vici/Session.pm
+++ b/src/libcharon/plugins/vici/perl/Vici-Session/lib/Vici/Session.pm
@@ -36,6 +36,10 @@ sub terminate {
return request_vars_res('terminate', @_);
}
+sub redirect {
+ return request_vars_res('redirect', @_);
+}
+
sub install {
return request_vars_res('install', @_);
}
diff --git a/src/libcharon/plugins/vici/python/vici/session.py b/src/libcharon/plugins/vici/python/vici/session.py
index 283e3d13d..66de8590a 100644
--- a/src/libcharon/plugins/vici/python/vici/session.py
+++ b/src/libcharon/plugins/vici/python/vici/session.py
@@ -53,6 +53,14 @@ class Session(object):
"""
return self.handler.streamed_request("terminate", "control-log", sa)
+ def redirect(self, sa):
+ """Redirect an IKE_SA.
+
+ :param sa: the SA to redirect
+ :type sa: dict
+ """
+ self.handler.request("redirect", sa)
+
def install(self, policy):
"""Install a trap, drop or bypass policy defined by a CHILD_SA config.
diff --git a/src/libcharon/plugins/vici/ruby/lib/vici.rb b/src/libcharon/plugins/vici/ruby/lib/vici.rb
index f8169add0..018f50766 100644
--- a/src/libcharon/plugins/vici/ruby/lib/vici.rb
+++ b/src/libcharon/plugins/vici/ruby/lib/vici.rb
@@ -505,6 +505,12 @@ module Vici
end
##
+ # Redirect an IKE_SA.
+ def redirect(options)
+ check_success(@transp.request("redirect", Message.new(options)))
+ end
+
+ ##
# Install a shunt/route policy.
def install(policy)
check_success(@transp.request("install", Message.new(policy)))
diff --git a/src/libcharon/plugins/vici/vici_control.c b/src/libcharon/plugins/vici/vici_control.c
index 87794d24d..c526d2fda 100644
--- a/src/libcharon/plugins/vici/vici_control.c
+++ b/src/libcharon/plugins/vici/vici_control.c
@@ -1,4 +1,7 @@
/*
+ * Copyright (C) 2015 Tobias Brunner
+ * Hochschule fuer Technik Rapperswil
+ *
* Copyright (C) 2014 Martin Willi
* Copyright (C) 2014 revosec AG
*
@@ -20,6 +23,7 @@
#include <daemon.h>
#include <collections/array.h>
+#include <processing/jobs/redirect_job.h>
typedef struct private_vici_control_t private_vici_control_t;
@@ -357,6 +361,150 @@ CALLBACK(terminate, vici_message_t*,
}
/**
+ * Parse a peer-ip specified, which can be a subnet in CIDR notation, a range
+ * or a single IP address.
+ */
+static traffic_selector_t *parse_peer_ip(char *ip)
+{
+ traffic_selector_t *ts;
+ host_t *from, *to;
+ ts_type_t type;
+
+ if (host_create_from_range(ip, &from, &to))
+ {
+ if (to->get_family(to) == AF_INET)
+ {
+ type = TS_IPV4_ADDR_RANGE;
+ }
+ else
+ {
+ type = TS_IPV6_ADDR_RANGE;
+ }
+ ts = traffic_selector_create_from_bytes(0, type,
+ from->get_address(from), 0,
+ to->get_address(to), 0xFFFF);
+ from->destroy(from);
+ to->destroy(to);
+ return ts;
+ }
+ return traffic_selector_create_from_cidr(ip, 0, 0, 0xFFFF);
+}
+
+CALLBACK(redirect, vici_message_t*,
+ private_vici_control_t *this, char *name, u_int id, vici_message_t *request)
+{
+ enumerator_t *sas;
+ char *ike, *peer_ip, *peer_id, *gw, *errmsg = NULL;
+ u_int ike_id, current, found = 0;
+ identification_t *gateway, *identity = NULL, *other_id;
+ traffic_selector_t *ts = NULL;
+ ike_sa_t *ike_sa;
+ vici_builder_t *builder;
+
+ ike = request->get_str(request, NULL, "ike");
+ ike_id = request->get_int(request, 0, "ike-id");
+ peer_ip = request->get_str(request, NULL, "peer-ip");
+ peer_id = request->get_str(request, NULL, "peer-id");
+ gw = request->get_str(request, NULL, "gateway");
+
+ if (!gw || !(gateway = identification_create_from_string(gw)))
+ {
+ return send_reply(this, "missing target gateway");
+ }
+ switch (gateway->get_type(gateway))
+ {
+ case ID_IPV4_ADDR:
+ case ID_IPV6_ADDR:
+ case ID_FQDN:
+ break;
+ default:
+ return send_reply(this, "unsupported gateway identity");
+ }
+ if (peer_ip)
+ {
+ ts = parse_peer_ip(peer_ip);
+ if (!ts)
+ {
+ return send_reply(this, "invalid peer IP selector");
+ }
+ DBG1(DBG_CFG, "vici redirect IKE_SAs with src %R to %Y", ts,
+ gateway);
+ }
+ if (peer_id)
+ {
+ identity = identification_create_from_string(peer_id);
+ if (!identity)
+ {
+ DESTROY_IF(ts);
+ return send_reply(this, "invalid peer identity selector");
+ }
+ DBG1(DBG_CFG, "vici redirect IKE_SAs with ID '%Y' to %Y", identity,
+ gateway);
+ }
+ if (ike_id)
+ {
+ DBG1(DBG_CFG, "vici redirect IKE_SA #%d to '%Y'", ike_id, gateway);
+ }
+ if (ike)
+ {
+ DBG1(DBG_CFG, "vici redirect IKE_SA '%s' to '%Y'", ike, gateway);
+ }
+ if (!peer_ip && !peer_id && !ike && !ike_id)
+ {
+ return send_reply(this, "missing redirect selector");
+ }
+
+ sas = charon->controller->create_ike_sa_enumerator(charon->controller, TRUE);
+ while (sas->enumerate(sas, &ike_sa))
+ {
+ if (ike_sa->get_version(ike_sa) != IKEV2)
+ {
+ continue;
+ }
+ current = ike_sa->get_unique_id(ike_sa);
+ if (ike_id && ike_id != current)
+ {
+ continue;
+ }
+ if (ike && !streq(ike, ike_sa->get_name(ike_sa)))
+ {
+ continue;
+ }
+ if (ts && !ts->includes(ts, ike_sa->get_other_host(ike_sa)))
+ {
+ continue;
+ }
+ if (identity)
+ {
+ other_id = ike_sa->get_other_eap_id(ike_sa);
+ if (!other_id->matches(other_id, identity))
+ {
+ continue;
+ }
+ }
+ lib->processor->queue_job(lib->processor,
+ (job_t*)redirect_job_create(ike_sa->get_id(ike_sa), gateway));
+ found++;
+ }
+ sas->destroy(sas);
+
+ builder = vici_builder_create();
+ if (!found)
+ {
+ errmsg = "no matching SAs to redirect found";
+ }
+ builder->add_kv(builder, "success", errmsg ? "no" : "yes");
+ if (errmsg)
+ {
+ builder->add_kv(builder, "errmsg", "%s", errmsg);
+ }
+ gateway->destroy(gateway);
+ DESTROY_IF(identity);
+ DESTROY_IF(ts);
+ return builder->finalize(builder);
+}
+
+/**
* Find reqid of an existing CHILD_SA
*/
static u_int32_t find_reqid(child_cfg_t *cfg)
@@ -498,6 +646,7 @@ static void manage_commands(private_vici_control_t *this, bool reg)
{
manage_command(this, "initiate", initiate, reg);
manage_command(this, "terminate", terminate, reg);
+ manage_command(this, "redirect", redirect, reg);
manage_command(this, "install", install, reg);
manage_command(this, "uninstall", uninstall, reg);
manage_command(this, "reload-settings", reload_settings, reg);
diff --git a/src/libcharon/processing/jobs/redirect_job.c b/src/libcharon/processing/jobs/redirect_job.c
new file mode 100644
index 000000000..e1af662c9
--- /dev/null
+++ b/src/libcharon/processing/jobs/redirect_job.c
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2015 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 <daemon.h>
+
+#include "redirect_job.h"
+
+typedef struct private_redirect_job_t private_redirect_job_t;
+
+/**
+ * Private data
+ */
+struct private_redirect_job_t {
+
+ /**
+ * Public interface
+ */
+ redirect_job_t public;
+
+ /**
+ * ID of the IKE_SA to redirect
+ */
+ ike_sa_id_t *ike_sa_id;
+
+ /**
+ * Target gateway identity
+ */
+ identification_t *gateway;
+};
+
+
+METHOD(job_t, destroy, void,
+ private_redirect_job_t *this)
+{
+ this->ike_sa_id->destroy(this->ike_sa_id);
+ this->gateway->destroy(this->gateway);
+ free(this);
+}
+
+METHOD(job_t, execute, job_requeue_t,
+ private_redirect_job_t *this)
+{
+ ike_sa_t *ike_sa;
+
+ ike_sa = charon->ike_sa_manager->checkout(charon->ike_sa_manager,
+ this->ike_sa_id);
+ if (ike_sa)
+ {
+ if (ike_sa->get_state(ike_sa) == IKE_PASSIVE)
+ {
+ charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
+ return JOB_REQUEUE_NONE;
+ }
+ if (ike_sa->redirect(ike_sa, this->gateway) == DESTROY_ME)
+ {
+ charon->ike_sa_manager->checkin_and_destroy(
+ charon->ike_sa_manager, ike_sa);
+ }
+ else
+ {
+ charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa);
+ }
+ }
+ return JOB_REQUEUE_NONE;
+}
+
+METHOD(job_t, get_priority, job_priority_t,
+ private_redirect_job_t *this)
+{
+ return JOB_PRIO_MEDIUM;
+}
+
+/*
+ * Described in header
+ */
+redirect_job_t *redirect_job_create(ike_sa_id_t *ike_sa_id,
+ identification_t *gateway)
+{
+ private_redirect_job_t *this;
+
+ INIT(this,
+ .public = {
+ .job_interface = {
+ .execute = _execute,
+ .get_priority = _get_priority,
+ .destroy = _destroy,
+ },
+ },
+ .ike_sa_id = ike_sa_id->clone(ike_sa_id),
+ .gateway = gateway->clone(gateway),
+ );
+
+ return &(this->public);
+}
diff --git a/src/libcharon/processing/jobs/redirect_job.h b/src/libcharon/processing/jobs/redirect_job.h
new file mode 100644
index 000000000..fe4b34ee9
--- /dev/null
+++ b/src/libcharon/processing/jobs/redirect_job.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+/**
+ * @defgroup redirect_job redirect_job
+ * @{ @ingroup cjobs
+ */
+
+#ifndef REDIRECT_JOB_H_
+#define REDIRECT_JOB_H_
+
+typedef struct redirect_job_t redirect_job_t;
+
+#include <library.h>
+#include <sa/ike_sa_id.h>
+#include <processing/jobs/job.h>
+
+/**
+ * Job used to redirect an IKE_SA.
+ */
+struct redirect_job_t {
+
+ /**
+ * The job_t interface.
+ */
+ job_t job_interface;
+};
+
+/**
+ * Creates a job to redirect an IKE_SA.
+ *
+ * @param ike_sa_id id of the IKE_SA to redirect (cloned)
+ * @param gateway gateway identity (IP or FQDN) of target (cloned)
+ * @return created redirect_job_t object
+ */
+redirect_job_t *redirect_job_create(ike_sa_id_t *ike_sa_id,
+ identification_t *gateway);
+
+#endif /** REDIRECT_JOB_H_ @}*/
diff --git a/src/libcharon/sa/ike_sa.c b/src/libcharon/sa/ike_sa.c
index 48a4b274a..b07ff0e74 100644
--- a/src/libcharon/sa/ike_sa.c
+++ b/src/libcharon/sa/ike_sa.c
@@ -56,6 +56,8 @@
#include <processing/jobs/rekey_ike_sa_job.h>
#include <processing/jobs/retry_initiate_job.h>
#include <sa/ikev2/tasks/ike_auth_lifetime.h>
+#include <sa/ikev2/tasks/ike_reauth_complete.h>
+#include <sa/ikev2/tasks/ike_redirect.h>
#ifdef ME
#include <sa/ikev2/tasks/ike_me.h>
@@ -282,6 +284,21 @@ struct private_ike_sa_t {
* Maximum length of a single fragment, 0 for address-specific defaults
*/
size_t fragment_size;
+
+ /**
+ * Whether to follow IKEv2 redirects
+ */
+ bool follow_redirects;
+
+ /**
+ * Original gateway address from which we got redirected
+ */
+ host_t *redirected_from;
+
+ /**
+ * Timestamps of redirect attempts to handle loops
+ */
+ array_t *redirected_at;
};
/**
@@ -386,6 +403,12 @@ METHOD(ike_sa_t, set_other_host, void,
this->other_host = other;
}
+METHOD(ike_sa_t, get_redirected_from, host_t*,
+ private_ike_sa_t *this)
+{
+ return this->redirected_from;
+}
+
METHOD(ike_sa_t, get_peer_cfg, peer_cfg_t*,
private_ike_sa_t *this)
{
@@ -743,6 +766,8 @@ METHOD(ike_sa_t, set_state, void,
{
keepalives = TRUE;
}
+ DESTROY_IF(this->redirected_from);
+ this->redirected_from = NULL;
}
break;
}
@@ -1750,6 +1775,86 @@ static bool is_child_queued(private_ike_sa_t *this, task_queue_t queue)
return found;
}
+/**
+ * Reestablish CHILD_SAs and migrate queued tasks.
+ *
+ * If force is true all SAs are restarted, otherwise their close/dpd_action
+ * is followed.
+ */
+static status_t reestablish_children(private_ike_sa_t *this, ike_sa_t *new,
+ bool force)
+{
+ enumerator_t *enumerator;
+ child_sa_t *child_sa;
+ child_cfg_t *child_cfg;
+ action_t action;
+ status_t status = FAILED;
+
+ /* handle existing CHILD_SAs */
+ enumerator = create_child_sa_enumerator(this);
+ while (enumerator->enumerate(enumerator, (void**)&child_sa))
+ {
+ if (force)
+ {
+ switch (child_sa->get_state(child_sa))
+ {
+ case CHILD_ROUTED:
+ { /* move routed child directly */
+ remove_child_sa(this, enumerator);
+ new->add_child_sa(new, child_sa);
+ action = ACTION_NONE;
+ break;
+ }
+ default:
+ { /* initiate/queue all other CHILD_SAs */
+ action = ACTION_RESTART;
+ break;
+ }
+ }
+ }
+ else
+ { /* only restart CHILD_SAs that are configured accordingly */
+ if (this->state == IKE_DELETING)
+ {
+ action = child_sa->get_close_action(child_sa);
+ }
+ else
+ {
+ action = child_sa->get_dpd_action(child_sa);
+ }
+ }
+ switch (action)
+ {
+ case ACTION_RESTART:
+ child_cfg = child_sa->get_config(child_sa);
+ DBG1(DBG_IKE, "restarting CHILD_SA %s",
+ child_cfg->get_name(child_cfg));
+ child_cfg->get_ref(child_cfg);
+ status = new->initiate(new, child_cfg,
+ child_sa->get_reqid(child_sa), NULL, NULL);
+ break;
+ default:
+ continue;
+ }
+ if (status == DESTROY_ME)
+ {
+ break;
+ }
+ }
+ enumerator->destroy(enumerator);
+ /* adopt any active or queued CHILD-creating tasks */
+ if (status != DESTROY_ME)
+ {
+ task_manager_t *other_tasks = ((private_ike_sa_t*)new)->task_manager;
+ other_tasks->adopt_child_tasks(other_tasks, this->task_manager);
+ if (new->get_state(new) == IKE_CREATED)
+ {
+ status = new->initiate(new, NULL, 0, NULL, NULL);
+ }
+ }
+ return status;
+}
+
METHOD(ike_sa_t, reestablish, status_t,
private_ike_sa_t *this)
{
@@ -1758,7 +1863,6 @@ METHOD(ike_sa_t, reestablish, status_t,
action_t action;
enumerator_t *enumerator;
child_sa_t *child_sa;
- child_cfg_t *child_cfg;
bool restart = FALSE;
status_t status = FAILED;
@@ -1851,8 +1955,11 @@ METHOD(ike_sa_t, reestablish, status_t,
host = this->my_host;
new->set_my_host(new, host->clone(host));
charon->bus->ike_reestablish_pre(charon->bus, &this->public, new);
- /* resolve hosts but use the old addresses above as fallback */
- resolve_hosts((private_ike_sa_t*)new);
+ if (!has_condition(this, COND_REAUTHENTICATING))
+ { /* reauthenticate to the same addresses, but resolve hosts if
+ * reestablishing (old addresses serve as fallback) */
+ resolve_hosts((private_ike_sa_t*)new);
+ }
/* if we already have a virtual IP, we reuse it */
enumerator = array_create_enumerator(this->my_vips);
while (enumerator->enumerate(enumerator, &host))
@@ -1869,68 +1976,8 @@ METHOD(ike_sa_t, reestablish, status_t,
else
#endif /* ME */
{
- /* handle existing CHILD_SAs */
- enumerator = create_child_sa_enumerator(this);
- while (enumerator->enumerate(enumerator, (void**)&child_sa))
- {
- if (has_condition(this, COND_REAUTHENTICATING))
- {
- switch (child_sa->get_state(child_sa))
- {
- case CHILD_ROUTED:
- { /* move routed child directly */
- remove_child_sa(this, enumerator);
- new->add_child_sa(new, child_sa);
- action = ACTION_NONE;
- break;
- }
- default:
- { /* initiate/queue all other CHILD_SAs */
- action = ACTION_RESTART;
- break;
- }
- }
- }
- else
- { /* only restart CHILD_SAs that are configured accordingly */
- if (this->state == IKE_DELETING)
- {
- action = child_sa->get_close_action(child_sa);
- }
- else
- {
- action = child_sa->get_dpd_action(child_sa);
- }
- }
- switch (action)
- {
- case ACTION_RESTART:
- child_cfg = child_sa->get_config(child_sa);
- DBG1(DBG_IKE, "restarting CHILD_SA %s",
- child_cfg->get_name(child_cfg));
- child_cfg->get_ref(child_cfg);
- status = new->initiate(new, child_cfg,
- child_sa->get_reqid(child_sa), NULL, NULL);
- break;
- default:
- continue;
- }
- if (status == DESTROY_ME)
- {
- break;
- }
- }
- enumerator->destroy(enumerator);
- /* adopt any active or queued CHILD-creating tasks */
- if (status != DESTROY_ME)
- {
- task_manager_t *other_tasks = ((private_ike_sa_t*)new)->task_manager;
- other_tasks->adopt_child_tasks(other_tasks, this->task_manager);
- if (new->get_state(new) == IKE_CREATED)
- {
- status = new->initiate(new, NULL, 0, NULL, NULL);
- }
- }
+ status = reestablish_children(this, new,
+ has_condition(this, COND_REAUTHENTICATING));
}
if (status == DESTROY_ME)
@@ -1951,6 +1998,195 @@ METHOD(ike_sa_t, reestablish, status_t,
return status;
}
+/**
+ * Resolve the given gateway ID
+ */
+static host_t *resolve_gateway_id(identification_t *gateway)
+{
+ char gw[BUF_LEN];
+ host_t *addr;
+
+ snprintf(gw, sizeof(gw), "%Y", gateway);
+ gw[sizeof(gw)-1] = '\0';
+ addr = host_create_from_dns(gw, AF_UNSPEC, IKEV2_UDP_PORT);
+ if (!addr)
+ {
+ DBG1(DBG_IKE, "unable to resolve gateway ID '%Y', redirect failed",
+ gateway);
+ }
+ return addr;
+}
+
+/**
+ * Redirect the current SA to the given target host
+ */
+static bool redirect_established(private_ike_sa_t *this, identification_t *to)
+{
+ private_ike_sa_t *new_priv;
+ ike_sa_t *new;
+ host_t *other;
+ time_t redirect;
+
+ new = charon->ike_sa_manager->checkout_new(charon->ike_sa_manager,
+ this->version, TRUE);
+ if (!new)
+ {
+ return FALSE;
+ }
+ new_priv = (private_ike_sa_t*)new;
+ new->set_peer_cfg(new, this->peer_cfg);
+ new_priv->redirected_from = this->other_host->clone(this->other_host);
+ charon->bus->ike_reestablish_pre(charon->bus, &this->public, new);
+ other = resolve_gateway_id(to);
+ if (other)
+ {
+ set_my_host(new_priv, this->my_host->clone(this->my_host));
+ /* this allows us to force the remote address while we still properly
+ * resolve the local address */
+ new_priv->remote_host = other;
+ resolve_hosts(new_priv);
+ new_priv->redirected_at = array_create(sizeof(time_t), MAX_REDIRECTS);
+ while (array_remove(this->redirected_at, ARRAY_HEAD, &redirect))
+ {
+ array_insert(new_priv->redirected_at, ARRAY_TAIL, &redirect);
+ }
+ if (reestablish_children(this, new, TRUE) != DESTROY_ME)
+ {
+#ifdef USE_IKEV2
+ new->queue_task(new, (task_t*)ike_reauth_complete_create(new,
+ this->ike_sa_id));
+#endif
+ charon->bus->ike_reestablish_post(charon->bus, &this->public, new,
+ TRUE);
+ charon->ike_sa_manager->checkin(charon->ike_sa_manager, new);
+ charon->bus->set_sa(charon->bus, &this->public);
+ return TRUE;
+ }
+ }
+ charon->bus->ike_reestablish_post(charon->bus, &this->public, new,
+ FALSE);
+ charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, new);
+ charon->bus->set_sa(charon->bus, &this->public);
+ return FALSE;
+}
+
+/**
+ * Redirect the current connecting SA to the given target host
+ */
+static bool redirect_connecting(private_ike_sa_t *this, identification_t *to)
+{
+ host_t *other;
+
+ other = resolve_gateway_id(to);
+ if (!other)
+ {
+ return FALSE;
+ }
+ reset(this);
+ DESTROY_IF(this->redirected_from);
+ this->redirected_from = this->other_host->clone(this->other_host);
+ DESTROY_IF(this->remote_host);
+ /* this allows us to force the remote address while we still properly
+ * resolve the local address */
+ this->remote_host = other;
+ resolve_hosts(this);
+ return TRUE;
+}
+
+/**
+ * Check if the current redirect exceeds the limits for redirects
+ */
+static bool redirect_count_exceeded(private_ike_sa_t *this)
+{
+ time_t now, redirect;
+
+ now = time_monotonic(NULL);
+ /* remove entries outside the defined period */
+ while (array_get(this->redirected_at, ARRAY_HEAD, &redirect) &&
+ now - redirect >= REDIRECT_LOOP_DETECT_PERIOD)
+ {
+ array_remove(this->redirected_at, ARRAY_HEAD, NULL);
+ }
+ if (array_count(this->redirected_at) < MAX_REDIRECTS)
+ {
+ if (!this->redirected_at)
+ {
+ this->redirected_at = array_create(sizeof(time_t), MAX_REDIRECTS);
+ }
+ array_insert(this->redirected_at, ARRAY_TAIL, &now);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+METHOD(ike_sa_t, handle_redirect, bool,
+ private_ike_sa_t *this, identification_t *gateway)
+{
+ DBG1(DBG_IKE, "redirected to %Y", gateway);
+ if (!this->follow_redirects)
+ {
+ DBG1(DBG_IKE, "server sent REDIRECT even though we disabled it");
+ return FALSE;
+ }
+ if (redirect_count_exceeded(this))
+ {
+ DBG1(DBG_IKE, "only %d redirects are allowed within %d seconds",
+ MAX_REDIRECTS, REDIRECT_LOOP_DETECT_PERIOD);
+ return FALSE;
+ }
+
+ switch (this->state)
+ {
+ case IKE_CONNECTING:
+ return redirect_connecting(this, gateway);
+ case IKE_ESTABLISHED:
+ return redirect_established(this, gateway);
+ default:
+ DBG1(DBG_IKE, "unable to handle redirect for IKE_SA in state %N",
+ ike_sa_state_names, this->state);
+ return FALSE;
+ }
+}
+
+METHOD(ike_sa_t, redirect, status_t,
+ private_ike_sa_t *this, identification_t *gateway)
+{
+ switch (this->state)
+ {
+ case IKE_CONNECTING:
+ case IKE_ESTABLISHED:
+ case IKE_REKEYING:
+ if (has_condition(this, COND_REDIRECTED))
+ { /* IKE_SA already got redirected */
+ return SUCCESS;
+ }
+ if (has_condition(this, COND_ORIGINAL_INITIATOR))
+ {
+ DBG1(DBG_IKE, "unable to redirect IKE_SA as initiator");
+ return FAILED;
+ }
+ if (this->version == IKEV1)
+ {
+ DBG1(DBG_IKE, "unable to redirect IKEv1 SA");
+ return FAILED;
+ }
+ if (!supports_extension(this, EXT_IKE_REDIRECTION))
+ {
+ DBG1(DBG_IKE, "client does not support IKE redirection");
+ return FAILED;
+ }
+#ifdef USE_IKEV2
+ this->task_manager->queue_task(this->task_manager,
+ (task_t*)ike_redirect_create(&this->public, gateway));
+#endif
+ return this->task_manager->initiate(this->task_manager);
+ default:
+ DBG1(DBG_IKE, "unable to redirect IKE_SA in state %N",
+ ike_sa_state_names, this->state);
+ return INVALID_STATE;
+ }
+}
+
METHOD(ike_sa_t, retransmit, status_t,
private_ike_sa_t *this, u_int32_t message_id)
{
@@ -2464,6 +2700,8 @@ METHOD(ike_sa_t, destroy, void,
DESTROY_IF(this->other_id);
DESTROY_IF(this->local_host);
DESTROY_IF(this->remote_host);
+ DESTROY_IF(this->redirected_from);
+ array_destroy(this->redirected_at);
DESTROY_IF(this->ike_cfg);
DESTROY_IF(this->peer_cfg);
@@ -2543,6 +2781,9 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id, bool initiator,
.destroy = _destroy,
.send_dpd = _send_dpd,
.send_keepalive = _send_keepalive,
+ .redirect = _redirect,
+ .handle_redirect = _handle_redirect,
+ .get_redirected_from = _get_redirected_from,
.get_keymat = _get_keymat,
.add_child_sa = _add_child_sa,
.get_child_sa = _get_child_sa,
@@ -2608,6 +2849,8 @@ ike_sa_t * ike_sa_create(ike_sa_id_t *ike_sa_id, bool initiator,
"%s.flush_auth_cfg", FALSE, lib->ns),
.fragment_size = lib->settings->get_int(lib->settings,
"%s.fragment_size", 0, lib->ns),
+ .follow_redirects = lib->settings->get_bool(lib->settings,
+ "%s.follow_redirects", TRUE, lib->ns),
);
if (version == IKEV2)
diff --git a/src/libcharon/sa/ike_sa.h b/src/libcharon/sa/ike_sa.h
index e15ac2e38..158a690df 100644
--- a/src/libcharon/sa/ike_sa.h
+++ b/src/libcharon/sa/ike_sa.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2006-2014 Tobias Brunner
+ * Copyright (C) 2006-2015 Tobias Brunner
* Copyright (C) 2006 Daniel Roethlisberger
* Copyright (C) 2005-2009 Martin Willi
* Copyright (C) 2005 Jan Hutter
@@ -66,6 +66,16 @@ typedef struct ike_sa_t ike_sa_t;
#define RETRY_JITTER 20
/**
+ * Number of redirects allowed within REDIRECT_LOOP_DETECT_PERIOD.
+ */
+#define MAX_REDIRECTS 5
+
+/**
+ * Time period in seconds in which at most MAX_REDIRECTS are allowed.
+ */
+#define REDIRECT_LOOP_DETECT_PERIOD 300
+
+/**
* Extensions (or optional features) the peer supports
*/
enum ike_extension_t {
@@ -136,6 +146,11 @@ enum ike_extension_t {
* Signature Authentication, RFC 7427
*/
EXT_SIGNATURE_AUTH = (1<<12),
+
+ /**
+ * IKEv2 Redirect Mechanism, RFC 5685
+ */
+ EXT_IKE_REDIRECTION = (1<<13),
};
/**
@@ -197,6 +212,11 @@ enum ike_condition_t {
* This IKE_SA is currently being reauthenticated
*/
COND_REAUTHENTICATING = (1<<10),
+
+ /**
+ * This IKE_SA has been redirected
+ */
+ COND_REDIRECTED = (1<<11),
};
/**
@@ -843,6 +863,32 @@ struct ike_sa_t {
void (*send_keepalive) (ike_sa_t *this, bool scheduled);
/**
+ * Redirect an active IKE_SA.
+ *
+ * @param gateway gateway ID (IP or FQDN) of the target
+ * @return state, including DESTROY_ME, if this IKE_SA MUST be
+ * destroyed
+ */
+ status_t (*redirect)(ike_sa_t *this, identification_t *gateway);
+
+ /**
+ * Handle a redirect request.
+ *
+ * The behavior is different depending on the state of the IKE_SA.
+ *
+ * @param gateway gateway ID (IP or FQDN) of the target
+ * @return FALSE if redirect not possible, TRUE otherwise
+ */
+ bool (*handle_redirect)(ike_sa_t *this, identification_t *gateway);
+
+ /**
+ * Get the address of the gateway that redirected us.
+ *
+ * @return original gateway address
+ */
+ host_t *(*get_redirected_from)(ike_sa_t *this);
+
+ /**
* Get the keying material of this IKE_SA.
*
* @return per IKE_SA keymat instance
diff --git a/src/libcharon/sa/ikev2/task_manager_v2.c b/src/libcharon/sa/ikev2/task_manager_v2.c
index 4676867df..8ed86302b 100644
--- a/src/libcharon/sa/ikev2/task_manager_v2.c
+++ b/src/libcharon/sa/ikev2/task_manager_v2.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007-2014 Tobias Brunner
+ * Copyright (C) 2007-2015 Tobias Brunner
* Copyright (C) 2007-2010 Martin Willi
* Hochschule fuer Technik Rapperswil
*
@@ -30,6 +30,7 @@
#include <sa/ikev2/tasks/ike_rekey.h>
#include <sa/ikev2/tasks/ike_reauth.h>
#include <sa/ikev2/tasks/ike_reauth_complete.h>
+#include <sa/ikev2/tasks/ike_redirect.h>
#include <sa/ikev2/tasks/ike_delete.h>
#include <sa/ikev2/tasks/ike_config.h>
#include <sa/ikev2/tasks/ike_dpd.h>
@@ -474,6 +475,11 @@ METHOD(task_manager_t, initiate, status_t,
exchange = INFORMATIONAL;
break;
}
+ if (activate_task(this, TASK_IKE_REDIRECT))
+ {
+ exchange = INFORMATIONAL;
+ break;
+ }
if (activate_task(this, TASK_CHILD_DELETE))
{
exchange = INFORMATIONAL;
@@ -656,6 +662,32 @@ static status_t process_response(private_task_manager_t *this,
return DESTROY_ME;
}
+ enumerator = array_create_enumerator(this->active_tasks);
+ while (enumerator->enumerate(enumerator, &task))
+ {
+ if (!task->pre_process)
+ {
+ continue;
+ }
+ switch (task->pre_process(task, message))
+ {
+ case SUCCESS:
+ break;
+ case FAILED:
+ default:
+ /* just ignore the message */
+ DBG1(DBG_IKE, "ignore invalid %N response",
+ exchange_type_names, message->get_exchange_type(message));
+ enumerator->destroy(enumerator);
+ return SUCCESS;
+ case DESTROY_ME:
+ /* critical failure, destroy IKE_SA */
+ enumerator->destroy(enumerator);
+ return DESTROY_ME;
+ }
+ }
+ enumerator->destroy(enumerator);
+
/* catch if we get resetted while processing */
this->reset = FALSE;
enumerator = array_create_enumerator(this->active_tasks);
@@ -992,6 +1024,11 @@ static status_t process_request(private_task_manager_t *this,
* invokes all the required hooks. */
task = (task_t*)ike_delete_create(
this->ike_sa, FALSE);
+ break;
+ case REDIRECT:
+ task = (task_t*)ike_redirect_create(
+ this->ike_sa, NULL);
+ break;
default:
break;
}
@@ -1041,6 +1078,44 @@ static status_t process_request(private_task_manager_t *this,
}
}
+ enumerator = array_create_enumerator(this->passive_tasks);
+ while (enumerator->enumerate(enumerator, &task))
+ {
+ if (!task->pre_process)
+ {
+ continue;
+ }
+ switch (task->pre_process(task, message))
+ {
+ case SUCCESS:
+ break;
+ case FAILED:
+ default:
+ /* just ignore the message */
+ DBG1(DBG_IKE, "ignore invalid %N request",
+ exchange_type_names, message->get_exchange_type(message));
+ enumerator->destroy(enumerator);
+ switch (message->get_exchange_type(message))
+ {
+ case IKE_SA_INIT:
+ /* no point in keeping the SA when it was created with
+ * an invalid IKE_SA_INIT message */
+ return DESTROY_ME;
+ default:
+ /* remove tasks we queued for this request */
+ flush_queue(this, TASK_QUEUE_PASSIVE);
+ /* fall-through */
+ case IKE_AUTH:
+ return NEED_MORE;
+ }
+ case DESTROY_ME:
+ /* critical failure, destroy IKE_SA */
+ enumerator->destroy(enumerator);
+ return DESTROY_ME;
+ }
+ }
+ enumerator->destroy(enumerator);
+
/* let the tasks process the message */
enumerator = array_create_enumerator(this->passive_tasks);
while (enumerator->enumerate(enumerator, (void*)&task))
@@ -1331,12 +1406,17 @@ METHOD(task_manager_t, process_message, status_t,
{ /* ignore messages altered to EXCHANGE_TYPE_UNDEFINED */
return SUCCESS;
}
- if (process_request(this, msg) != SUCCESS)
+ switch (process_request(this, msg))
{
- flush(this);
- return DESTROY_ME;
+ case SUCCESS:
+ this->responding.mid++;
+ break;
+ case NEED_MORE:
+ break;
+ default:
+ flush(this);
+ return DESTROY_ME;
}
- this->responding.mid++;
}
else if ((mid == this->responding.mid - 1) &&
array_count(this->responding.packets))
diff --git a/src/libcharon/sa/ikev2/tasks/child_create.c b/src/libcharon/sa/ikev2/tasks/child_create.c
index 740d09778..3d4ded944 100644
--- a/src/libcharon/sa/ikev2/tasks/child_create.c
+++ b/src/libcharon/sa/ikev2/tasks/child_create.c
@@ -1220,6 +1220,10 @@ METHOD(task_t, build_r, status_t,
{ /* wait until all authentication round completed */
return NEED_MORE;
}
+ if (this->ike_sa->has_condition(this->ike_sa, COND_REDIRECTED))
+ { /* no CHILD_SA is created for redirected SAs */
+ return SUCCESS;
+ }
ike_auth = TRUE;
default:
break;
diff --git a/src/libcharon/sa/ikev2/tasks/ike_auth.c b/src/libcharon/sa/ikev2/tasks/ike_auth.c
index 2554496c1..79a436fbf 100644
--- a/src/libcharon/sa/ikev2/tasks/ike_auth.c
+++ b/src/libcharon/sa/ikev2/tasks/ike_auth.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 Tobias Brunner
+ * Copyright (C) 2012-2015 Tobias Brunner
* Copyright (C) 2005-2009 Martin Willi
* Copyright (C) 2005 Jan Hutter
* Hochschule fuer Technik Rapperswil
@@ -25,6 +25,7 @@
#include <encoding/payloads/eap_payload.h>
#include <encoding/payloads/nonce_payload.h>
#include <sa/ikev2/authenticators/eap_authenticator.h>
+#include <processing/jobs/delete_ike_sa_job.h>
typedef struct private_ike_auth_t private_ike_auth_t;
@@ -117,6 +118,11 @@ struct private_ike_auth_t {
* Is EAP acceptable, did we strictly authenticate peer?
*/
bool eap_acceptable;
+
+ /**
+ * Gateway ID if redirected
+ */
+ identification_t *redirect_to;
};
/**
@@ -685,6 +691,7 @@ METHOD(task_t, process_r, status_t,
METHOD(task_t, build_r, status_t,
private_ike_auth_t *this, message_t *message)
{
+ identification_t *gateway;
auth_cfg_t *cfg;
if (message->get_exchange_type(message) == IKE_SA_INIT)
@@ -817,34 +824,56 @@ METHOD(task_t, build_r, status_t,
{
this->do_another_auth = FALSE;
}
- if (!this->do_another_auth && !this->expect_another_auth)
+ if (this->do_another_auth || this->expect_another_auth)
{
- if (charon->ike_sa_manager->check_uniqueness(charon->ike_sa_manager,
- this->ike_sa, FALSE))
- {
- DBG1(DBG_IKE, "cancelling IKE_SA setup due to uniqueness policy");
- charon->bus->alert(charon->bus, ALERT_UNIQUE_KEEP);
- message->add_notify(message, TRUE, AUTHENTICATION_FAILED,
- chunk_empty);
- return FAILED;
- }
- if (!charon->bus->authorize(charon->bus, TRUE))
- {
- DBG1(DBG_IKE, "final authorization hook forbids IKE_SA, cancelling");
- goto peer_auth_failed;
- }
- DBG0(DBG_IKE, "IKE_SA %s[%d] established between %H[%Y]...%H[%Y]",
- this->ike_sa->get_name(this->ike_sa),
- this->ike_sa->get_unique_id(this->ike_sa),
- this->ike_sa->get_my_host(this->ike_sa),
- this->ike_sa->get_my_id(this->ike_sa),
- this->ike_sa->get_other_host(this->ike_sa),
- this->ike_sa->get_other_id(this->ike_sa));
- this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED);
- charon->bus->ike_updown(charon->bus, this->ike_sa, TRUE);
- return SUCCESS;
+ return NEED_MORE;
}
- return NEED_MORE;
+
+ if (charon->ike_sa_manager->check_uniqueness(charon->ike_sa_manager,
+ this->ike_sa, FALSE))
+ {
+ DBG1(DBG_IKE, "cancelling IKE_SA setup due to uniqueness policy");
+ charon->bus->alert(charon->bus, ALERT_UNIQUE_KEEP);
+ message->add_notify(message, TRUE, AUTHENTICATION_FAILED,
+ chunk_empty);
+ return FAILED;
+ }
+ if (!charon->bus->authorize(charon->bus, TRUE))
+ {
+ DBG1(DBG_IKE, "final authorization hook forbids IKE_SA, cancelling");
+ goto peer_auth_failed;
+ }
+ if (this->ike_sa->supports_extension(this->ike_sa, EXT_IKE_REDIRECTION) &&
+ charon->redirect->redirect_on_auth(charon->redirect, this->ike_sa,
+ &gateway))
+ {
+ delete_ike_sa_job_t *job;
+ chunk_t data;
+
+ DBG1(DBG_IKE, "redirecting peer to %Y", gateway);
+ data = redirect_data_create(gateway, chunk_empty);
+ message->add_notify(message, FALSE, REDIRECT, data);
+ gateway->destroy(gateway);
+ chunk_free(&data);
+ /* we use this condition to prevent the CHILD_SA from getting created */
+ this->ike_sa->set_condition(this->ike_sa, COND_REDIRECTED, TRUE);
+ /* if the peer does not delete the SA we do so after a while */
+ job = delete_ike_sa_job_create(this->ike_sa->get_id(this->ike_sa), TRUE);
+ lib->scheduler->schedule_job(lib->scheduler, (job_t*)job,
+ lib->settings->get_int(lib->settings,
+ "%s.half_open_timeout", HALF_OPEN_IKE_SA_TIMEOUT,
+ lib->ns));
+ }
+ DBG0(DBG_IKE, "IKE_SA %s[%d] established between %H[%Y]...%H[%Y]",
+ this->ike_sa->get_name(this->ike_sa),
+ this->ike_sa->get_unique_id(this->ike_sa),
+ this->ike_sa->get_my_host(this->ike_sa),
+ this->ike_sa->get_my_id(this->ike_sa),
+ this->ike_sa->get_other_host(this->ike_sa),
+ this->ike_sa->get_other_id(this->ike_sa));
+ this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED);
+ charon->bus->ike_updown(charon->bus, this->ike_sa, TRUE);
+ return SUCCESS;
peer_auth_failed:
message->add_notify(message, TRUE, AUTHENTICATION_FAILED, chunk_empty);
@@ -964,6 +993,15 @@ METHOD(task_t, process_i, status_t,
case ME_ENDPOINT:
/* handled in ike_me task */
break;
+ case REDIRECT:
+ DESTROY_IF(this->redirect_to);
+ this->redirect_to = redirect_data_parse(
+ notify->get_notification_data(notify), NULL);
+ if (!this->redirect_to)
+ {
+ DBG1(DBG_IKE, "received invalid REDIRECT notify");
+ }
+ break;
default:
{
if (type <= 16383)
@@ -1094,30 +1132,35 @@ METHOD(task_t, process_i, status_t,
{
this->expect_another_auth = FALSE;
}
- if (!this->expect_another_auth && !this->do_another_auth && !this->my_auth)
+ if (this->expect_another_auth || this->do_another_auth || this->my_auth)
{
- if (!update_cfg_candidates(this, TRUE))
- {
- goto peer_auth_failed;
- }
- if (!charon->bus->authorize(charon->bus, TRUE))
- {
- DBG1(DBG_IKE, "final authorization hook forbids IKE_SA, "
- "cancelling");
- goto peer_auth_failed;
- }
- DBG0(DBG_IKE, "IKE_SA %s[%d] established between %H[%Y]...%H[%Y]",
- this->ike_sa->get_name(this->ike_sa),
- this->ike_sa->get_unique_id(this->ike_sa),
- this->ike_sa->get_my_host(this->ike_sa),
- this->ike_sa->get_my_id(this->ike_sa),
- this->ike_sa->get_other_host(this->ike_sa),
- this->ike_sa->get_other_id(this->ike_sa));
- this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED);
- charon->bus->ike_updown(charon->bus, this->ike_sa, TRUE);
- return SUCCESS;
+ return NEED_MORE;
}
- return NEED_MORE;
+ if (!update_cfg_candidates(this, TRUE))
+ {
+ goto peer_auth_failed;
+ }
+ if (!charon->bus->authorize(charon->bus, TRUE))
+ {
+ DBG1(DBG_IKE, "final authorization hook forbids IKE_SA, "
+ "cancelling");
+ goto peer_auth_failed;
+ }
+ DBG0(DBG_IKE, "IKE_SA %s[%d] established between %H[%Y]...%H[%Y]",
+ this->ike_sa->get_name(this->ike_sa),
+ this->ike_sa->get_unique_id(this->ike_sa),
+ this->ike_sa->get_my_host(this->ike_sa),
+ this->ike_sa->get_my_id(this->ike_sa),
+ this->ike_sa->get_other_host(this->ike_sa),
+ this->ike_sa->get_other_id(this->ike_sa));
+ this->ike_sa->set_state(this->ike_sa, IKE_ESTABLISHED);
+ charon->bus->ike_updown(charon->bus, this->ike_sa, TRUE);
+
+ if (this->redirect_to)
+ {
+ this->ike_sa->handle_redirect(this->ike_sa, this->redirect_to);
+ }
+ return SUCCESS;
peer_auth_failed:
charon->bus->alert(charon->bus, ALERT_PEER_AUTH_FAILED);
@@ -1141,6 +1184,7 @@ METHOD(task_t, migrate, void,
DESTROY_IF(this->peer_cfg);
DESTROY_IF(this->my_auth);
DESTROY_IF(this->other_auth);
+ DESTROY_IF(this->redirect_to);
this->candidates->destroy_offset(this->candidates, offsetof(peer_cfg_t, destroy));
this->my_packet = NULL;
@@ -1149,6 +1193,7 @@ METHOD(task_t, migrate, void,
this->peer_cfg = NULL;
this->my_auth = NULL;
this->other_auth = NULL;
+ this->redirect_to = NULL;
this->do_another_auth = TRUE;
this->expect_another_auth = TRUE;
this->authentication_failed = FALSE;
@@ -1165,6 +1210,7 @@ METHOD(task_t, destroy, void,
DESTROY_IF(this->my_auth);
DESTROY_IF(this->other_auth);
DESTROY_IF(this->peer_cfg);
+ DESTROY_IF(this->redirect_to);
this->candidates->destroy_offset(this->candidates, offsetof(peer_cfg_t, destroy));
free(this);
}
diff --git a/src/libcharon/sa/ikev2/tasks/ike_config.c b/src/libcharon/sa/ikev2/tasks/ike_config.c
index 646f20c61..6c42b81a6 100644
--- a/src/libcharon/sa/ikev2/tasks/ike_config.c
+++ b/src/libcharon/sa/ikev2/tasks/ike_config.c
@@ -333,6 +333,11 @@ METHOD(task_t, build_r, status_t,
linked_list_t *vips, *pools;
host_t *requested;
+ if (this->ike_sa->has_condition(this->ike_sa, COND_REDIRECTED))
+ { /* don't assign attributes for redirected SAs */
+ return SUCCESS;
+ }
+
id = this->ike_sa->get_other_eap_id(this->ike_sa);
config = this->ike_sa->get_peer_cfg(this->ike_sa);
vips = linked_list_create();
diff --git a/src/libcharon/sa/ikev2/tasks/ike_init.c b/src/libcharon/sa/ikev2/tasks/ike_init.c
index 1ff643d62..5cfb51807 100644
--- a/src/libcharon/sa/ikev2/tasks/ike_init.c
+++ b/src/libcharon/sa/ikev2/tasks/ike_init.c
@@ -118,6 +118,11 @@ struct private_ike_init_t {
* Whether to use Signature Authentication as per RFC 7427
*/
bool signature_authentication;
+
+ /**
+ * Whether to follow IKEv2 redirects as per RFC 5685
+ */
+ bool follow_redirects;
};
/**
@@ -324,6 +329,29 @@ static bool build_payloads(private_ike_init_t *this, message_t *message)
send_supported_hash_algorithms(this, message);
}
}
+ /* notify other peer if we support redirection */
+ if (!this->old_sa && this->initiator && this->follow_redirects)
+ {
+ identification_t *gateway;
+ host_t *from;
+ chunk_t data;
+
+ from = this->ike_sa->get_redirected_from(this->ike_sa);
+ if (from)
+ {
+ gateway = identification_create_from_sockaddr(
+ from->get_sockaddr(from));
+ data = redirect_data_create(gateway, chunk_empty);
+ message->add_notify(message, FALSE, REDIRECTED_FROM, data);
+ chunk_free(&data);
+ gateway->destroy(gateway);
+ }
+ else
+ {
+ message->add_notify(message, FALSE, REDIRECT_SUPPORTED,
+ chunk_empty);
+ }
+ }
return TRUE;
}
@@ -391,6 +419,30 @@ static void process_payloads(private_ike_init_t *this, message_t *message)
handle_supported_hash_algorithms(this, notify);
}
break;
+ case REDIRECTED_FROM:
+ {
+ identification_t *gateway;
+ chunk_t data;
+
+ data = notify->get_notification_data(notify);
+ gateway = redirect_data_parse(data, NULL);
+ if (!gateway)
+ {
+ DBG1(DBG_IKE, "received invalid REDIRECTED_FROM "
+ "notify, ignored");
+ break;
+ }
+ DBG1(DBG_IKE, "client got redirected from %Y", gateway);
+ gateway->destroy(gateway);
+ /* fall-through */
+ }
+ case REDIRECT_SUPPORTED:
+ if (!this->old_sa)
+ {
+ this->ike_sa->enable_extension(this->ike_sa,
+ EXT_IKE_REDIRECTION);
+ }
+ break;
default:
/* other notifies are handled elsewhere */
break;
@@ -550,6 +602,8 @@ static bool derive_keys(private_ike_init_t *this,
METHOD(task_t, build_r, status_t,
private_ike_init_t *this, message_t *message)
{
+ identification_t *gateway;
+
/* check if we have everything we need */
if (this->proposal == NULL ||
this->other_nonce.len == 0 || this->my_nonce.len == 0)
@@ -560,6 +614,22 @@ METHOD(task_t, build_r, status_t,
}
this->ike_sa->set_proposal(this->ike_sa, this->proposal);
+ /* check if we'd have to redirect the client */
+ if (!this->old_sa &&
+ this->ike_sa->supports_extension(this->ike_sa, EXT_IKE_REDIRECTION) &&
+ charon->redirect->redirect_on_init(charon->redirect, this->ike_sa,
+ &gateway))
+ {
+ chunk_t data;
+
+ DBG1(DBG_IKE, "redirecting peer to %Y", gateway);
+ data = redirect_data_create(gateway, this->other_nonce);
+ message->add_notify(message, TRUE, REDIRECT, data);
+ gateway->destroy(gateway);
+ chunk_free(&data);
+ return FAILED;
+ }
+
if (this->dh == NULL ||
!this->proposal->has_dh_group(this->proposal, this->dh_group))
{
@@ -623,6 +693,54 @@ static void raise_alerts(private_ike_init_t *this, notify_type_t type)
}
}
+METHOD(task_t, pre_process_i, status_t,
+ private_ike_init_t *this, message_t *message)
+{
+ enumerator_t *enumerator;
+ payload_t *payload;
+
+ /* check for erroneous notifies */
+ enumerator = message->create_payload_enumerator(message);
+ while (enumerator->enumerate(enumerator, &payload))
+ {
+ if (payload->get_type(payload) == PLV2_NOTIFY)
+ {
+ notify_payload_t *notify = (notify_payload_t*)payload;
+ notify_type_t type = notify->get_notify_type(notify);
+
+ switch (type)
+ {
+ case REDIRECT:
+ {
+ identification_t *gateway;
+ chunk_t data, nonce = chunk_empty;
+ status_t status = SUCCESS;
+
+ if (this->old_sa)
+ {
+ break;
+ }
+ data = notify->get_notification_data(notify);
+ gateway = redirect_data_parse(data, &nonce);
+ if (!gateway || !chunk_equals(nonce, this->my_nonce))
+ {
+ DBG1(DBG_IKE, "received invalid REDIRECT notify");
+ status = FAILED;
+ }
+ DESTROY_IF(gateway);
+ chunk_free(&nonce);
+ enumerator->destroy(enumerator);
+ return status;
+ }
+ default:
+ break;
+ }
+ }
+ }
+ enumerator->destroy(enumerator);
+ return SUCCESS;
+}
+
METHOD(task_t, process_i, status_t,
private_ike_init_t *this, message_t *message)
{
@@ -678,6 +796,29 @@ METHOD(task_t, process_i, status_t,
this->retry++;
return NEED_MORE;
}
+ case REDIRECT:
+ {
+ identification_t *gateway;
+ chunk_t data, nonce = chunk_empty;
+ status_t status = FAILED;
+
+ if (this->old_sa)
+ {
+ DBG1(DBG_IKE, "received REDIRECT notify during rekeying"
+ ", ignored");
+ break;
+ }
+ data = notify->get_notification_data(notify);
+ gateway = redirect_data_parse(data, &nonce);
+ if (this->ike_sa->handle_redirect(this->ike_sa, gateway))
+ {
+ status = NEED_MORE;
+ }
+ DESTROY_IF(gateway);
+ chunk_free(&nonce);
+ enumerator->destroy(enumerator);
+ return status;
+ }
default:
{
if (type <= 16383)
@@ -802,6 +943,8 @@ ike_init_t *ike_init_create(ike_sa_t *ike_sa, bool initiator, ike_sa_t *old_sa)
.old_sa = old_sa,
.signature_authentication = lib->settings->get_bool(lib->settings,
"%s.signature_authentication", TRUE, lib->ns),
+ .follow_redirects = lib->settings->get_bool(lib->settings,
+ "%s.follow_redirects", TRUE, lib->ns),
);
this->nonceg = this->keymat->keymat.create_nonce_gen(&this->keymat->keymat);
@@ -809,6 +952,7 @@ ike_init_t *ike_init_create(ike_sa_t *ike_sa, bool initiator, ike_sa_t *old_sa)
{
this->public.task.build = _build_i;
this->public.task.process = _process_i;
+ this->public.task.pre_process = _pre_process_i;
}
else
{
diff --git a/src/libcharon/sa/ikev2/tasks/ike_redirect.c b/src/libcharon/sa/ikev2/tasks/ike_redirect.c
new file mode 100644
index 000000000..f82c80f71
--- /dev/null
+++ b/src/libcharon/sa/ikev2/tasks/ike_redirect.c
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2015 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_redirect.h"
+
+#include <daemon.h>
+#include <processing/jobs/delete_ike_sa_job.h>
+
+typedef struct private_ike_redirect_t private_ike_redirect_t;
+
+/**
+ * Private members
+ */
+struct private_ike_redirect_t {
+
+ /**
+ * Public interface
+ */
+ ike_redirect_t public;
+
+ /**
+ * Assigned IKE_SA
+ */
+ ike_sa_t *ike_sa;
+
+ /**
+ * Gateway ID to redirect to
+ */
+ identification_t *gateway;
+};
+
+METHOD(task_t, build_i, status_t,
+ private_ike_redirect_t *this, message_t *message)
+{
+ chunk_t data;
+
+ DBG1(DBG_IKE, "redirecting peer to %Y", this->gateway);
+ data = redirect_data_create(this->gateway, chunk_empty);
+ message->add_notify(message, FALSE, REDIRECT, data);
+ chunk_free(&data);
+ this->ike_sa->set_condition(this->ike_sa, COND_REDIRECTED, TRUE);
+ return NEED_MORE;
+}
+
+METHOD(task_t, process_r, status_t,
+ private_ike_redirect_t *this, message_t *message)
+{
+ notify_payload_t *notify;
+ identification_t *to;
+
+ notify = message->get_notify(message, REDIRECT);
+ if (!notify)
+ {
+ return SUCCESS;
+ }
+
+ to = redirect_data_parse(notify->get_notification_data(notify), NULL);
+ if (!to)
+ {
+ DBG1(DBG_IKE, "received invalid REDIRECT notify");
+ }
+ else
+ {
+ this->ike_sa->handle_redirect(this->ike_sa, to);
+ to->destroy(to);
+ }
+ return SUCCESS;
+}
+
+METHOD(task_t, build_r, status_t,
+ private_ike_redirect_t *this, message_t *message)
+{
+ /* not called because SUCCESS is returned above */
+ return SUCCESS;
+}
+
+METHOD(task_t, process_i, status_t,
+ private_ike_redirect_t *this, message_t *message)
+{
+ delete_ike_sa_job_t *job;
+
+ /* if the peer does not delete the SA we do so after a while */
+ job = delete_ike_sa_job_create(this->ike_sa->get_id(this->ike_sa), TRUE);
+ lib->scheduler->schedule_job(lib->scheduler, (job_t*)job,
+ lib->settings->get_int(lib->settings,
+ "%s.half_open_timeout", HALF_OPEN_IKE_SA_TIMEOUT,
+ lib->ns));
+ return SUCCESS;
+}
+
+METHOD(task_t, get_type, task_type_t,
+ private_ike_redirect_t *this)
+{
+ return TASK_IKE_REDIRECT;
+}
+
+METHOD(task_t, migrate, void,
+ private_ike_redirect_t *this, ike_sa_t *ike_sa)
+{
+ this->ike_sa = ike_sa;
+}
+
+METHOD(task_t, destroy, void,
+ private_ike_redirect_t *this)
+{
+ DESTROY_IF(this->gateway);
+ free(this);
+}
+
+/*
+ * Described in header.
+ */
+ike_redirect_t *ike_redirect_create(ike_sa_t *ike_sa, identification_t *to)
+{
+ private_ike_redirect_t *this;
+
+ INIT(this,
+ .public = {
+ .task = {
+ .get_type = _get_type,
+ .build = _build_r,
+ .process = _process_r,
+ .migrate = _migrate,
+ .destroy = _destroy,
+ },
+ },
+ .ike_sa = ike_sa,
+ );
+
+ if (to)
+ {
+ this->gateway = to->clone(to);
+ this->public.task.build = _build_i;
+ this->public.task.process = _process_i;
+ }
+
+ return &this->public;
+}
diff --git a/src/libcharon/sa/ikev2/tasks/ike_redirect.h b/src/libcharon/sa/ikev2/tasks/ike_redirect.h
new file mode 100644
index 000000000..afa00ce5d
--- /dev/null
+++ b/src/libcharon/sa/ikev2/tasks/ike_redirect.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+/**
+ * @defgroup ike_redirect ike_redirect
+ * @{ @ingroup tasks_v2
+ */
+
+#ifndef IKE_REDIRECT_H_
+#define IKE_REDIRECT_H_
+
+typedef struct ike_redirect_t ike_redirect_t;
+
+#include <library.h>
+#include <sa/ike_sa.h>
+#include <sa/task.h>
+
+/**
+ * Task that handles redirection requests for established SAs.
+ */
+struct ike_redirect_t {
+
+ /**
+ * Implements the task_t interface
+ */
+ task_t task;
+};
+
+/**
+ * Create a new ike_redirect_t task.
+ *
+ * As initiator (i.e. original responder) pass the ID of the target gateway,
+ * as responder (i.e. original initiator) this argument is NULL.
+ *
+ * @param ike_sa IKE_SA this task works for
+ * @param to gateway ID (gets cloned), or NULL as responder
+ * @return task instance
+ */
+ike_redirect_t *ike_redirect_create(ike_sa_t *ike_sa,
+ identification_t *to);
+
+#endif /** IKE_REDIRECT_H_ @}*/
diff --git a/src/libcharon/sa/redirect_manager.c b/src/libcharon/sa/redirect_manager.c
new file mode 100644
index 000000000..ff92ac29f
--- /dev/null
+++ b/src/libcharon/sa/redirect_manager.c
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2015 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 "redirect_manager.h"
+
+#include <collections/linked_list.h>
+#include <threading/rwlock.h>
+#include <bio/bio_reader.h>
+#include <bio/bio_writer.h>
+
+typedef struct private_redirect_manager_t private_redirect_manager_t;
+
+/**
+ * Private data
+ */
+struct private_redirect_manager_t {
+
+ /**
+ * Public interface
+ */
+ redirect_manager_t public;
+
+ /**
+ * Registered providers
+ */
+ linked_list_t *providers;
+
+ /**
+ * Lock to access list of providers
+ */
+ rwlock_t *lock;
+};
+
+
+/**
+ * Gateway identify types
+ *
+ * The encoding is the same as that for corresponding ID payloads.
+ */
+typedef enum {
+ /** IPv4 address of the VPN gateway */
+ GATEWAY_ID_TYPE_IPV4 = 1,
+ /** IPv6 address of the VPN gateway */
+ GATEWAY_ID_TYPE_IPV6 = 2,
+ /** FQDN of the VPN gateway */
+ GATEWAY_ID_TYPE_FQDN = 3,
+} gateway_id_type_t;
+
+/**
+ * Mapping of gateway identity types to identity types
+ */
+static id_type_t gateway_to_id_type(gateway_id_type_t type)
+{
+ switch (type)
+ {
+ case GATEWAY_ID_TYPE_IPV4:
+ return ID_IPV4_ADDR;
+ case GATEWAY_ID_TYPE_IPV6:
+ return ID_IPV6_ADDR;
+ case GATEWAY_ID_TYPE_FQDN:
+ return ID_FQDN;
+ default:
+ return 0;
+ }
+}
+
+/**
+ * Mapping of identity types to gateway identity types
+ */
+static gateway_id_type_t id_type_to_gateway(id_type_t type)
+{
+ switch (type)
+ {
+ case ID_IPV4_ADDR:
+ return GATEWAY_ID_TYPE_IPV4;
+ case ID_IPV6_ADDR:
+ return GATEWAY_ID_TYPE_IPV6;
+ case ID_FQDN:
+ return GATEWAY_ID_TYPE_FQDN;
+ default:
+ return 0;
+ }
+}
+
+METHOD(redirect_manager_t, add_provider, void,
+ private_redirect_manager_t *this, redirect_provider_t *provider)
+{
+ this->lock->write_lock(this->lock);
+ this->providers->insert_last(this->providers, provider);
+ this->lock->unlock(this->lock);
+}
+
+METHOD(redirect_manager_t, remove_provider, void,
+ private_redirect_manager_t *this, redirect_provider_t *provider)
+{
+ this->lock->write_lock(this->lock);
+ this->providers->remove(this->providers, provider, NULL);
+ this->lock->unlock(this->lock);
+}
+
+/**
+ * Determine whether a client should be redirected using the callback with the
+ * given offset into the redirect_provider_t interface.
+ */
+static bool should_redirect(private_redirect_manager_t *this, ike_sa_t *ike_sa,
+ identification_t **gateway, size_t offset)
+{
+ enumerator_t *enumerator;
+ void *provider;
+ bool redirect = FALSE;
+
+ this->lock->read_lock(this->lock);
+ enumerator = this->providers->create_enumerator(this->providers);
+ while (enumerator->enumerate(enumerator, &provider))
+ {
+ bool (**method)(void*,ike_sa_t*,identification_t**) = provider + offset;
+ if (*method && (*method)(provider, ike_sa, gateway))
+ {
+ if (*gateway && id_type_to_gateway((*gateway)->get_type(*gateway)))
+ {
+ redirect = TRUE;
+ break;
+ }
+ else
+ {
+ DBG1(DBG_CFG, "redirect provider returned invalid gateway ID");
+ DESTROY_IF(*gateway);
+ }
+ }
+ }
+ enumerator->destroy(enumerator);
+ this->lock->unlock(this->lock);
+ return redirect;
+}
+
+METHOD(redirect_manager_t, redirect_on_init, bool,
+ private_redirect_manager_t *this, ike_sa_t *ike_sa,
+ identification_t **gateway)
+{
+ return should_redirect(this, ike_sa, gateway,
+ offsetof(redirect_provider_t, redirect_on_init));
+}
+
+METHOD(redirect_manager_t, redirect_on_auth, bool,
+ private_redirect_manager_t *this, ike_sa_t *ike_sa,
+ identification_t **gateway)
+{
+ return should_redirect(this, ike_sa, gateway,
+ offsetof(redirect_provider_t, redirect_on_auth));
+}
+
+METHOD(redirect_manager_t, destroy, void,
+ private_redirect_manager_t *this)
+{
+ this->providers->destroy(this->providers);
+ this->lock->destroy(this->lock);
+ free(this);
+}
+
+/*
+ * Described in header
+ */
+redirect_manager_t *redirect_manager_create()
+{
+ private_redirect_manager_t *this;
+
+ INIT(this,
+ .public = {
+ .add_provider = _add_provider,
+ .remove_provider = _remove_provider,
+ .redirect_on_init = _redirect_on_init,
+ .redirect_on_auth = _redirect_on_auth,
+ .destroy = _destroy,
+ },
+ .providers = linked_list_create(),
+ .lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
+ );
+
+ return &this->public;
+}
+
+/*
+ * Encoding of a REDIRECT or REDIRECTED_FROM notify
+ *
+ 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Next Payload |C| RESERVED | Payload Length |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |Protocol ID(=0)| SPI Size (=0) | Notify Message Type |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | GW Ident Type | GW Ident Len | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ ~
+ ~ New Responder GW Identity ~
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | |
+ ~ Nonce Data ~
+ | |
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+*/
+
+/*
+ * Described in header
+ */
+chunk_t redirect_data_create(identification_t *gw, chunk_t nonce)
+{
+ gateway_id_type_t type;
+ bio_writer_t *writer;
+ chunk_t data;
+
+ type = id_type_to_gateway(gw->get_type(gw));
+ if (!type)
+ {
+ return chunk_empty;
+ }
+
+ writer = bio_writer_create(0);
+ writer->write_uint8(writer, type);
+ writer->write_data8(writer, gw->get_encoding(gw));
+ if (nonce.ptr)
+ {
+ writer->write_data(writer, nonce);
+ }
+
+ data = writer->extract_buf(writer);
+ writer->destroy(writer);
+ return data;
+}
+
+/*
+ * Described in header
+ */
+identification_t *redirect_data_parse(chunk_t data, chunk_t *nonce)
+{
+ bio_reader_t *reader;
+ id_type_t id_type;
+ chunk_t gateway;
+ u_int8_t type;
+
+ reader = bio_reader_create(data);
+ if (!reader->read_uint8(reader, &type) ||
+ !reader->read_data8(reader, &gateway))
+ {
+ DBG1(DBG_ENC, "invalid REDIRECT notify data");
+ reader->destroy(reader);
+ return NULL;
+ }
+ id_type = gateway_to_id_type(type);
+ if (!id_type)
+ {
+ DBG1(DBG_ENC, "invalid gateway ID type (%d) in REDIRECT notify", type);
+ reader->destroy(reader);
+ return NULL;
+ }
+ if (nonce)
+ {
+ *nonce = chunk_clone(reader->peek(reader));
+ }
+ reader->destroy(reader);
+ return identification_create_from_encoding(id_type, gateway);
+}
diff --git a/src/libcharon/sa/redirect_manager.h b/src/libcharon/sa/redirect_manager.h
new file mode 100644
index 000000000..45a727c29
--- /dev/null
+++ b/src/libcharon/sa/redirect_manager.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+/**
+ * @defgroup redirect_manager redirect_manager
+ * @{ @ingroup sa
+ */
+
+#ifndef REDIRECT_MANAGER_H_
+#define REDIRECT_MANAGER_H_
+
+typedef struct redirect_manager_t redirect_manager_t;
+
+#include <sa/redirect_provider.h>
+
+/**
+ * Manages redirect providers.
+ */
+struct redirect_manager_t {
+
+ /**
+ * Add a redirect provider.
+ *
+ * All registered providers are queried until one of them decides to
+ * redirect a client.
+ *
+ * A provider may be called concurrently for different IKE_SAs.
+ *
+ * @param provider provider to register
+ */
+ void (*add_provider)(redirect_manager_t *this,
+ redirect_provider_t *provider);
+
+ /**
+ * Remove a redirect provider.
+ *
+ * @param provider provider to unregister
+ */
+ void (*remove_provider)(redirect_manager_t *this,
+ redirect_provider_t *provider);
+
+ /**
+ * Determine whether a client should be redirected upon receipt of the
+ * IKE_SA_INIT message.
+ *
+ * @param ike_sa IKE_SA for which this is called
+ * @param gateway[out] new IKE gateway (IP or FQDN)
+ * @return TRUE if client should be redirected, FALSE otherwise
+ */
+ bool (*redirect_on_init)(redirect_manager_t *this, ike_sa_t *ike_sa,
+ identification_t **gateway);
+
+ /**
+ * Determine whether a client should be redirected after the IKE_AUTH has
+ * been handled. Should be called after the client is authenticated and
+ * when the server authenticates itself.
+ *
+ * @param ike_sa IKE_SA for which this is called
+ * @param gateway[out] new IKE gateway (IP or FQDN)
+ * @return TRUE if client should be redirected, FALSE otherwise
+ */
+ bool (*redirect_on_auth)(redirect_manager_t *this, ike_sa_t *ike_sa,
+ identification_t **gateway);
+
+ /**
+ * Destroy this instance.
+ */
+ void (*destroy)(redirect_manager_t *this);
+};
+
+/**
+ * Create a redirect manager instance.
+ *
+ * @return manager instance
+ */
+redirect_manager_t *redirect_manager_create();
+
+/**
+ * Create notification data of a REDIRECT or REDIRECT_FROM payload using the
+ * given gateway identity and optional nonce (only used during IKE_SA_INIT).
+ *
+ * @param gw gateway identity (IP or FQDN), gets cloned
+ * @param nonce nonce value, or chunk_empty, gets cloned
+ * @return notify data, chunk_empty if ID type is not supported
+ */
+chunk_t redirect_data_create(identification_t *gw, chunk_t nonce);
+
+/**
+ * Parse notification data of a REDIRECT or REDIRECTED_FROM notify payload.
+ *
+ * @param data notification data to parse
+ * @param nonce[out] nonce data (allocated), if any was provided
+ * @return gateway identity, NULL if data is invalid
+ */
+identification_t *redirect_data_parse(chunk_t data, chunk_t *nonce);
+
+#endif /** REDIRECT_MANAGER_H_ @}*/
diff --git a/src/libcharon/sa/redirect_provider.h b/src/libcharon/sa/redirect_provider.h
new file mode 100644
index 000000000..ef2288ffc
--- /dev/null
+++ b/src/libcharon/sa/redirect_provider.h
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+/**
+ * @defgroup redirect_provider redirect_provider
+ * @{ @ingroup sa
+ */
+
+#ifndef REDIRECT_PROVIDER_H_
+#define REDIRECT_PROVIDER_H_
+
+typedef struct redirect_provider_t redirect_provider_t;
+
+#include <library.h>
+#include <sa/ike_sa.h>
+
+/**
+ * Interface that allows implementations to decide whether a client is
+ * redirected during IKE_SA_INIT or IKE_AUTH using RFC 5685.
+ */
+struct redirect_provider_t {
+
+ /**
+ * Decide whether a client is redirect directly upon receipt of the
+ * IKE_SA_INIT message.
+ *
+ * @param ike_sa IKE_SA for which this is called
+ * @param gateway[out] new IKE gateway (IP or FQDN)
+ * @return TRUE if client should be redirected, FALSE otherwise
+ */
+ bool (*redirect_on_init)(redirect_provider_t *this, ike_sa_t *ike_sa,
+ identification_t **gateway);
+
+ /**
+ * Decide whether a client is redirect after the IKE_AUTH has been
+ * handled. This is called after the client is authenticated and when the
+ * server authenticates itself.
+ *
+ * @param ike_sa IKE_SA for which this is called
+ * @param gateway[out] new IKE gateway (IP or FQDN)
+ * @return TRUE if client should be redirected, FALSE otherwise
+ */
+ bool (*redirect_on_auth)(redirect_provider_t *this, ike_sa_t *ike_sa,
+ identification_t **gateway);
+};
+
+#endif /** REDIRECT_PROVIDER_H_ @}*/
diff --git a/src/libcharon/sa/task.c b/src/libcharon/sa/task.c
index b35b58185..4bd2ba221 100644
--- a/src/libcharon/sa/task.c
+++ b/src/libcharon/sa/task.c
@@ -28,6 +28,7 @@ ENUM(task_type_names, TASK_IKE_INIT, TASK_ISAKMP_CERT_POST,
"IKE_REKEY",
"IKE_REAUTH",
"IKE_REAUTH_COMPLETE",
+ "IKE_REDIRECT",
"IKE_DELETE",
"IKE_DPD",
"IKE_VENDOR",
diff --git a/src/libcharon/sa/task.h b/src/libcharon/sa/task.h
index 7bd3da1fe..b2e9d8886 100644
--- a/src/libcharon/sa/task.h
+++ b/src/libcharon/sa/task.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2007 Tobias Brunner
+ * Copyright (C) 2007-2015 Tobias Brunner
* Copyright (C) 2006 Martin Willi
* Hochschule fuer Technik Rapperswil
*
@@ -57,6 +57,8 @@ enum task_type_t {
TASK_IKE_REAUTH,
/** completion task for make-before-break IKE_SA re-authentication */
TASK_IKE_REAUTH_COMPLETE,
+ /** redirect an active IKE_SA */
+ TASK_IKE_REDIRECT,
/** delete an IKE_SA */
TASK_IKE_DELETE,
/** liveness check */
@@ -154,6 +156,18 @@ struct task_t {
status_t (*process) (task_t *this, message_t *message);
/**
+ * Verify a message before processing it (optional to implement by tasks).
+ *
+ * @param message message to verify
+ * @return
+ * - FAILED if verification is not successful, the
+ * message will be silently discarded
+ * - DESTROY_ME if IKE_SA has to be destroyed
+ * - SUCCESS if verification is successful
+ */
+ status_t (*pre_process) (task_t *this, message_t *message);
+
+ /**
* Get the type of the task implementation.
*/
task_type_t (*get_type) (task_t *this);
diff --git a/src/swanctl/Makefile.am b/src/swanctl/Makefile.am
index 5b6b8e4be..fb027149a 100644
--- a/src/swanctl/Makefile.am
+++ b/src/swanctl/Makefile.am
@@ -4,6 +4,7 @@ swanctl_SOURCES = \
command.c command.h \
commands/initiate.c \
commands/terminate.c \
+ commands/redirect.c \
commands/install.c \
commands/list_sas.c \
commands/list_pols.c \
diff --git a/src/swanctl/command.h b/src/swanctl/command.h
index 7eb11a68d..8d0a2e6b9 100644
--- a/src/swanctl/command.h
+++ b/src/swanctl/command.h
@@ -27,7 +27,7 @@
/**
* Maximum number of commands (+1).
*/
-#define MAX_COMMANDS 22
+#define MAX_COMMANDS 23
/**
* Maximum number of options in a command (+3)
diff --git a/src/swanctl/commands/redirect.c b/src/swanctl/commands/redirect.c
new file mode 100644
index 000000000..6edb936e6
--- /dev/null
+++ b/src/swanctl/commands/redirect.c
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2015 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 "command.h"
+
+#include <errno.h>
+
+static int redirect(vici_conn_t *conn)
+{
+ vici_req_t *req;
+ vici_res_t *res;
+ command_format_options_t format = COMMAND_FORMAT_NONE;
+ char *arg, *peer_ip = NULL, *peer_id = NULL, *ike = NULL, *gateway = NULL;
+ int ret = 0, ike_id = 0;
+
+ while (TRUE)
+ {
+ switch (command_getopt(&arg))
+ {
+ case 'h':
+ return command_usage(NULL);
+ case 'P':
+ format |= COMMAND_FORMAT_PRETTY;
+ /* fall through to raw */
+ case 'r':
+ format |= COMMAND_FORMAT_RAW;
+ continue;
+ case 'i':
+ ike = arg;
+ continue;
+ case 'I':
+ ike_id = atoi(arg);
+ continue;
+ case 'p':
+ peer_ip = arg;
+ continue;
+ case 'd':
+ peer_id = arg;
+ continue;
+ case 'g':
+ gateway = arg;
+ continue;
+ case EOF:
+ break;
+ default:
+ return command_usage("invalid --redirect option");
+ }
+ break;
+ }
+ req = vici_begin("redirect");
+ if (ike)
+ {
+ vici_add_key_valuef(req, "ike", "%s", ike);
+ }
+ if (ike_id)
+ {
+ vici_add_key_valuef(req, "ike-id", "%d", ike_id);
+ }
+ if (peer_ip)
+ {
+ vici_add_key_valuef(req, "peer-ip", "%s", peer_ip);
+ }
+ if (peer_id)
+ {
+ vici_add_key_valuef(req, "peer-id", "%s", peer_id);
+ }
+ if (gateway)
+ {
+ vici_add_key_valuef(req, "gateway", "%s", gateway);
+ }
+ res = vici_submit(req, conn);
+ if (!res)
+ {
+ ret = errno;
+ fprintf(stderr, "redirect request failed: %s\n", strerror(errno));
+ return ret;
+ }
+ if (format & COMMAND_FORMAT_RAW)
+ {
+ vici_dump(res, "redirect reply", format & COMMAND_FORMAT_PRETTY,
+ stdout);
+ }
+ else
+ {
+ if (streq(vici_find_str(res, "no", "success"), "yes"))
+ {
+ printf("redirect completed successfully\n");
+ }
+ else
+ {
+ fprintf(stderr, "redirect failed: %s\n",
+ vici_find_str(res, "", "errmsg"));
+ ret = 1;
+ }
+ }
+ vici_free_res(res);
+ return ret;
+}
+
+/**
+ * Register the command.
+ */
+static void __attribute__ ((constructor))reg()
+{
+ command_register((command_t) {
+ redirect, 'd', "redirect", "redirect an IKE_SA",
+ {"--ike <name> | --ike-id <id> | --peer-ip <ip|subnet|range>",
+ "--peer-id <id|wildcards> | --gateway <ip|fqdn> [--raw|--pretty]"},
+ {
+ {"help", 'h', 0, "show usage information"},
+ {"ike", 'i', 1, "redirect by IKE_SA name"},
+ {"ike-id", 'I', 1, "redirect by IKE_SA unique identifier"},
+ {"peer-ip", 'p', 1, "redirect by client IP"},
+ {"peer-id", 'd', 1, "redirect by IKE_SA name"},
+ {"gateway", 'g', 1, "target gateway (IP or FQDN)"},
+ {"raw", 'r', 0, "dump raw response message"},
+ {"pretty", 'P', 0, "dump raw response message in pretty print"},
+ }
+ });
+}
diff --git a/src/swanctl/swanctl.8.in b/src/swanctl/swanctl.8.in
index 4b49d3098..a3074601e 100644
--- a/src/swanctl/swanctl.8.in
+++ b/src/swanctl/swanctl.8.in
@@ -41,6 +41,10 @@ initiate a connection
\-\-terminate\fR
terminate a connection
.TP
+.B "\-d, \-\-redirect"
+\-\-redirect\fR
+redirect an IKE_SA
+.TP
.B "\-p, \-\-install"
install a trap or shunt policy
.TP