diff options
author | Tobias Brunner <tobias@strongswan.org> | 2011-07-06 12:56:34 +0200 |
---|---|---|
committer | Tobias Brunner <tobias@strongswan.org> | 2011-07-06 12:56:34 +0200 |
commit | 17927ca6159a50240bac33817c88ad727fd453cb (patch) | |
tree | be207f8b4906e335aaab5315205a1ebd3db9e399 /src/libhydra/plugins/kernel_pfkey/kernel_pfkey_ipsec.c | |
parent | 4e9e4372f51aaf9cc9b0414bd4e4578fba66d463 (diff) | |
download | strongswan-17927ca6159a50240bac33817c88ad727fd453cb.tar.bz2 strongswan-17927ca6159a50240bac33817c88ad727fd453cb.tar.xz |
Record usage history of policies in PF_KEY kernel interface.
The implementation is nearly the same as in the Netlink kernel interface.
Diffstat (limited to 'src/libhydra/plugins/kernel_pfkey/kernel_pfkey_ipsec.c')
-rw-r--r-- | src/libhydra/plugins/kernel_pfkey/kernel_pfkey_ipsec.c | 626 |
1 files changed, 457 insertions, 169 deletions
diff --git a/src/libhydra/plugins/kernel_pfkey/kernel_pfkey_ipsec.c b/src/libhydra/plugins/kernel_pfkey/kernel_pfkey_ipsec.c index 7dd0b371b..78726c2a6 100644 --- a/src/libhydra/plugins/kernel_pfkey/kernel_pfkey_ipsec.c +++ b/src/libhydra/plugins/kernel_pfkey/kernel_pfkey_ipsec.c @@ -58,6 +58,7 @@ #include <debug.h> #include <utils/host.h> #include <utils/linked_list.h> +#include <utils/hashtable.h> #include <threading/thread.h> #include <threading/mutex.h> #include <processing/jobs/callback_job.h> @@ -163,6 +164,11 @@ struct private_kernel_pfkey_ipsec_t linked_list_t *policies; /** + * Hash table of IPsec SAs using policies (ipsec_sa_t) + */ + hashtable_t *sas; + + /** * whether to install routes along policies */ bool install_routes; @@ -227,48 +233,213 @@ static void route_entry_destroy(route_entry_t *this) free(this); } +/** + * compare two route_entry_t objects + */ +static bool route_entry_equals(route_entry_t *a, route_entry_t *b) +{ + return a->if_name && b->if_name && streq(a->if_name, b->if_name) && + a->src_ip->equals(a->src_ip, b->src_ip) && + a->gateway->equals(a->gateway, b->gateway) && + chunk_equals(a->dst_net, b->dst_net) && a->prefixlen == b->prefixlen; +} + +typedef struct ipsec_sa_t ipsec_sa_t; + +/** + * IPsec SA assigned to a policy. + */ +struct ipsec_sa_t { + /** Source address of this SA */ + host_t *src; + + /** Destination address of this SA */ + host_t *dst; + + /** Description of this SA */ + ipsec_sa_cfg_t cfg; + + /** Reference count for this SA */ + refcount_t refcount; +}; + +/** + * Hash function for ipsec_sa_t objects + */ +static u_int ipsec_sa_hash(ipsec_sa_t *sa) +{ + return chunk_hash_inc(sa->src->get_address(sa->src), + chunk_hash_inc(sa->dst->get_address(sa->dst), + chunk_hash(chunk_from_thing(sa->cfg)))); +} + +/** + * Equality function for ipsec_sa_t objects + */ +static bool ipsec_sa_equals(ipsec_sa_t *sa, ipsec_sa_t *other_sa) +{ + return sa->src->ip_equals(sa->src, other_sa->src) && + sa->dst->ip_equals(sa->dst, other_sa->dst) && + memeq(&sa->cfg, &other_sa->cfg, sizeof(ipsec_sa_cfg_t)); +} + +/** + * Allocate or reference an IPsec SA object + */ +static ipsec_sa_t *ipsec_sa_create(private_kernel_pfkey_ipsec_t *this, + host_t *src, host_t *dst, + ipsec_sa_cfg_t *cfg) +{ + ipsec_sa_t *sa, *found; + INIT(sa, + .src = src, + .dst = dst, + .cfg = *cfg, + ); + found = this->sas->get(this->sas, sa); + if (!found) + { + sa->src = src->clone(src); + sa->dst = dst->clone(dst); + this->sas->put(this->sas, sa, sa); + } + else + { + free(sa); + sa = found; + } + ref_get(&sa->refcount); + return sa; +} + +/** + * Release and destroy an IPsec SA object + */ +static void ipsec_sa_destroy(private_kernel_pfkey_ipsec_t *this, + ipsec_sa_t *sa) +{ + if (ref_put(&sa->refcount)) + { + this->sas->remove(this->sas, sa); + DESTROY_IF(sa->src); + DESTROY_IF(sa->dst); + free(sa); + } +} + +typedef struct policy_sa_t policy_sa_t; +typedef struct policy_sa_fwd_t policy_sa_fwd_t; + +/** + * Mapping between a policy and an IPsec SA. + */ +struct policy_sa_t { + /** Priority assigned to the policy when installed with this SA */ + u_int32_t priority; + + /** Type of the policy */ + policy_type_t type; + + /** Assigned SA */ + ipsec_sa_t *sa; +}; + +/** + * For forward policies we also cache the traffic selectors in order to install + * the route. + */ +struct policy_sa_fwd_t { + /** Generic interface */ + policy_sa_t generic; + + /** Source traffic selector of this policy */ + traffic_selector_t *src_ts; + + /** Destination traffic selector of this policy */ + traffic_selector_t *dst_ts; +}; + +/** + * Create a policy_sa(_fwd)_t object + */ +static policy_sa_t *policy_sa_create(private_kernel_pfkey_ipsec_t *this, + policy_dir_t dir, policy_type_t type, host_t *src, host_t *dst, + traffic_selector_t *src_ts, traffic_selector_t *dst_ts, ipsec_sa_cfg_t *cfg) +{ + policy_sa_t *policy; + + if (dir == POLICY_FWD) + { + policy_sa_fwd_t *fwd; + INIT(fwd, + .src_ts = src_ts->clone(src_ts), + .dst_ts = dst_ts->clone(dst_ts), + ); + policy = &fwd->generic; + } + else + { + INIT(policy); + } + policy->type = type; + policy->sa = ipsec_sa_create(this, src, dst, cfg); + return policy; +} + +/** + * Destroy a policy_sa(_fwd)_t object + */ +static void policy_sa_destroy(policy_sa_t *policy, policy_dir_t *dir, + private_kernel_pfkey_ipsec_t *this) +{ + if (*dir == POLICY_FWD) + { + policy_sa_fwd_t *fwd = (policy_sa_fwd_t*)policy; + fwd->src_ts->destroy(fwd->src_ts); + fwd->dst_ts->destroy(fwd->dst_ts); + } + ipsec_sa_destroy(this, policy->sa); + free(policy); +} + typedef struct policy_entry_t policy_entry_t; /** * installed kernel policy. */ struct policy_entry_t { - - /** reqid of this policy */ - u_int32_t reqid; - - /** index assigned by the kernel */ + /** Index assigned by the kernel */ u_int32_t index; - /** direction of this policy: in, out, forward */ + /** Direction of this policy: in, out, forward */ u_int8_t direction; - /** parameters of installed policy */ + /** Parameters of installed policy */ struct { - /** subnet and port */ + /** Subnet and port */ host_t *net; - /** subnet mask */ + /** Subnet mask */ u_int8_t mask; - /** protocol */ + /** Protocol */ u_int8_t proto; } src, dst; - /** associated route installed for this policy */ + /** Associated route installed for this policy */ route_entry_t *route; - /** by how many CHILD_SAs this policy is used */ - u_int refcount; + /** List of SAs this policy is used by, ordered by priority */ + linked_list_t *used_by; }; /** - * create a policy_entry_t object + * Create a policy_entry_t object */ static policy_entry_t *create_policy_entry(traffic_selector_t *src_ts, - traffic_selector_t *dst_ts, policy_dir_t dir, u_int32_t reqid) + traffic_selector_t *dst_ts, + policy_dir_t dir) { policy_entry_t *policy; INIT(policy, - .reqid = reqid, .direction = dir, ); @@ -285,17 +456,25 @@ static policy_entry_t *create_policy_entry(traffic_selector_t *src_ts, } /** - * destroy a policy_entry_t object + * Destroy a policy_entry_t object */ -static void policy_entry_destroy(policy_entry_t *this) +static void policy_entry_destroy(policy_entry_t *policy, + private_kernel_pfkey_ipsec_t *this) { - DESTROY_IF(this->src.net); - DESTROY_IF(this->dst.net); - if (this->route) + if (policy->route) { - route_entry_destroy(this->route); + route_entry_destroy(policy->route); } - free(this); + if (policy->used_by) + { + policy->used_by->invoke_function(policy->used_by, + (linked_list_invoke_t)policy_sa_destroy, + &policy->direction, this); + policy->used_by->destroy(policy->used_by); + } + DESTROY_IF(policy->src.net); + DESTROY_IF(policy->dst.net); + free(policy); } /** @@ -472,6 +651,23 @@ static u_int8_t dir2kernel(policy_dir_t dir) } } +/** + * convert the policy type to the one defined in ipsec.h + */ +static inline u_int16_t type2kernel(policy_type_t type) +{ + switch (type) + { + case POLICY_IPSEC: + return IPSEC_POLICY_IPSEC; + case POLICY_PASS: + return IPSEC_POLICY_NONE; + case POLICY_DROP: + return IPSEC_POLICY_DISCARD; + } + return type; +} + #ifdef SADB_X_MIGRATE /** * convert the policy direction in ipsec.h to the general one. @@ -892,6 +1088,7 @@ static void process_acquire(private_kernel_pfkey_ipsec_t *this, u_int32_t index, reqid = 0; traffic_selector_t *src_ts, *dst_ts; policy_entry_t *policy; + policy_sa_t *sa; switch (msg->sadb_msg_satype) { @@ -915,9 +1112,10 @@ static void process_acquire(private_kernel_pfkey_ipsec_t *this, this->mutex->lock(this->mutex); if (this->policies->find_first(this->policies, (linked_list_match_t)policy_entry_match_byindex, - (void**)&policy, &index) == SUCCESS) + (void**)&policy, &index) == SUCCESS && + policy->used_by->get_first(policy->used_by, (void**)&sa) == SUCCESS) { - reqid = policy->reqid; + reqid = sa->sa->cfg.reqid; } else { @@ -1614,55 +1812,27 @@ METHOD(kernel_ipsec_t, del_sa, status_t, return SUCCESS; } -METHOD(kernel_ipsec_t, add_policy, status_t, - private_kernel_pfkey_ipsec_t *this, host_t *src, host_t *dst, - traffic_selector_t *src_ts, traffic_selector_t *dst_ts, - policy_dir_t direction, policy_type_t type, ipsec_sa_cfg_t *sa, - mark_t mark, bool routed) +/** + * Add or update a policy in the kernel. + * + * Note: The mutex has to be locked when entering this function. + */ +static status_t add_policy_internal(private_kernel_pfkey_ipsec_t *this, + policy_entry_t *policy, policy_sa_t *mapping, bool update) { unsigned char request[PFKEY_BUFFER_SIZE]; struct sadb_msg *msg, *out; struct sadb_x_policy *pol; struct sadb_x_ipsecrequest *req; - policy_entry_t *policy, *found = NULL; + ipsec_sa_t *ipsec = mapping->sa; pfkey_msg_t response; size_t len; - if (dir2kernel(direction) == IPSEC_DIR_INVALID) - { /* FWD policies are not supported on all platforms */ - return SUCCESS; - } - - /* create a policy */ - policy = create_policy_entry(src_ts, dst_ts, direction, sa->reqid); - - /* find a matching policy */ - this->mutex->lock(this->mutex); - if (this->policies->find_first(this->policies, - (linked_list_match_t)policy_entry_equals, - (void**)&found, policy) == SUCCESS) - { /* use existing policy, but cache the most recent reqid */ - found->refcount++; - found->reqid = policy->reqid; - DBG2(DBG_KNL, "policy %R === %R %N already exists, increasing " - "refcount", src_ts, dst_ts, policy_dir_names, direction); - policy_entry_destroy(policy); - policy = found; - } - else - { /* use the new one, if we have no such policy */ - this->policies->insert_last(this->policies, policy); - policy->refcount = 1; - } - memset(&request, 0, sizeof(request)); - DBG2(DBG_KNL, "adding policy %R === %R %N", src_ts, dst_ts, - policy_dir_names, direction); - msg = (struct sadb_msg*)request; msg->sadb_msg_version = PF_KEY_V2; - msg->sadb_msg_type = found ? SADB_X_SPDUPDATE : SADB_X_SPDADD; + msg->sadb_msg_type = update ? SADB_X_SPDUPDATE : SADB_X_SPDADD; msg->sadb_msg_satype = 0; msg->sadb_msg_len = PFKEY_LEN(sizeof(struct sadb_msg)); @@ -1670,45 +1840,27 @@ METHOD(kernel_ipsec_t, add_policy, status_t, pol->sadb_x_policy_exttype = SADB_X_EXT_POLICY; pol->sadb_x_policy_len = PFKEY_LEN(sizeof(struct sadb_x_policy)); pol->sadb_x_policy_id = 0; - pol->sadb_x_policy_dir = dir2kernel(direction); - switch (type) - { - case POLICY_IPSEC: - pol->sadb_x_policy_type = IPSEC_POLICY_IPSEC; - break; - case POLICY_PASS: - pol->sadb_x_policy_type = IPSEC_POLICY_NONE; - break; - case POLICY_DROP: - pol->sadb_x_policy_type = IPSEC_POLICY_DISCARD; - break; - } + pol->sadb_x_policy_dir = dir2kernel(policy->direction); + pol->sadb_x_policy_type = type2kernel(mapping->type); #ifdef HAVE_STRUCT_SADB_X_POLICY_SADB_X_POLICY_PRIORITY - /* calculate priority based on selector size, small size = high prio */ - pol->sadb_x_policy_priority = routed ? PRIO_LOW : PRIO_HIGH; - pol->sadb_x_policy_priority -= policy->src.mask; - pol->sadb_x_policy_priority -= policy->dst.mask; - pol->sadb_x_policy_priority <<= 2; /* make some room for the flags */ - pol->sadb_x_policy_priority += policy->src.net->get_port(policy->src.net) || - policy->dst.net->get_port(policy->dst.net) ? - 0 : 2; - pol->sadb_x_policy_priority += policy->src.proto != IPSEC_PROTO_ANY ? 0 : 1; + pol->sadb_x_policy_priority = mapping->priority; #endif /* one or more sadb_x_ipsecrequest extensions are added to the * sadb_x_policy extension */ req = (struct sadb_x_ipsecrequest*)(pol + 1); - req->sadb_x_ipsecrequest_proto = sa->esp.use ? IPPROTO_ESP : IPPROTO_AH; + req->sadb_x_ipsecrequest_proto = ipsec->cfg.esp.use ? IPPROTO_ESP + : IPPROTO_AH; /* !!! the length here MUST be in octets instead of 64 bit words */ req->sadb_x_ipsecrequest_len = sizeof(struct sadb_x_ipsecrequest); - req->sadb_x_ipsecrequest_mode = mode2kernel(sa->mode); - req->sadb_x_ipsecrequest_reqid = sa->reqid; + req->sadb_x_ipsecrequest_mode = mode2kernel(ipsec->cfg.mode); + req->sadb_x_ipsecrequest_reqid = ipsec->cfg.reqid; req->sadb_x_ipsecrequest_level = IPSEC_LEVEL_UNIQUE; - if (sa->mode == MODE_TUNNEL) + if (ipsec->cfg.mode == MODE_TUNNEL) { - len = hostcpy(req + 1, src); + len = hostcpy(req + 1, ipsec->src); req->sadb_x_ipsecrequest_len += len; - len = hostcpy((char*)(req + 1) + len, dst); + len = hostcpy((char*)(req + 1) + len, ipsec->dst); req->sadb_x_ipsecrequest_len += len; } @@ -1736,23 +1888,20 @@ METHOD(kernel_ipsec_t, add_policy, status_t, if (pfkey_send(this, msg, &out, &len) != SUCCESS) { - DBG1(DBG_KNL, "unable to add policy %R === %R %N", src_ts, dst_ts, - policy_dir_names, direction); return FAILED; } else if (out->sadb_msg_errno) { - DBG1(DBG_KNL, "unable to add policy %R === %R %N: %s (%d)", - src_ts, dst_ts, policy_dir_names, direction, - strerror(out->sadb_msg_errno), out->sadb_msg_errno); + DBG1(DBG_KNL, "unable to %s policy: %s (%d)", + update ? "update" : "add", strerror(out->sadb_msg_errno), + out->sadb_msg_errno); free(out); return FAILED; } else if (parse_pfkey_message(out, &response) != SUCCESS) { - DBG1(DBG_KNL, "unable to add policy %R === %R %N: parsing response " - "from kernel failed", src_ts, dst_ts, policy_dir_names, - direction); + DBG1(DBG_KNL, "unable to %s policy: parsing response from kernel " + "failed", update ? "update" : "add"); free(out); return FAILED; } @@ -1762,9 +1911,8 @@ METHOD(kernel_ipsec_t, add_policy, status_t, if (this->policies->find_last(this->policies, NULL, (void**)&policy) != SUCCESS) { - DBG2(DBG_KNL, "unable to update index, the policy %R === %R %N is " - "already gone, ignoring", src_ts, dst_ts, - policy_dir_names, direction); + DBG2(DBG_KNL, "unable to update index, the policy is already gone, " + "ignoring"); this->mutex->unlock(this->mutex); free(out); return SUCCESS; @@ -1773,54 +1921,78 @@ METHOD(kernel_ipsec_t, add_policy, status_t, free(out); /* install a route, if: - * - we are NOT updating a policy * - this is a forward policy (to just get one for each child) * - we are in tunnel mode - * - we are not using IPv6 (does not work correctly yet!) * - routing is not disabled via strongswan.conf */ - if (policy->route == NULL && direction == POLICY_FWD && - sa->mode != MODE_TRANSPORT && src->get_family(src) != AF_INET6 && - this->install_routes) + if (policy->direction == POLICY_FWD && + ipsec->cfg.mode != MODE_TRANSPORT && this->install_routes) { route_entry_t *route = malloc_thing(route_entry_t); + policy_sa_fwd_t *fwd = (policy_sa_fwd_t*)mapping; if (hydra->kernel_interface->get_address_by_ts(hydra->kernel_interface, - dst_ts, &route->src_ip) == SUCCESS) + fwd->dst_ts, &route->src_ip) == SUCCESS) { /* get the nexthop to src (src as we are in POLICY_FWD).*/ route->gateway = hydra->kernel_interface->get_nexthop( - hydra->kernel_interface, src); + hydra->kernel_interface, ipsec->src); + /* install route via outgoing interface */ route->if_name = hydra->kernel_interface->get_interface( - hydra->kernel_interface, dst); + hydra->kernel_interface, ipsec->dst); route->dst_net = chunk_clone(policy->src.net->get_address( policy->src.net)); route->prefixlen = policy->src.mask; - if (route->if_name) + if (!route->if_name) + { + this->mutex->unlock(this->mutex); + route_entry_destroy(route); + return SUCCESS; + } + + if (policy->route) { - switch (hydra->kernel_interface->add_route( - hydra->kernel_interface, route->dst_net, - route->prefixlen, route->gateway, - route->src_ip, route->if_name)) + route_entry_t *old = policy->route; + if (route_entry_equals(old, route)) + { /* keep previously installed route */ + this->mutex->unlock(this->mutex); + route_entry_destroy(route); + return SUCCESS; + } + /* uninstall previously installed route */ + if (hydra->kernel_interface->del_route(hydra->kernel_interface, + old->dst_net, old->prefixlen, old->gateway, + old->src_ip, old->if_name) != SUCCESS) { - default: - DBG1(DBG_KNL, "unable to install source route for %H", - route->src_ip); - /* FALL */ - case ALREADY_DONE: - /* route exists, do not uninstall */ - route_entry_destroy(route); - break; - case SUCCESS: - /* cache the installed route */ - policy->route = route; - break; + DBG1(DBG_KNL, "error uninstalling route installed with " + "policy %R === %R %N", fwd->src_ts, + fwd->dst_ts, policy_dir_names, + policy->direction); } + route_entry_destroy(old); + policy->route = NULL; } - else + + DBG2(DBG_KNL, "installing route: %R via %H src %H dev %s", + fwd->src_ts, route->gateway, route->src_ip, route->if_name); + switch (hydra->kernel_interface->add_route( + hydra->kernel_interface, route->dst_net, + route->prefixlen, route->gateway, + route->src_ip, route->if_name)) { - route_entry_destroy(route); + default: + DBG1(DBG_KNL, "unable to install source route for %H", + route->src_ip); + /* FALL */ + case ALREADY_DONE: + /* route exists, do not uninstall */ + route_entry_destroy(route); + break; + case SUCCESS: + /* cache the installed route */ + policy->route = route; + break; } } else @@ -1832,6 +2004,91 @@ METHOD(kernel_ipsec_t, add_policy, status_t, return SUCCESS; } +METHOD(kernel_ipsec_t, add_policy, status_t, + private_kernel_pfkey_ipsec_t *this, host_t *src, host_t *dst, + traffic_selector_t *src_ts, traffic_selector_t *dst_ts, + policy_dir_t direction, policy_type_t type, ipsec_sa_cfg_t *sa, + mark_t mark, bool routed) +{ + policy_entry_t *policy, *found = NULL; + policy_sa_t *assigned_sa, *current_sa; + enumerator_t *enumerator; + bool update = TRUE; + + if (dir2kernel(direction) == IPSEC_DIR_INVALID) + { /* FWD policies are not supported on all platforms */ + return SUCCESS; + } + + /* create a policy */ + policy = create_policy_entry(src_ts, dst_ts, direction); + + /* find a matching policy */ + this->mutex->lock(this->mutex); + if (this->policies->find_first(this->policies, + (linked_list_match_t)policy_entry_equals, + (void**)&found, policy) == SUCCESS) + { /* use existing policy */ + DBG2(DBG_KNL, "policy %R === %R %N already exists, increasing " + "refcount", src_ts, dst_ts, policy_dir_names, direction); + policy_entry_destroy(policy, this); + policy = found; + } + else + { /* use the new one, if we have no such policy */ + this->policies->insert_last(this->policies, policy); + policy->used_by = linked_list_create(); + } + + /* cache the assigned IPsec SA */ + assigned_sa = policy_sa_create(this, direction, type, src, dst, src_ts, + dst_ts, sa); + + + /* calculate priority based on selector size, small size = high prio */ + assigned_sa->priority = routed ? PRIO_LOW : PRIO_HIGH; + assigned_sa->priority -= policy->src.mask; + assigned_sa->priority -= policy->dst.mask; + assigned_sa->priority <<= 2; /* make some room for the two flags */ + assigned_sa->priority += policy->src.net->get_port(policy->src.net) || + policy->dst.net->get_port(policy->dst.net) ? + 0 : 2; + assigned_sa->priority += policy->src.proto != IPSEC_PROTO_ANY ? 0 : 1; + + /* insert the SA according to its priority */ + enumerator = policy->used_by->create_enumerator(policy->used_by); + while (enumerator->enumerate(enumerator, (void**)¤t_sa)) + { + if (current_sa->priority >= assigned_sa->priority) + { + break; + } + update = FALSE; + } + policy->used_by->insert_before(policy->used_by, enumerator, assigned_sa); + enumerator->destroy(enumerator); + + 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); + return SUCCESS; + } + + DBG2(DBG_KNL, "%s policy %R === %R %N", + found ? "updating" : "adding", src_ts, dst_ts, + policy_dir_names, direction); + + if (add_policy_internal(this, policy, assigned_sa, found) != SUCCESS) + { + DBG1(DBG_KNL, "unable to %s policy %R === %R %N", + found ? "update" : "add", src_ts, dst_ts, + policy_dir_names, direction); + return FAILED; + } + return SUCCESS; +} + METHOD(kernel_ipsec_t, query_policy, status_t, private_kernel_pfkey_ipsec_t *this, traffic_selector_t *src_ts, traffic_selector_t *dst_ts, policy_dir_t direction, mark_t mark, @@ -1853,7 +2110,7 @@ METHOD(kernel_ipsec_t, query_policy, status_t, policy_dir_names, direction); /* create a policy */ - policy = create_policy_entry(src_ts, dst_ts, direction, 0); + policy = create_policy_entry(src_ts, dst_ts, direction); /* find a matching policy */ this->mutex->lock(this->mutex); @@ -1863,11 +2120,11 @@ METHOD(kernel_ipsec_t, query_policy, status_t, { DBG1(DBG_KNL, "querying policy %R === %R %N failed, not found", src_ts, dst_ts, policy_dir_names, direction); - policy_entry_destroy(policy); + policy_entry_destroy(policy, this); this->mutex->unlock(this->mutex); return NOT_FOUND; } - policy_entry_destroy(policy); + policy_entry_destroy(policy, this); policy = found; memset(&request, 0, sizeof(request)); @@ -1946,7 +2203,9 @@ METHOD(kernel_ipsec_t, del_policy, status_t, struct sadb_msg *msg, *out; struct sadb_x_policy *pol; policy_entry_t *policy, *found = NULL; - route_entry_t *route; + policy_sa_t *mapping; + enumerator_t *enumerator; + bool is_installed = TRUE; size_t len; if (dir2kernel(direction) == IPSEC_DIR_INVALID) @@ -1958,35 +2217,58 @@ METHOD(kernel_ipsec_t, del_policy, status_t, policy_dir_names, direction); /* create a policy */ - policy = create_policy_entry(src_ts, dst_ts, direction, 0); + policy = create_policy_entry(src_ts, dst_ts, direction); /* find a matching policy */ this->mutex->lock(this->mutex); if (this->policies->find_first(this->policies, (linked_list_match_t)policy_entry_equals, - (void**)&found, policy) == SUCCESS) - { - if (--found->refcount > 0) - { /* is used by more SAs, keep in kernel */ - DBG2(DBG_KNL, "policy still used by another CHILD_SA, not removed"); - policy_entry_destroy(policy); - this->mutex->unlock(this->mutex); - return SUCCESS; - } - /* remove if last reference */ - this->policies->remove(this->policies, found, NULL); - policy_entry_destroy(policy); - policy = found; - } - else + (void**)&found, policy) != SUCCESS) { DBG1(DBG_KNL, "deleting policy %R === %R %N failed, not found", src_ts, dst_ts, policy_dir_names, direction); - policy_entry_destroy(policy); + policy_entry_destroy(policy, this); this->mutex->unlock(this->mutex); return NOT_FOUND; } - this->mutex->unlock(this->mutex); + policy_entry_destroy(policy, this); + policy = found; + + /* remove mapping to SA by reqid */ + enumerator = policy->used_by->create_enumerator(policy->used_by); + while (enumerator->enumerate(enumerator, (void**)&mapping)) + { + if (reqid == mapping->sa->cfg.reqid) + { + policy->used_by->remove_at(policy->used_by, enumerator); + break; + } + is_installed = FALSE; + } + enumerator->destroy(enumerator); + + if (policy->used_by->get_count(policy->used_by) > 0) + { /* policy is used by more SAs, keep in kernel */ + DBG2(DBG_KNL, "policy still used by another CHILD_SA, not removed"); + policy_sa_destroy(mapping, &direction, this); + + if (!is_installed) + { /* no need to update as the policy was not installed for this SA */ + this->mutex->unlock(this->mutex); + return SUCCESS; + } + + DBG2(DBG_KNL, "updating policy %R === %R %N", src_ts, dst_ts, + policy_dir_names, direction); + policy->used_by->get_first(policy->used_by, (void**)&mapping); + if (add_policy_internal(this, policy, mapping, TRUE) != SUCCESS) + { + DBG1(DBG_KNL, "unable to update policy %R === %R %N", + src_ts, dst_ts, policy_dir_names, direction); + return FAILED; + } + return SUCCESS; + } memset(&request, 0, sizeof(request)); @@ -2000,7 +2282,7 @@ METHOD(kernel_ipsec_t, del_policy, status_t, pol->sadb_x_policy_exttype = SADB_X_EXT_POLICY; pol->sadb_x_policy_len = PFKEY_LEN(sizeof(struct sadb_x_policy)); pol->sadb_x_policy_dir = dir2kernel(direction); - pol->sadb_x_policy_type = IPSEC_POLICY_IPSEC; + pol->sadb_x_policy_type = type2kernel(mapping->type); PFKEY_EXT_ADD(msg, pol); add_addr_ext(msg, policy->src.net, SADB_EXT_ADDRESS_SRC, policy->src.proto, @@ -2008,9 +2290,23 @@ METHOD(kernel_ipsec_t, del_policy, status_t, add_addr_ext(msg, policy->dst.net, SADB_EXT_ADDRESS_DST, policy->dst.proto, policy->dst.mask); - route = policy->route; - policy->route = NULL; - policy_entry_destroy(policy); + if (policy->route) + { + route_entry_t *route = policy->route; + if (hydra->kernel_interface->del_route(hydra->kernel_interface, + route->dst_net, route->prefixlen, route->gateway, + route->src_ip, route->if_name) != SUCCESS) + { + DBG1(DBG_KNL, "error uninstalling route installed with " + "policy %R === %R %N", src_ts, dst_ts, + policy_dir_names, direction); + } + } + + this->policies->remove(this->policies, found, NULL); + policy_sa_destroy(mapping, &direction, this); + policy_entry_destroy(policy, this); + this->mutex->unlock(this->mutex); if (pfkey_send(this, msg, &out, &len) != SUCCESS) { @@ -2027,19 +2323,6 @@ METHOD(kernel_ipsec_t, del_policy, status_t, return FAILED; } free(out); - - if (route) - { - if (hydra->kernel_interface->del_route(hydra->kernel_interface, - route->dst_net, route->prefixlen, route->gateway, - route->src_ip, route->if_name) != SUCCESS) - { - DBG1(DBG_KNL, "error uninstalling route installed with " - "policy %R === %R %N", src_ts, dst_ts, - policy_dir_names, direction); - } - route_entry_destroy(route); - } return SUCCESS; } @@ -2138,8 +2421,11 @@ METHOD(kernel_ipsec_t, destroy, void, { close(this->socket_events); } - this->policies->destroy_function(this->policies, - (void*)policy_entry_destroy); + this->policies->invoke_function(this->policies, + (linked_list_invoke_t)policy_entry_destroy, + this); + this->policies->destroy(this->policies); + this->sas->destroy(this->sas); this->mutex->destroy(this->mutex); this->mutex_pfkey->destroy(this->mutex_pfkey); free(this); @@ -2169,6 +2455,8 @@ kernel_pfkey_ipsec_t *kernel_pfkey_ipsec_create() }, }, .policies = linked_list_create(), + .sas = hashtable_create((hashtable_hash_t)ipsec_sa_hash, + (hashtable_equals_t)ipsec_sa_equals, 32), .mutex = mutex_create(MUTEX_TYPE_DEFAULT), .mutex_pfkey = mutex_create(MUTEX_TYPE_DEFAULT), .install_routes = lib->settings->get_bool(lib->settings, |