Skip to content

Commit ecba84a

Browse files
committed
child-delete: Don't send delete for expired CHILD_SAs that were already rekeyed
The peer might not have seen the CREATE_CHILD_SA response yet, receiving a DELETE for the SA could then trigger it to abort the rekeying, causing the deletion of the newly established SA (it can't know whether the DELETE was sent due to an expire or because the user manually deleted it). We just treat this SA as if we received a DELETE for it. This is not an ideal situation anyway, as it causes some traffic to get dropped, so it should usually be avoided by setting appropriate soft and hard limits. References #2815.
1 parent a9b9450 commit ecba84a

File tree

2 files changed

+32
-38
lines changed

2 files changed

+32
-38
lines changed

src/libcharon/sa/ikev2/tasks/child_delete.c

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,11 @@ static void install_outbound(private_child_delete_t *this,
174174
linked_list_t *my_ts, *other_ts;
175175
status_t status;
176176

177+
if (!spi)
178+
{
179+
return;
180+
}
181+
177182
child_sa = this->ike_sa->get_child_sa(this->ike_sa, protocol,
178183
spi, FALSE);
179184
if (!child_sa)
@@ -312,7 +317,7 @@ static status_t destroy_and_reestablish(private_child_delete_t *this)
312317
child_sa_t *child_sa;
313318
child_cfg_t *child_cfg;
314319
protocol_id_t protocol;
315-
uint32_t spi, reqid, rekey_spi;
320+
uint32_t spi, reqid;
316321
action_t action;
317322
status_t status = SUCCESS;
318323
time_t now, expire;
@@ -335,11 +340,7 @@ static status_t destroy_and_reestablish(private_child_delete_t *this)
335340
}
336341
else
337342
{
338-
rekey_spi = child_sa->get_rekey_spi(child_sa);
339-
if (rekey_spi)
340-
{
341-
install_outbound(this, protocol, rekey_spi);
342-
}
343+
install_outbound(this, protocol, child_sa->get_rekey_spi(child_sa));
343344
/* for rekeyed CHILD_SAs we uninstall the outbound SA but don't
344345
* immediately destroy it, by default, so we can process delayed
345346
* packets */
@@ -459,6 +460,17 @@ METHOD(task_t, build_i, status_t,
459460
this->spi = child_sa->get_spi(child_sa, TRUE);
460461
}
461462

463+
if (this->expired && child_sa->get_state(child_sa) == CHILD_REKEYED)
464+
{ /* the peer was expected to delete this SA, but if we send a DELETE
465+
* we might cause a collision there if the CREATE_CHILD_SA response
466+
* is delayed (the peer wouldn't know if we deleted this SA due to an
467+
* expire or because of a forced delete by the user and might then
468+
* ignore the CREATE_CHILD_SA response once it arrives) */
469+
child_sa->set_state(child_sa, CHILD_DELETED);
470+
install_outbound(this, this->protocol,
471+
child_sa->get_rekey_spi(child_sa));
472+
}
473+
462474
if (child_sa->get_state(child_sa) == CHILD_DELETED)
463475
{ /* DELETEs for this CHILD_SA were already exchanged, but it was not yet
464476
* destroyed to allow delayed packets to get processed */

src/libcharon/tests/suites/test_child_rekey.c

Lines changed: 14 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -370,8 +370,8 @@ END_TEST
370370

371371
/**
372372
* Check that the responder handles hard expires properly while waiting for the
373-
* delete after a rekeying (e.g. if the initiator of the rekeying fails to
374-
* delete the CHILD_SA for some reason).
373+
* delete after a rekeying (e.g. if the rekey settings are tight or the
374+
* CREATE_CHILD_SA response is delayed).
375375
*/
376376
START_TEST(test_regular_responder_handle_hard_expire)
377377
{
@@ -405,28 +405,22 @@ START_TEST(test_regular_responder_handle_hard_expire)
405405

406406
/* we don't expect this to get called anymore */
407407
assert_hook_not_called(child_rekey);
408-
/* this is similar to a regular delete collision */
409-
assert_single_payload(OUT, PLV2_DELETE);
408+
/* this is similar to a regular delete collision, but we don't actually
409+
* want to send a delete back as that might conflict with a delayed
410+
* CREATE_CHILD_SA response */
410411
call_ikesa(b, delete_child_sa, PROTO_ESP, 2, TRUE);
411-
assert_child_sa_state(b, 2, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
412-
assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
413-
/* since the SAs expired they would not actually be installed in the kernel
414-
* anymore and since we have not yet installed a new outbound SA this
415-
* will result in dropped packets and possibly acquires */
416-
assert_ipsec_sas_installed(b, 1, 2, 4);
412+
assert_child_sa_count(b, 1);
413+
assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
414+
/* the expire causes the outbound SA to get installed */
415+
assert_ipsec_sas_installed(b, 3, 4);
417416

418417
/* INFORMATIONAL { D } --> */
418+
assert_no_jobs_scheduled();
419419
assert_single_payload(IN, PLV2_DELETE);
420420
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
421-
assert_child_sa_state(b, 2, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
422-
assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_REGISTERED);
423-
assert_ipsec_sas_installed(b, 1, 2, 4);
424-
/* <-- INFORMATIONAL { D } */
425-
assert_single_payload(IN, PLV2_DELETE);
426-
exchange_test_helper->process_message(exchange_test_helper, a, NULL);
427-
assert_child_sa_state(a, 1, CHILD_DELETING, CHILD_OUTBOUND_INSTALLED);
428-
assert_child_sa_state(a, 3, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
429-
assert_ipsec_sas_installed(a, 1, 2, 3, 4);
421+
assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
422+
assert_ipsec_sas_installed(b, 3, 4);
423+
assert_scheduler();
430424
/* <-- INFORMATIONAL { } */
431425
assert_jobs_scheduled(1);
432426
assert_message_empty(IN);
@@ -436,23 +430,11 @@ START_TEST(test_regular_responder_handle_hard_expire)
436430
assert_child_sa_count(a, 2);
437431
assert_ipsec_sas_installed(a, 1, 3, 4);
438432
assert_scheduler();
439-
/* INFORMATIONAL { } --> */
440-
assert_jobs_scheduled(1);
441-
assert_message_empty(IN);
442-
exchange_test_helper->process_message(exchange_test_helper, b, NULL);
443-
assert_child_sa_state(b, 2, CHILD_DELETED, CHILD_OUTBOUND_NONE);
444-
assert_child_sa_state(b, 4, CHILD_INSTALLED, CHILD_OUTBOUND_INSTALLED);
445-
assert_child_sa_count(b, 2);
446-
assert_ipsec_sas_installed(b, 2, 3, 4);
447-
assert_scheduler();
448433

449-
/* simulate the execution of the scheduled jobs */
434+
/* simulate the execution of the scheduled job */
450435
destroy_rekeyed(a, 1);
451436
assert_child_sa_count(a, 1);
452437
assert_ipsec_sas_installed(a, 3, 4);
453-
destroy_rekeyed(b, 2);
454-
assert_child_sa_count(b, 1);
455-
assert_ipsec_sas_installed(b, 3, 4);
456438

457439
/* child_rekey/child_updown */
458440
assert_hook();

0 commit comments

Comments
 (0)