diff options
author | Tobias Brunner <tobias@strongswan.org> | 2016-03-04 16:03:07 +0100 |
---|---|---|
committer | Tobias Brunner <tobias@strongswan.org> | 2016-03-04 16:03:07 +0100 |
commit | 765db8d2fe9949c2a38d5f393f13e4101dc25ce4 (patch) | |
tree | c7694752dd55cc98f921f42d2492679633b10644 /src | |
parent | ad82c95f0a8e2733f9579c8c644baf4685cb5a34 (diff) | |
parent | 47701e1178a91da363a61bd0478932352bfde2af (diff) | |
download | strongswan-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')
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 |