diff options
Diffstat (limited to 'src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c')
-rw-r--r-- | src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c | 94 |
1 files changed, 69 insertions, 25 deletions
diff --git a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c index a27e70b9b..e78e13e40 100644 --- a/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c +++ b/src/libcharon/plugins/kernel_netlink/kernel_netlink_ipsec.c @@ -41,6 +41,7 @@ #include <daemon.h> #include <utils/debug.h> #include <threading/mutex.h> +#include <threading/condvar.h> #include <collections/array.h> #include <collections/hashtable.h> #include <collections/linked_list.h> @@ -290,6 +291,11 @@ struct private_kernel_netlink_ipsec_t { mutex_t *mutex; /** + * Condvar to synchronize access to individual policies + */ + condvar_t *condvar; + + /** * Hash table of installed policies (policy_entry_t) */ hashtable_t *policies; @@ -575,6 +581,12 @@ struct policy_entry_t { /** reqid for this policy */ uint32_t reqid; + + /** Number of threads waiting to work on this policy */ + int waiting; + + /** TRUE if a thread is working on this policy */ + bool working; }; /** @@ -2149,6 +2161,20 @@ METHOD(kernel_ipsec_t, flush_sas, status_t, } /** + * Unlock the mutex and signal waiting threads + */ +static void policy_change_done(private_kernel_netlink_ipsec_t *this, + policy_entry_t *policy) +{ + policy->working = FALSE; + if (policy->waiting) + { /* don't need to wake threads waiting for other policies */ + this->condvar->broadcast(this->condvar); + } + this->mutex->unlock(this->mutex); +} + +/** * Add or update a policy in the kernel. * * Note: The mutex has to be locked when entering this function @@ -2219,7 +2245,7 @@ static status_t add_policy_internal(private_kernel_netlink_ipsec_t *this, count * sizeof(*tmpl)); if (!tmpl) { - this->mutex->unlock(this->mutex); + policy_change_done(this, policy); return FAILED; } @@ -2252,7 +2278,7 @@ static status_t add_policy_internal(private_kernel_netlink_ipsec_t *this, if (!add_mark(hdr, sizeof(request), ipsec->mark)) { - this->mutex->unlock(this->mutex); + policy_change_done(this, policy); return FAILED; } this->mutex->unlock(this->mutex); @@ -2264,22 +2290,13 @@ static status_t add_policy_internal(private_kernel_netlink_ipsec_t *this, hdr->nlmsg_type = XFRM_MSG_UPDPOLICY; status = this->socket_xfrm->send_ack(this->socket_xfrm, hdr); } + + this->mutex->lock(this->mutex); if (status != SUCCESS) { + policy_change_done(this, policy); return FAILED; } - - /* find the policy again */ - this->mutex->lock(this->mutex); - policy = this->policies->get(this->policies, &clone); - if (!policy || - policy->used_by->find_first(policy->used_by, - NULL, (void**)&mapping) != SUCCESS) - { /* policy or mapping is already gone, ignore */ - this->mutex->unlock(this->mutex); - return SUCCESS; - } - /* install a route, if: * - this is a inbound policy (to just get one for each child) * - we are in tunnel/BEET mode or install a bypass policy @@ -2328,7 +2345,7 @@ static status_t add_policy_internal(private_kernel_netlink_ipsec_t *this, if (!charon->kernel->get_interface(charon->kernel, iface, &route->if_name)) { - this->mutex->unlock(this->mutex); + policy_change_done(this, policy); route_entry_destroy(route); return SUCCESS; } @@ -2338,7 +2355,7 @@ static status_t add_policy_internal(private_kernel_netlink_ipsec_t *this, route_entry_t *old = policy->route; if (route_entry_equals(old, route)) { - this->mutex->unlock(this->mutex); + policy_change_done(this, policy); route_entry_destroy(route); return SUCCESS; } @@ -2380,7 +2397,7 @@ static status_t add_policy_internal(private_kernel_netlink_ipsec_t *this, free(route); } } - this->mutex->unlock(this->mutex); + policy_change_done(this, policy); return SUCCESS; } @@ -2428,6 +2445,14 @@ METHOD(kernel_ipsec_t, add_policy, status_t, policy_entry_destroy(this, policy); policy = current; found = TRUE; + + policy->waiting++; + while (policy->working) + { + this->condvar->wait(this->condvar, this->mutex); + } + policy->waiting--; + policy->working = TRUE; } else { /* use the new one, if we have no such policy */ @@ -2478,7 +2503,7 @@ METHOD(kernel_ipsec_t, add_policy, status_t, if (!update) { /* we don't update the policy if the priority is lower than that of * the currently installed one */ - this->mutex->unlock(this->mutex); + policy_change_done(this, policy); DBG2(DBG_KNL, "not updating policy %R === %R %N%s [priority %u," "refcount %d]", id->src_ts, id->dst_ts, policy_dir_names, id->dir, markstr, cur_priority, use_count); @@ -2606,6 +2631,7 @@ METHOD(kernel_ipsec_t, del_policy, status_t, }; char markstr[32] = ""; int use_count; + status_t status = SUCCESS; format_mark(markstr, sizeof(markstr), id->mark); @@ -2628,6 +2654,13 @@ METHOD(kernel_ipsec_t, del_policy, status_t, this->mutex->unlock(this->mutex); return NOT_FOUND; } + current->waiting++; + while (current->working) + { + this->condvar->wait(this->condvar, this->mutex); + } + current->working = TRUE; + current->waiting--; /* remove mapping to SA by reqid and priority */ auto_priority = get_priority(current, data->prio,id->interface); @@ -2661,7 +2694,7 @@ METHOD(kernel_ipsec_t, del_policy, status_t, DBG2(DBG_KNL, "policy still used by another CHILD_SA, not removed"); if (!is_installed) { /* no need to update as the policy was not installed for this SA */ - this->mutex->unlock(this->mutex); + policy_change_done(this, current); DBG2(DBG_KNL, "not updating policy %R === %R %N%s [priority %u, " "refcount %d]", id->src_ts, id->dst_ts, policy_dir_names, id->dir, markstr, cur_priority, use_count); @@ -2695,7 +2728,7 @@ METHOD(kernel_ipsec_t, del_policy, status_t, if (!add_mark(hdr, sizeof(request), id->mark)) { - this->mutex->unlock(this->mutex); + policy_change_done(this, current); return FAILED; } @@ -2711,18 +2744,27 @@ METHOD(kernel_ipsec_t, del_policy, status_t, id->dir, markstr); } } - - this->policies->remove(this->policies, current); - policy_entry_destroy(this, current); this->mutex->unlock(this->mutex); if (this->socket_xfrm->send_ack(this->socket_xfrm, hdr) != SUCCESS) { DBG1(DBG_KNL, "unable to delete policy %R === %R %N%s", id->src_ts, id->dst_ts, policy_dir_names, id->dir, markstr); - return FAILED; + status = FAILED; } - return SUCCESS; + + this->mutex->lock(this->mutex); + if (!current->waiting) + { /* only if no other thread still needs the policy */ + this->policies->remove(this->policies, current); + policy_entry_destroy(this, current); + this->mutex->unlock(this->mutex); + } + else + { + policy_change_done(this, current); + } + return status; } METHOD(kernel_ipsec_t, flush_policies, status_t, @@ -2978,6 +3020,7 @@ METHOD(kernel_ipsec_t, destroy, void, enumerator->destroy(enumerator); this->policies->destroy(this->policies); this->sas->destroy(this->sas); + this->condvar->destroy(this->condvar); this->mutex->destroy(this->mutex); free(this); } @@ -3017,6 +3060,7 @@ kernel_netlink_ipsec_t *kernel_netlink_ipsec_create() (hashtable_equals_t)ipsec_sa_equals, 32), .bypass = array_create(sizeof(bypass_t), 0), .mutex = mutex_create(MUTEX_TYPE_DEFAULT), + .condvar = condvar_create(CONDVAR_TYPE_DEFAULT), .get_priority = dlsym(RTLD_DEFAULT, "kernel_netlink_get_priority_custom"), .policy_update = lib->settings->get_bool(lib->settings, |