diff --git a/src/charon-nm/nm/nm_service.c b/src/charon-nm/nm/nm_service.c index 82e126ace..0e7035ae9 100644 --- a/src/charon-nm/nm/nm_service.c +++ b/src/charon-nm/nm/nm_service.c @@ -741,7 +741,7 @@ static gboolean do_disconnect(gpointer plugin) { id = ike_sa->get_unique_id(ike_sa); enumerator->destroy(enumerator); - charon->controller->terminate_ike(charon->controller, id, + charon->controller->terminate_ike(charon->controller, id, FALSE, controller_cb_empty, NULL, 0); return FALSE; } diff --git a/src/conftest/actions.c b/src/conftest/actions.c index 36c3c8e74..66e41f743 100644 --- a/src/conftest/actions.c +++ b/src/conftest/actions.c @@ -209,7 +209,8 @@ static job_requeue_t close_ike(char *config) if (id) { DBG1(DBG_CFG, "closing IKE_SA '%s'", config); - charon->controller->terminate_ike(charon->controller, id, NULL, NULL, 0); + charon->controller->terminate_ike(charon->controller, id, FALSE, NULL, + NULL, 0); } else { diff --git a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c index 806375c2f..b1a095bcd 100644 --- a/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c +++ b/src/frontends/android/app/src/main/jni/libandroidbridge/backend/android_service.c @@ -401,7 +401,7 @@ static void close_tun_device(private_android_service_t *this) CALLBACK(terminate, job_requeue_t, uint32_t *id) { - charon->controller->terminate_ike(charon->controller, *id, + charon->controller->terminate_ike(charon->controller, *id, FALSE, controller_cb_empty, NULL, 0); return JOB_REQUEUE_NONE; } diff --git a/src/frontends/osx/charon-xpc/xpc_channels.c b/src/frontends/osx/charon-xpc/xpc_channels.c index 6724841d9..d013e752b 100644 --- a/src/frontends/osx/charon-xpc/xpc_channels.c +++ b/src/frontends/osx/charon-xpc/xpc_channels.c @@ -131,7 +131,7 @@ static void stop_connection(private_xpc_channels_t *this, uint32_t ike_sa, { status_t status; - status = charon->controller->terminate_ike(charon->controller, ike_sa, + status = charon->controller->terminate_ike(charon->controller, ike_sa, FALSE, NULL, NULL, 0); xpc_dictionary_set_bool(reply, "success", status != NOT_FOUND); } diff --git a/src/libcharon/control/controller.c b/src/libcharon/control/controller.c index dd8255f41..3ff1c4b80 100644 --- a/src/libcharon/control/controller.c +++ b/src/libcharon/control/controller.c @@ -117,10 +117,17 @@ struct interface_listener_t { */ spinlock_t *lock; - /** - * whether to check limits - */ - bool limits; + union { + /** + * whether to check limits during initiation + */ + bool limits; + + /** + * whether to force termination + */ + bool force; + } options; }; @@ -423,7 +430,7 @@ METHOD(job_t, initiate_execute, job_requeue_t, } peer_cfg->destroy(peer_cfg); - if (listener->limits && ike_sa->get_state(ike_sa) == IKE_CREATED) + if (listener->options.limits && ike_sa->get_state(ike_sa) == IKE_CREATED) { /* only check if we are not reusing an IKE_SA */ u_int half_open, limit_half_open, limit_job_load; @@ -508,7 +515,7 @@ METHOD(controller_t, initiate, status_t, .child_cfg = child_cfg, .peer_cfg = peer_cfg, .lock = spinlock_create(), - .limits = limits, + .options.limits = limits, }, .public = { .execute = _initiate_execute, @@ -557,8 +564,8 @@ METHOD(job_t, terminate_ike_execute, job_requeue_t, listener->ike_sa = ike_sa; listener->lock->unlock(listener->lock); - if (ike_sa->delete(ike_sa) != DESTROY_ME) - { /* delete failed */ + if (ike_sa->delete(ike_sa, listener->options.force) != DESTROY_ME) + { /* delete queued */ listener->status = FAILED; charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa); } @@ -575,7 +582,7 @@ METHOD(job_t, terminate_ike_execute, job_requeue_t, } METHOD(controller_t, terminate_ike, status_t, - controller_t *this, uint32_t unique_id, + controller_t *this, uint32_t unique_id, bool force, controller_cb_t callback, void *param, u_int timeout) { interface_job_t *job; @@ -610,13 +617,24 @@ METHOD(controller_t, terminate_ike, status_t, if (callback == NULL) { + job->listener.options.force = force; terminate_ike_execute(job); } else { + if (!timeout) + { + job->listener.options.force = force; + } if (wait_for_listener(job, timeout)) { job->listener.status = OUT_OF_RES; + + if (force) + { /* force termination once timeout is reached */ + job->listener.options.force = TRUE; + terminate_ike_execute(job); + } } } status = job->listener.status; diff --git a/src/libcharon/control/controller.h b/src/libcharon/control/controller.h index 9524f53b9..7ba721860 100644 --- a/src/libcharon/control/controller.h +++ b/src/libcharon/control/controller.h @@ -102,6 +102,11 @@ struct controller_t { * until the IKE_SA is properly deleted, or the call timed out. * * @param unique_id unique id of the IKE_SA to terminate. + * @param force whether to immediately destroy the IKE_SA without + * waiting for a response or retransmitting the delete, + * if a callback is provided and timeout is > 0 the + * IKE_SA is destroyed once the timeout is reached but + * retransmits are sent until then * @param cb logging callback * @param param parameter to include in each call of cb * @param timeout timeout in ms to wait for callbacks, 0 to disable @@ -112,7 +117,7 @@ struct controller_t { * - OUT_OF_RES if timed out */ status_t (*terminate_ike)(controller_t *this, uint32_t unique_id, - controller_cb_t callback, void *param, + bool force, controller_cb_t callback, void *param, u_int timeout); /** diff --git a/src/libcharon/plugins/smp/smp.c b/src/libcharon/plugins/smp/smp.c index 56891b263..d4b01f42c 100644 --- a/src/libcharon/plugins/smp/smp.c +++ b/src/libcharon/plugins/smp/smp.c @@ -415,7 +415,7 @@ static void request_control_terminate(xmlTextReaderPtr reader, if (ike) { status = charon->controller->terminate_ike( - charon->controller, id, + charon->controller, id, FALSE, (controller_cb_t)xml_callback, writer, 0); } else diff --git a/src/libcharon/plugins/stroke/stroke_control.c b/src/libcharon/plugins/stroke/stroke_control.c index d3c279131..016f0e71b 100644 --- a/src/libcharon/plugins/stroke/stroke_control.c +++ b/src/libcharon/plugins/stroke/stroke_control.c @@ -316,7 +316,8 @@ static void charon_terminate(private_stroke_control_t *this, uint32_t id, else { status = charon->controller->terminate_ike(charon->controller, id, - (controller_cb_t)stroke_log, &info, this->timeout); + FALSE, (controller_cb_t)stroke_log, &info, + this->timeout); } report_terminate_status(this, status, out, id, child); } @@ -327,7 +328,7 @@ static void charon_terminate(private_stroke_control_t *this, uint32_t id, } else { - charon->controller->terminate_ike(charon->controller, id, + charon->controller->terminate_ike(charon->controller, id, FALSE, NULL, NULL, 0); } } diff --git a/src/libcharon/plugins/uci/uci_control.c b/src/libcharon/plugins/uci/uci_control.c index a7d26e67d..22fd3c27d 100644 --- a/src/libcharon/plugins/uci/uci_control.c +++ b/src/libcharon/plugins/uci/uci_control.c @@ -180,7 +180,7 @@ static void terminate(private_uci_control_t *this, char *name) { id = ike_sa->get_unique_id(ike_sa); enumerator->destroy(enumerator); - charon->controller->terminate_ike(charon->controller, id, + charon->controller->terminate_ike(charon->controller, id, FALSE, controller_cb_empty, NULL, 0); write_fifo(this, "connection '%s' terminated\n", name); return; diff --git a/src/libcharon/plugins/vici/README.md b/src/libcharon/plugins/vici/README.md index 49cce379d..0038f0844 100644 --- a/src/libcharon/plugins/vici/README.md +++ b/src/libcharon/plugins/vici/README.md @@ -279,7 +279,9 @@ Terminates an SA while streaming _control-log_ events. ike = child-id = ike-id = - timeout = + force = + timeout = loglevel = } => { success = diff --git a/src/libcharon/plugins/vici/vici_config.c b/src/libcharon/plugins/vici/vici_config.c index 2faa66e91..c85161550 100644 --- a/src/libcharon/plugins/vici/vici_config.c +++ b/src/libcharon/plugins/vici/vici_config.c @@ -2083,7 +2083,7 @@ static void clear_start_action(private_vici_config_t *this, char *peer_name, while (array_remove(ikeids, ARRAY_HEAD, &id)) { DBG1(DBG_CFG, "closing IKE_SA #%u", id); - charon->controller->terminate_ike(charon->controller, + charon->controller->terminate_ike(charon->controller, FALSE, id, NULL, NULL, 0); } array_destroy(ikeids); diff --git a/src/libcharon/plugins/vici/vici_control.c b/src/libcharon/plugins/vici/vici_control.c index f9c7d8f19..ce19608dc 100644 --- a/src/libcharon/plugins/vici/vici_control.c +++ b/src/libcharon/plugins/vici/vici_control.c @@ -225,6 +225,7 @@ CALLBACK(terminate, vici_message_t*, enumerator_t *enumerator, *isas, *csas; char *child, *ike, *errmsg = NULL; u_int child_id, ike_id, current, *del, done = 0; + bool force; int timeout; ike_sa_t *ike_sa; child_sa_t *child_sa; @@ -240,6 +241,7 @@ CALLBACK(terminate, vici_message_t*, ike = request->get_str(request, NULL, "ike"); child_id = request->get_int(request, 0, "child-id"); ike_id = request->get_int(request, 0, "ike-id"); + force = request->get_bool(request, FALSE, "force"); timeout = request->get_int(request, 0, "timeout"); log.level = request->get_int(request, 1, "loglevel"); @@ -326,7 +328,7 @@ CALLBACK(terminate, vici_message_t*, } else { - if (charon->controller->terminate_ike(charon->controller, *del, + if (charon->controller->terminate_ike(charon->controller, *del, force, log_cb, &log, timeout) == SUCCESS) { done++; diff --git a/src/libcharon/processing/jobs/delete_ike_sa_job.c b/src/libcharon/processing/jobs/delete_ike_sa_job.c index 53a170510..d539e4b94 100644 --- a/src/libcharon/processing/jobs/delete_ike_sa_job.c +++ b/src/libcharon/processing/jobs/delete_ike_sa_job.c @@ -64,7 +64,7 @@ METHOD(job_t, execute, job_requeue_t, } if (this->delete_if_established) { - if (ike_sa->delete(ike_sa) == DESTROY_ME) + if (ike_sa->delete(ike_sa, FALSE) == DESTROY_ME) { charon->ike_sa_manager->checkin_and_destroy( charon->ike_sa_manager, ike_sa); diff --git a/src/libcharon/processing/jobs/inactivity_job.c b/src/libcharon/processing/jobs/inactivity_job.c index bf16e51b5..e46861f09 100644 --- a/src/libcharon/processing/jobs/inactivity_job.c +++ b/src/libcharon/processing/jobs/inactivity_job.c @@ -101,7 +101,7 @@ METHOD(job_t, execute, job_requeue_t, { DBG1(DBG_JOB, "deleting IKE_SA after %d seconds " "of CHILD_SA inactivity", this->timeout); - status = ike_sa->delete(ike_sa); + status = ike_sa->delete(ike_sa, FALSE); } else { diff --git a/src/libcharon/sa/ike_sa.c b/src/libcharon/sa/ike_sa.c index 18d1c2441..f39fed6f0 100644 --- a/src/libcharon/sa/ike_sa.c +++ b/src/libcharon/sa/ike_sa.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2017 Tobias Brunner + * Copyright (C) 2006-2018 Tobias Brunner * Copyright (C) 2006 Daniel Roethlisberger * Copyright (C) 2005-2009 Martin Willi * Copyright (C) 2005 Jan Hutter @@ -1793,8 +1793,10 @@ METHOD(ike_sa_t, destroy_child_sa, status_t, } METHOD(ike_sa_t, delete_, status_t, - private_ike_sa_t *this) + private_ike_sa_t *this, bool force) { + status_t status = DESTROY_ME; + switch (this->state) { case IKE_ESTABLISHED: @@ -1806,19 +1808,38 @@ METHOD(ike_sa_t, delete_, status_t, charon->bus->alert(charon->bus, ALERT_IKE_SA_EXPIRED); } this->task_manager->queue_ike_delete(this->task_manager); - return this->task_manager->initiate(this->task_manager); + status = this->task_manager->initiate(this->task_manager); + break; case IKE_CREATED: DBG1(DBG_IKE, "deleting unestablished IKE_SA"); break; case IKE_PASSIVE: break; default: - DBG1(DBG_IKE, "destroying IKE_SA in state %N " - "without notification", ike_sa_state_names, this->state); - charon->bus->ike_updown(charon->bus, &this->public, FALSE); + DBG1(DBG_IKE, "destroying IKE_SA in state %N without notification", + ike_sa_state_names, this->state); + force = TRUE; break; } - return DESTROY_ME; + + if (force) + { + status = DESTROY_ME; + + if (this->version == IKEV2) + { /* for IKEv1 we trigger this in the ISAKMP delete task */ + switch (this->state) + { + case IKE_ESTABLISHED: + case IKE_REKEYING: + case IKE_DELETING: + charon->bus->ike_updown(charon->bus, &this->public, FALSE); + default: + break; + } + } + } + return status; } METHOD(ike_sa_t, rekey, status_t, diff --git a/src/libcharon/sa/ike_sa.h b/src/libcharon/sa/ike_sa.h index b4fbc56d7..316b713ee 100644 --- a/src/libcharon/sa/ike_sa.h +++ b/src/libcharon/sa/ike_sa.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2017 Tobias Brunner + * Copyright (C) 2006-2018 Tobias Brunner * Copyright (C) 2006 Daniel Roethlisberger * Copyright (C) 2005-2009 Martin Willi * Copyright (C) 2005 Jan Hutter @@ -776,15 +776,18 @@ struct ike_sa_t { * * Sends a delete message to the remote peer and waits for * its response. If the response comes in, or a timeout occurs, - * the IKE SA gets deleted. + * the IKE SA gets destroyed, unless force is TRUE then the IKE_SA is + * destroyed immediately without waiting for a response. * + * @param force whether to immediately destroy the IKE_SA afterwards + * without waiting for a response * @return * - SUCCESS if deletion is initialized - * - DESTROY_ME, if the IKE_SA is not in - * an established state and can not be - * deleted (but destroyed). + * - DESTROY_ME, if destroying is forced, or the IKE_SA + * is not in an established state and can not be + * deleted (but destroyed) */ - status_t (*delete) (ike_sa_t *this); + status_t (*delete) (ike_sa_t *this, bool force); /** * Update IKE_SAs after network interfaces have changed. diff --git a/src/libcharon/sa/ike_sa_manager.c b/src/libcharon/sa/ike_sa_manager.c index 101d98678..2a499db40 100644 --- a/src/libcharon/sa/ike_sa_manager.c +++ b/src/libcharon/sa/ike_sa_manager.c @@ -2021,7 +2021,7 @@ static status_t enforce_replace(private_ike_sa_manager_t *this, } DBG1(DBG_IKE, "deleting duplicate IKE_SA for peer '%Y' due to " "uniqueness policy", other); - return duplicate->delete(duplicate); + return duplicate->delete(duplicate, FALSE); } METHOD(ike_sa_manager_t, check_uniqueness, bool, @@ -2266,20 +2266,7 @@ METHOD(ike_sa_manager_t, flush, void, while (enumerator->enumerate(enumerator, &entry, &segment)) { charon->bus->set_sa(charon->bus, entry->ike_sa); - if (entry->ike_sa->get_version(entry->ike_sa) == IKEV2) - { /* as the delete never gets processed, fire down events */ - switch (entry->ike_sa->get_state(entry->ike_sa)) - { - case IKE_ESTABLISHED: - case IKE_REKEYING: - case IKE_DELETING: - charon->bus->ike_updown(charon->bus, entry->ike_sa, FALSE); - break; - default: - break; - } - } - entry->ike_sa->delete(entry->ike_sa); + entry->ike_sa->delete(entry->ike_sa, TRUE); } enumerator->destroy(enumerator); diff --git a/src/libcharon/sa/ikev2/tasks/ike_rekey.c b/src/libcharon/sa/ikev2/tasks/ike_rekey.c index 2f0552a33..11123b415 100644 --- a/src/libcharon/sa/ikev2/tasks/ike_rekey.c +++ b/src/libcharon/sa/ikev2/tasks/ike_rekey.c @@ -363,7 +363,7 @@ METHOD(task_t, process_i, status_t, /* IKE_SAs in state IKE_REKEYED are silently deleted, so we use * IKE_REKEYING */ this->new_sa->set_state(this->new_sa, IKE_REKEYING); - if (this->new_sa->delete(this->new_sa) == DESTROY_ME) + if (this->new_sa->delete(this->new_sa, FALSE) == DESTROY_ME) { this->new_sa->destroy(this->new_sa); } diff --git a/src/libcharon/tests/suites/test_child_delete.c b/src/libcharon/tests/suites/test_child_delete.c index 437e919c7..8660d7291 100644 --- a/src/libcharon/tests/suites/test_child_delete.c +++ b/src/libcharon/tests/suites/test_child_delete.c @@ -290,7 +290,7 @@ START_TEST(test_collision_ike_delete) } call_ikesa(a, delete_child_sa, PROTO_ESP, spi_a, FALSE); assert_child_sa_state(a, spi_a, CHILD_DELETING); - call_ikesa(b, delete); + call_ikesa(b, delete, FALSE); assert_ike_sa_state(b, IKE_DELETING); /* RFC 7296, 2.25.2 does not explicitly state what the behavior SHOULD be if diff --git a/src/libcharon/tests/suites/test_child_rekey.c b/src/libcharon/tests/suites/test_child_rekey.c index 6bf3588a1..51d577cd8 100644 --- a/src/libcharon/tests/suites/test_child_rekey.c +++ b/src/libcharon/tests/suites/test_child_rekey.c @@ -1906,7 +1906,7 @@ START_TEST(test_collision_ike_delete) &a, &b, NULL); } initiate_rekey(a, spi_a); - call_ikesa(b, delete); + call_ikesa(b, delete, FALSE); assert_ike_sa_state(b, IKE_DELETING); /* this should never get called as there is no successful rekeying on diff --git a/src/libcharon/tests/suites/test_ike_delete.c b/src/libcharon/tests/suites/test_ike_delete.c index d79f9bc50..7633718d4 100644 --- a/src/libcharon/tests/suites/test_ike_delete.c +++ b/src/libcharon/tests/suites/test_ike_delete.c @@ -40,7 +40,7 @@ START_TEST(test_regular) } assert_hook_not_called(ike_updown); assert_hook_not_called(child_updown); - call_ikesa(a, delete); + call_ikesa(a, delete, FALSE); assert_ike_sa_state(a, IKE_DELETING); assert_hook(); assert_hook(); @@ -81,9 +81,9 @@ START_TEST(test_collision) assert_hook_not_called(ike_updown); assert_hook_not_called(child_updown); - call_ikesa(a, delete); + call_ikesa(a, delete, FALSE); assert_ike_sa_state(a, IKE_DELETING); - call_ikesa(b, delete); + call_ikesa(b, delete, FALSE); assert_ike_sa_state(b, IKE_DELETING); assert_hook(); assert_hook(); diff --git a/src/libcharon/tests/suites/test_ike_rekey.c b/src/libcharon/tests/suites/test_ike_rekey.c index e22a0c288..b6a015445 100644 --- a/src/libcharon/tests/suites/test_ike_rekey.c +++ b/src/libcharon/tests/suites/test_ike_rekey.c @@ -1319,7 +1319,7 @@ START_TEST(test_collision_delete) assert_hook_not_called(ike_rekey); initiate_rekey(a); - call_ikesa(b, delete); + call_ikesa(b, delete, FALSE); assert_ike_sa_state(b, IKE_DELETING); /* RFC 7296, 2.25.2: If a peer receives a request to rekey an IKE SA that @@ -1401,7 +1401,7 @@ START_TEST(test_collision_delete_drop_delete) assert_hook_not_called(ike_rekey); initiate_rekey(a); - call_ikesa(b, delete); + call_ikesa(b, delete, FALSE); assert_ike_sa_state(b, IKE_DELETING); /* RFC 7296, 2.25.2: If a peer receives a request to rekey an IKE SA that diff --git a/src/swanctl/commands/terminate.c b/src/swanctl/commands/terminate.c index 8b3233c89..bce404a54 100644 --- a/src/swanctl/commands/terminate.c +++ b/src/swanctl/commands/terminate.c @@ -39,6 +39,7 @@ static int terminate(vici_conn_t *conn) command_format_options_t format = COMMAND_FORMAT_NONE; char *arg, *child = NULL, *ike = NULL; int ret = 0, timeout = 0, level = 1, child_id = 0, ike_id = 0; + bool force = FALSE; while (TRUE) { @@ -55,6 +56,9 @@ static int terminate(vici_conn_t *conn) case 'c': child = arg; continue; + case 'f': + force = TRUE; + continue; case 'i': ike = arg; continue; @@ -101,6 +105,10 @@ static int terminate(vici_conn_t *conn) { vici_add_key_valuef(req, "ike-id", "%d", ike_id); } + if (force) + { + vici_add_key_valuef(req, "force", "yes"); + } if (timeout) { vici_add_key_valuef(req, "timeout", "%d", timeout * 1000); @@ -150,6 +158,7 @@ static void __attribute__ ((constructor))reg() {"ike", 'i', 1, "terminate by IKE_SA name"}, {"child-id", 'C', 1, "terminate by CHILD_SA reqid"}, {"ike-id", 'I', 1, "terminate by IKE_SA unique identifier"}, + {"force", 'f', 0, "terminate IKE_SA without waiting, unless timeout is set"}, {"timeout", 't', 1, "timeout in seconds before detaching"}, {"raw", 'r', 0, "dump raw response message"}, {"pretty", 'P', 0, "dump raw response message in pretty print"},