mirror of
https://github.com/strongswan/strongswan.git
synced 2025-10-04 00:00:14 -04:00
Merge branch 'ike-sa-flush'
This fixes a race condition during shutdown between the main thread flushing the IKE_SA manager and worker threads still creating IKE_SAs. Closes strongswan/strongswan#1252
This commit is contained in:
commit
33f5e23c4e
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2008-2021 Tobias Brunner
|
||||
* Copyright (C) 2008-2022 Tobias Brunner
|
||||
* Copyright (C) 2005-2011 Martin Willi
|
||||
* Copyright (C) 2005 Jan Hutter
|
||||
*
|
||||
@ -65,14 +65,9 @@ struct entry_t {
|
||||
thread_t *checked_out;
|
||||
|
||||
/**
|
||||
* Does this SA drives out new threads?
|
||||
* Whether threads are prevented from getting this IKE_SA.
|
||||
*/
|
||||
bool driveout_new_threads;
|
||||
|
||||
/**
|
||||
* Does this SA drives out waiting threads?
|
||||
*/
|
||||
bool driveout_waiting_threads;
|
||||
bool driveout_threads;
|
||||
|
||||
/**
|
||||
* Identification of an IKE_SA (SPIs).
|
||||
@ -350,6 +345,12 @@ struct private_ike_sa_manager_t {
|
||||
*/
|
||||
u_int segment_mask;
|
||||
|
||||
/**
|
||||
* Enabled while shutting down to mark all new entries so no threads
|
||||
* will check them out.
|
||||
*/
|
||||
bool new_entries_driveout_threads;
|
||||
|
||||
/**
|
||||
* Hash table with half_open_t objects.
|
||||
*/
|
||||
@ -631,6 +632,8 @@ static u_int put_entry(private_ike_sa_manager_t *this, entry_t *entry)
|
||||
segment = row & this->segment_mask;
|
||||
|
||||
lock_single_segment(this, segment);
|
||||
/* mark entry during shutdown */
|
||||
entry->driveout_threads = this->new_entries_driveout_threads;
|
||||
current = this->ike_sa_table[row];
|
||||
if (current)
|
||||
{ /* insert at the front of current bucket */
|
||||
@ -759,12 +762,12 @@ static status_t get_entry_by_sa(private_ike_sa_manager_t *this,
|
||||
static bool wait_for_entry(private_ike_sa_manager_t *this, entry_t *entry,
|
||||
u_int segment)
|
||||
{
|
||||
if (entry->driveout_new_threads)
|
||||
if (entry->driveout_threads)
|
||||
{
|
||||
/* we are not allowed to get this */
|
||||
return FALSE;
|
||||
}
|
||||
while (entry->checked_out && !entry->driveout_waiting_threads)
|
||||
while (entry->checked_out && !entry->driveout_threads)
|
||||
{
|
||||
/* so wait until we can get it for us.
|
||||
* we register us as waiting. */
|
||||
@ -773,7 +776,7 @@ static bool wait_for_entry(private_ike_sa_manager_t *this, entry_t *entry,
|
||||
entry->waiting_threads--;
|
||||
}
|
||||
/* hm, a deletion request forbids us to get this SA, get next one */
|
||||
if (entry->driveout_waiting_threads)
|
||||
if (entry->driveout_threads)
|
||||
{
|
||||
/* we must signal here, others may be waiting on it, too */
|
||||
entry->condvar->signal(entry->condvar);
|
||||
@ -1761,7 +1764,7 @@ METHOD(ike_sa_manager_t, new_initiator_spi, bool,
|
||||
|
||||
if (get_entry_by_sa(this, ike_sa_id, ike_sa, &entry, &segment) == SUCCESS)
|
||||
{
|
||||
if (entry->driveout_waiting_threads && entry->driveout_new_threads)
|
||||
if (entry->driveout_threads)
|
||||
{ /* it looks like flush() has been called and the SA is being deleted
|
||||
* anyway, no need for a new SPI */
|
||||
DBG2(DBG_MGR, "ignored change of initiator SPI during shutdown");
|
||||
@ -1833,8 +1836,7 @@ CALLBACK(enumerator_filter_skip, bool,
|
||||
|
||||
while (orig->enumerate(orig, &entry, &segment))
|
||||
{
|
||||
if (!entry->driveout_new_threads &&
|
||||
!entry->driveout_waiting_threads &&
|
||||
if (!entry->driveout_threads &&
|
||||
!entry->checked_out)
|
||||
{
|
||||
*out = entry->ike_sa;
|
||||
@ -1997,7 +1999,7 @@ METHOD(ike_sa_manager_t, checkin_and_destroy, void,
|
||||
|
||||
if (get_entry_by_sa(this, ike_sa_id, ike_sa, &entry, &segment) == SUCCESS)
|
||||
{
|
||||
if (entry->driveout_waiting_threads && entry->driveout_new_threads)
|
||||
if (entry->driveout_threads)
|
||||
{ /* it looks like flush() has been called and the SA is being deleted
|
||||
* anyway, just check it in */
|
||||
DBG2(DBG_MGR, "ignored checkin and destroy of IKE_SA during shutdown");
|
||||
@ -2007,10 +2009,8 @@ METHOD(ike_sa_manager_t, checkin_and_destroy, void,
|
||||
return;
|
||||
}
|
||||
|
||||
/* drive out waiting threads, as we are in hurry */
|
||||
entry->driveout_waiting_threads = TRUE;
|
||||
/* mark it, so no new threads can get this entry */
|
||||
entry->driveout_new_threads = TRUE;
|
||||
/* mark it, so no threads can get this entry */
|
||||
entry->driveout_threads = TRUE;
|
||||
/* wait until all workers have done their work */
|
||||
while (entry->waiting_threads)
|
||||
{
|
||||
@ -2351,6 +2351,29 @@ METHOD(ike_sa_manager_t, set_spi_cb, void,
|
||||
this->spi_lock->unlock(this->spi_lock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy a single entry
|
||||
*/
|
||||
static void remove_and_destroy_entry(private_ike_sa_manager_t *this,
|
||||
enumerator_t *enumerator, entry_t *entry)
|
||||
{
|
||||
if (entry->half_open)
|
||||
{
|
||||
remove_half_open(this, entry->other,
|
||||
entry->ike_sa_id->is_initiator(entry->ike_sa_id));
|
||||
}
|
||||
if (entry->my_id && entry->other_id)
|
||||
{
|
||||
remove_connected_peers(this, entry);
|
||||
}
|
||||
if (entry->init_hash.ptr)
|
||||
{
|
||||
remove_init_hash(this, entry->init_hash);
|
||||
}
|
||||
remove_entry_at((private_enumerator_t*)enumerator);
|
||||
entry_destroy(entry);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy all entries
|
||||
*/
|
||||
@ -2364,21 +2387,7 @@ static void destroy_all_entries(private_ike_sa_manager_t *this)
|
||||
while (enumerator->enumerate(enumerator, &entry, &segment))
|
||||
{
|
||||
charon->bus->set_sa(charon->bus, entry->ike_sa);
|
||||
if (entry->half_open)
|
||||
{
|
||||
remove_half_open(this, entry->other,
|
||||
entry->ike_sa_id->is_initiator(entry->ike_sa_id));
|
||||
}
|
||||
if (entry->my_id && entry->other_id)
|
||||
{
|
||||
remove_connected_peers(this, entry);
|
||||
}
|
||||
if (entry->init_hash.ptr)
|
||||
{
|
||||
remove_init_hash(this, entry->init_hash);
|
||||
}
|
||||
remove_entry_at((private_enumerator_t*)enumerator);
|
||||
entry_destroy(entry);
|
||||
remove_and_destroy_entry(this, enumerator, entry);
|
||||
}
|
||||
enumerator->destroy(enumerator);
|
||||
charon->bus->set_sa(charon->bus, NULL);
|
||||
@ -2391,53 +2400,51 @@ METHOD(ike_sa_manager_t, flush, void,
|
||||
entry_t *entry;
|
||||
u_int segment;
|
||||
|
||||
lock_all_segments(this);
|
||||
DBG2(DBG_MGR, "going to destroy IKE_SA manager and all managed IKE_SA's");
|
||||
/* Step 1: drive out all waiting threads */
|
||||
DBG2(DBG_MGR, "set driveout flags for all stored IKE_SA's");
|
||||
enumerator = create_table_enumerator(this);
|
||||
while (enumerator->enumerate(enumerator, &entry, &segment))
|
||||
{
|
||||
/* do not accept new threads, drive out waiting threads */
|
||||
entry->driveout_new_threads = TRUE;
|
||||
entry->driveout_waiting_threads = TRUE;
|
||||
}
|
||||
enumerator->destroy(enumerator);
|
||||
DBG2(DBG_MGR, "wait for all threads to leave IKE_SA's");
|
||||
/* Step 2: wait until all are gone */
|
||||
enumerator = create_table_enumerator(this);
|
||||
while (enumerator->enumerate(enumerator, &entry, &segment))
|
||||
{
|
||||
while (entry->waiting_threads || entry->checked_out)
|
||||
{
|
||||
/* wake up all */
|
||||
entry->condvar->broadcast(entry->condvar);
|
||||
/* go sleeping until they are gone */
|
||||
entry->condvar->wait(entry->condvar, this->segments[segment].mutex);
|
||||
}
|
||||
}
|
||||
enumerator->destroy(enumerator);
|
||||
DBG2(DBG_MGR, "delete all IKE_SA's");
|
||||
/* Step 3: initiate deletion of all IKE_SAs */
|
||||
enumerator = create_table_enumerator(this);
|
||||
while (enumerator->enumerate(enumerator, &entry, &segment))
|
||||
{
|
||||
charon->bus->set_sa(charon->bus, entry->ike_sa);
|
||||
entry->ike_sa->delete(entry->ike_sa, TRUE);
|
||||
}
|
||||
enumerator->destroy(enumerator);
|
||||
|
||||
DBG2(DBG_MGR, "destroy all entries");
|
||||
/* Step 4: destroy all entries */
|
||||
destroy_all_entries(this);
|
||||
unlock_all_segments(this);
|
||||
|
||||
/* prevent threads from creating new SAs */
|
||||
this->spi_lock->write_lock(this->spi_lock);
|
||||
DESTROY_IF(this->rng);
|
||||
this->rng = NULL;
|
||||
this->spi_cb.cb = NULL;
|
||||
this->spi_cb.data = NULL;
|
||||
this->spi_lock->unlock(this->spi_lock);
|
||||
|
||||
lock_all_segments(this);
|
||||
DBG2(DBG_MGR, "going to destroy IKE_SA manager and all managed IKE_SAs");
|
||||
enumerator = create_table_enumerator(this);
|
||||
while (enumerator->enumerate(enumerator, &entry, &segment))
|
||||
{
|
||||
/* mark all entries so threads can't check these IKE_SAs out */
|
||||
entry->driveout_threads = TRUE;
|
||||
}
|
||||
enumerator->destroy(enumerator);
|
||||
DBG2(DBG_MGR, "wait for threads to leave IKE_SAs and delete and destroy "
|
||||
"them");
|
||||
/* we are intermittently unlocking the segments below, which allows threads
|
||||
* to create new entries. we want these to get marked directly so other
|
||||
* threads can't get them again. we re-enumerate the table until all
|
||||
* entries are gone */
|
||||
this->new_entries_driveout_threads = TRUE;
|
||||
while (ref_cur(&this->total_sa_count))
|
||||
{
|
||||
enumerator = create_table_enumerator(this);
|
||||
while (enumerator->enumerate(enumerator, &entry, &segment))
|
||||
{
|
||||
while (entry->waiting_threads || entry->checked_out)
|
||||
{
|
||||
/* wake up all */
|
||||
entry->condvar->broadcast(entry->condvar);
|
||||
/* go sleeping until they are gone */
|
||||
entry->condvar->wait(entry->condvar,
|
||||
this->segments[segment].mutex);
|
||||
}
|
||||
charon->bus->set_sa(charon->bus, entry->ike_sa);
|
||||
entry->ike_sa->delete(entry->ike_sa, TRUE);
|
||||
remove_and_destroy_entry(this, enumerator, entry);
|
||||
charon->bus->set_sa(charon->bus, NULL);
|
||||
}
|
||||
enumerator->destroy(enumerator);
|
||||
}
|
||||
unlock_all_segments(this);
|
||||
}
|
||||
|
||||
METHOD(ike_sa_manager_t, destroy, void,
|
||||
|
Loading…
x
Reference in New Issue
Block a user