/* * Copyright (C) 2011-2017 Tobias Brunner * Copyright (C) 2009 Martin Willi * * Copyright (C) secunet Security Networks AG * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation; either version 2 of the License, or (at your * option) any later version. See . * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. */ #include "trap_manager.h" #include #include #include #include #include #define INSTALL_DISABLED ((u_int)~0) typedef struct private_trap_manager_t private_trap_manager_t; typedef struct trap_listener_t trap_listener_t; /** * listener to track acquires */ struct trap_listener_t { /** * Implements listener interface */ listener_t listener; /** * points to trap_manager */ private_trap_manager_t *traps; }; /** * Private data of an trap_manager_t object. */ struct private_trap_manager_t { /** * Public trap_manager_t interface. */ trap_manager_t public; /** * Installed traps, as entry_t */ linked_list_t *traps; /** * read write lock for traps list */ rwlock_t *lock; /** * listener to track acquiring IKE_SAs */ trap_listener_t listener; /** * list of acquires we currently handle */ linked_list_t *acquires; /** * mutex for list of acquires */ mutex_t *mutex; /** * number of threads currently installing trap policies, or INSTALL_DISABLED */ u_int installing; /** * condvar to signal trap policy installation */ rwlock_condvar_t *condvar; /** * Whether to ignore traffic selectors from acquires */ bool ignore_acquire_ts; /** * Current acquire sequence number if not generated by the kernel */ refcount_t acquire_seq; }; /** * A installed trap entry */ typedef struct { /** name of the trapped CHILD_SA */ char *name; /** ref to peer_cfg to initiate */ peer_cfg_t *peer_cfg; /** ref to instantiated CHILD_SA (i.e the trap policy) */ child_sa_t *child_sa; /** TRUE in case of wildcard Transport Mode SA */ bool wildcard; /** TRUE for CHILD_SAs that are externally managed */ bool external; } entry_t; /** * A handled acquire */ typedef struct { /** pending IKE_SA connecting upon acquire */ ike_sa_t *ike_sa; /** reqid of pending trap policy */ uint32_t reqid; /** destination address (wildcard case) */ host_t *dst; /** data from the kernel */ kernel_acquire_data_t *data; /** optional sequence number from the kernel if an acquire is retriggered */ uint32_t new_seq; } acquire_t; /** * actually uninstall and destroy an installed entry */ static void destroy_entry(entry_t *this) { if (!this->external) { this->child_sa->destroy(this->child_sa); } this->peer_cfg->destroy(this->peer_cfg); free(this->name); free(this); } /** * destroy a cached acquire entry */ static void destroy_acquire(acquire_t *this) { DESTROY_IF(this->dst); kernel_acquire_data_destroy(this->data); free(this); } CALLBACK(acquire_by_reqid, bool, acquire_t *this, va_list args) { uint32_t reqid, cpu; sec_label_t *label; VA_ARGS_VGET(args, reqid, cpu, label); return this->reqid == reqid && this->data->cpu == cpu && sec_labels_equal(this->data->label, label); } CALLBACK(acquire_by_dst, bool, acquire_t *this, va_list args) { host_t *dst; VA_ARGS_VGET(args, dst); return this->dst && this->dst->ip_equals(this->dst, dst); } /** * Check if any remote TS are dynamic */ static bool dynamic_remote_ts(child_cfg_t *child) { enumerator_t *enumerator; linked_list_t *other_ts; traffic_selector_t *ts; bool found = FALSE; other_ts = child->get_traffic_selectors(child, FALSE, NULL); enumerator = other_ts->create_enumerator(other_ts); while (enumerator->enumerate(enumerator, &ts)) { if (ts->is_dynamic(ts)) { found = TRUE; break; } } enumerator->destroy(enumerator); other_ts->destroy_offset(other_ts, offsetof(traffic_selector_t, destroy)); return found; } /** * Install the given trap */ static status_t install_trap(child_sa_t *child_sa, linked_list_t *local, linked_list_t *remote) { linked_list_t *my_ts, *other_ts, *proposals; proposal_t *proposal; child_cfg_t *child; protocol_id_t proto = PROTO_ESP; child = child_sa->get_config(child_sa); my_ts = child->get_traffic_selectors(child, TRUE, local); other_ts = child->get_traffic_selectors(child, FALSE, remote); /* we don't know the finally negotiated protocol (ESP|AH), we install * the SA with the protocol of the first proposal */ proposals = child->get_proposals(child, TRUE); if (proposals->get_first(proposals, (void**)&proposal) == SUCCESS) { proto = proposal->get_protocol(proposal); } proposals->destroy_offset(proposals, offsetof(proposal_t, destroy)); child_sa->set_protocol(child_sa, proto); child_sa->set_mode(child_sa, child->get_mode(child)); child_sa->set_policies(child_sa, my_ts, other_ts); my_ts->destroy_offset(my_ts, offsetof(traffic_selector_t, destroy)); other_ts->destroy_offset(other_ts, offsetof(traffic_selector_t, destroy)); return child_sa->install_policies(child_sa); } METHOD(trap_manager_t, install, bool, private_trap_manager_t *this, peer_cfg_t *peer, child_cfg_t *child) { entry_t *entry, *found = NULL; ike_cfg_t *ike_cfg; child_sa_t *child_sa; host_t *me, *other; linked_list_t *local, *remote; enumerator_t *enumerator; status_t status; bool result = FALSE, wildcard = FALSE; /* try to resolve addresses */ ike_cfg = peer->get_ike_cfg(peer); other = ike_cfg->resolve_other(ike_cfg, AF_UNSPEC); if (other && other->is_anyaddr(other) && child->get_mode(child) == MODE_TRANSPORT) { /* allow wildcard for Transport Mode SAs */ me = host_create_any(other->get_family(other)); wildcard = TRUE; } else if (other && other->is_anyaddr(other)) { other->destroy(other); DBG1(DBG_CFG, "installing trap failed, remote address unknown"); return FALSE; } else { /* depending on the traffic selectors we don't really need a remote * host yet, but we might fail later if no IP can be resolved */ if (!other && dynamic_remote_ts(child)) { /* with dynamic TS we do need a host, otherwise 0.0.0.0/0 is used, * which is probably not what users expect*/ DBG1(DBG_CFG, "installing trap failed, remote address unknown with " "dynamic traffic selector"); return FALSE; } me = ike_cfg->resolve_me(ike_cfg, other ? other->get_family(other) : AF_UNSPEC); if (!other) { other = host_create_any(me ? me->get_family(me) : AF_INET); } other->set_port(other, ike_cfg->get_other_port(ike_cfg)); if ((!me || me->is_anyaddr(me)) && !other->is_anyaddr(other)) { DESTROY_IF(me); me = charon->kernel->get_source_addr(charon->kernel, other, NULL); } if (!me) { me = host_create_any(other->get_family(other)); } me->set_port(me, ike_cfg->get_my_port(ike_cfg)); } this->lock->write_lock(this->lock); if (this->installing == INSTALL_DISABLED) { /* flush() has been called */ this->lock->unlock(this->lock); other->destroy(other); me->destroy(me); return FALSE; } enumerator = this->traps->create_enumerator(this->traps); while (enumerator->enumerate(enumerator, &entry)) { if (!entry->external && streq(entry->name, child->get_name(child)) && streq(entry->peer_cfg->get_name(entry->peer_cfg), peer->get_name(peer))) { found = entry; if (entry->child_sa) { /* replace it with an updated version if already installed */ this->traps->remove_at(this->traps, enumerator); } break; } } enumerator->destroy(enumerator); if (found) { if (!found->child_sa) { DBG1(DBG_CFG, "CHILD_SA '%s' is already being routed", found->name); this->lock->unlock(this->lock); other->destroy(other); me->destroy(me); return FALSE; } /* config might have changed so update everything */ DBG1(DBG_CFG, "updating already routed CHILD_SA '%s'", found->name); } INIT(entry, .name = strdup(child->get_name(child)), .peer_cfg = peer->get_ref(peer), .wildcard = wildcard, ); this->traps->insert_first(this->traps, entry); this->installing++; /* don't hold lock while creating CHILD_SA and installing policies */ this->lock->unlock(this->lock); /* create and route CHILD_SA */ child_sa_create_t child_data = { /* TODO: no reason to allocate unique interface IDs, there is currently * no event to use them upon trap installation and we'd also have to * pass them in a later initiate() call */ .if_id_in_def = peer->get_if_id(peer, TRUE), .if_id_out_def = peer->get_if_id(peer, FALSE), .cpu = CPU_ID_MAX, }; child_sa = child_sa_create(me, other, child, &child_data); local = linked_list_create_with_items(me, NULL); remote = linked_list_create_with_items(other, NULL); status = install_trap(child_sa, local, remote); local->destroy_offset(local, offsetof(host_t, destroy)); remote->destroy_offset(remote, offsetof(host_t, destroy)); if (status != SUCCESS) { DBG1(DBG_CFG, "installing trap failed"); this->lock->write_lock(this->lock); this->traps->remove(this->traps, entry, NULL); this->lock->unlock(this->lock); entry->child_sa = child_sa; destroy_entry(entry); } else { this->lock->write_lock(this->lock); entry->child_sa = child_sa; this->lock->unlock(this->lock); result = TRUE; } if (found) { destroy_entry(found); } this->lock->write_lock(this->lock); /* do this at the end, so entries created temporarily are also destroyed */ this->installing--; this->condvar->signal(this->condvar); this->lock->unlock(this->lock); return result; } METHOD(trap_manager_t, uninstall, bool, private_trap_manager_t *this, char *peer, char *child) { enumerator_t *enumerator; entry_t *entry, *found = NULL; this->lock->write_lock(this->lock); while (this->installing) { this->condvar->wait(this->condvar, this->lock); } enumerator = this->traps->create_enumerator(this->traps); while (enumerator->enumerate(enumerator, &entry)) { if (!entry->external && streq(entry->name, child) && (!peer || streq(peer, entry->peer_cfg->get_name(entry->peer_cfg)))) { this->traps->remove_at(this->traps, enumerator); found = entry; break; } } enumerator->destroy(enumerator); this->lock->unlock(this->lock); if (!found) { return FALSE; } destroy_entry(found); return TRUE; } METHOD(trap_manager_t, install_external, bool, private_trap_manager_t *this, peer_cfg_t *peer, child_sa_t *child, linked_list_t *local, linked_list_t *remote) { entry_t *entry; this->lock->write_lock(this->lock); if (this->installing == INSTALL_DISABLED) { /* flush() has been called */ this->lock->unlock(this->lock); return FALSE; } INIT(entry, .name = strdup(child->get_name(child)), .peer_cfg = peer->get_ref(peer), .child_sa = child, .external = TRUE, ); this->traps->insert_first(this->traps, entry); this->lock->unlock(this->lock); if (install_trap(child, local, remote) != SUCCESS) { DBG1(DBG_CFG, "installing trap failed"); this->lock->write_lock(this->lock); this->traps->remove(this->traps, entry, NULL); this->lock->unlock(this->lock); destroy_entry(entry); return FALSE; } return TRUE; } METHOD(trap_manager_t, remove_external, bool, private_trap_manager_t *this, child_sa_t *child) { enumerator_t *enumerator; entry_t *entry, *found = NULL; this->lock->write_lock(this->lock); enumerator = this->traps->create_enumerator(this->traps); while (enumerator->enumerate(enumerator, &entry)) { if (entry->external && entry->child_sa == child) { this->traps->remove_at(this->traps, enumerator); found = entry; break; } } enumerator->destroy(enumerator); this->lock->unlock(this->lock); if (!found) { return FALSE; } destroy_entry(found); return TRUE; } CALLBACK(trap_filter, bool, rwlock_t *lock, enumerator_t *orig, va_list args) { entry_t *entry; peer_cfg_t **peer_cfg; child_sa_t **child_sa; VA_ARGS_VGET(args, peer_cfg, child_sa); while (orig->enumerate(orig, &entry)) { if (!entry->child_sa || entry->external) { /* skip entries that are currently being installed or are managed * externally */ continue; } if (peer_cfg) { *peer_cfg = entry->peer_cfg; } if (child_sa) { *child_sa = entry->child_sa; } return TRUE; } return FALSE; } METHOD(trap_manager_t, create_enumerator, enumerator_t*, private_trap_manager_t *this) { this->lock->read_lock(this->lock); return enumerator_create_filter(this->traps->create_enumerator(this->traps), trap_filter, this->lock, (void*)this->lock->unlock); } METHOD(trap_manager_t, acquire, void, private_trap_manager_t *this, uint32_t reqid, kernel_acquire_data_t *data) { enumerator_t *enumerator; entry_t *entry, *found = NULL; acquire_t *acquire = NULL; peer_cfg_t *peer; child_cfg_t *child; ike_sa_t *ike_sa; host_t *host = NULL; uint32_t allocated_reqid, seq = 0; bool wildcard; this->lock->read_lock(this->lock); enumerator = this->traps->create_enumerator(this->traps); while (enumerator->enumerate(enumerator, &entry)) { if (entry->child_sa && entry->child_sa->get_reqid(entry->child_sa) == reqid) { found = entry; break; } } enumerator->destroy(enumerator); if (!found) { DBG1(DBG_CFG, "trap not found, unable to acquire reqid %d", reqid); this->lock->unlock(this->lock); return; } wildcard = found->wildcard; if (wildcard && (!data->dst || !data->src)) { DBG1(DBG_CFG, "unable to process acquire without selectors for " "trap without destination address (reqid %u)", reqid); this->lock->unlock(this->lock); return; } this->mutex->lock(this->mutex); if (wildcard) { /* for wildcard acquires we check that we don't have a pending acquire * with the same peer */ uint8_t mask; data->dst->to_subnet(data->dst, &host, &mask); if (this->acquires->find_first(this->acquires, acquire_by_dst, (void**)&acquire, host)) { host->destroy(host); } } else { this->acquires->find_first(this->acquires, acquire_by_reqid, (void**)&acquire, reqid, data->cpu, data->label); } if (!acquire) { seq = data->seq = data->seq ?: ref_get_nonzero(&this->acquire_seq); INIT(acquire, .dst = host, .reqid = reqid, .data = kernel_acquire_data_clone(data), ); this->acquires->insert_last(this->acquires, acquire); } else if (data->seq && data->seq != acquire->data->seq) { /* acquire got retriggered, update to latest sequence number */ acquire->new_seq = data->seq; } this->mutex->unlock(this->mutex); if (!seq) { DBG1(DBG_CFG, "ignoring acquire for reqid %u, connection attempt " "pending", reqid); this->lock->unlock(this->lock); return; } peer = found->peer_cfg->get_ref(found->peer_cfg); child = found->child_sa->get_config(found->child_sa); child = child->get_ref(child); /* only pass allocated reqids explicitly, take a reference */ allocated_reqid = found->child_sa->get_reqid_ref(found->child_sa); /* don't hold the lock while checking out the IKE_SA */ this->lock->unlock(this->lock); if (wildcard) { /* the peer config would match IKE_SAs with other peers */ ike_sa = charon->ike_sa_manager->create_new(charon->ike_sa_manager, peer->get_ike_version(peer), TRUE); if (ike_sa) { ike_cfg_t *ike_cfg; uint16_t port; uint8_t mask; ike_sa->set_peer_cfg(ike_sa, peer); ike_cfg = ike_sa->get_ike_cfg(ike_sa); port = ike_cfg->get_other_port(ike_cfg); data->dst->to_subnet(data->dst, &host, &mask); host->set_port(host, port); ike_sa->set_other_host(ike_sa, host); port = ike_cfg->get_my_port(ike_cfg); data->src->to_subnet(data->src, &host, &mask); host->set_port(host, port); ike_sa->set_my_host(ike_sa, host); charon->bus->set_sa(charon->bus, ike_sa); } } else { ike_sa = charon->ike_sa_manager->checkout_by_config( charon->ike_sa_manager, peer); } peer->destroy(peer); if (ike_sa) { child_init_args_t args = { .reqid = allocated_reqid, .cpu = data->cpu, .src = data->src, .dst = data->dst, .label = data->label, .seq = seq, }; if (this->ignore_acquire_ts || ike_sa->get_version(ike_sa) == IKEV1) { /* in IKEv1, don't prepend the acquiring packet TS, as we only * have a single TS that we can establish in a Quick Mode. */ args.src = args.dst = NULL; } this->mutex->lock(this->mutex); acquire->ike_sa = ike_sa; this->mutex->unlock(this->mutex); if (ike_sa->initiate(ike_sa, child, &args) != DESTROY_ME) { charon->ike_sa_manager->checkin(charon->ike_sa_manager, ike_sa); } else { charon->ike_sa_manager->checkin_and_destroy(charon->ike_sa_manager, ike_sa); } } else { this->mutex->lock(this->mutex); this->acquires->remove(this->acquires, acquire, NULL); this->mutex->unlock(this->mutex); destroy_acquire(acquire); child->destroy(child); } if (allocated_reqid) { charon->kernel->release_reqid(charon->kernel, allocated_reqid); } } /** * Update the sequence number of the CHILD_SA before installing it in case * the acquire got retriggered. */ static void update_acquire_seq(private_trap_manager_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa) { enumerator_t *enumerator; acquire_t *acquire; uint32_t seq; /* ignore trap policies */ if (!ike_sa) { return; } /* if a CHILD_SA was not triggered by an acquire (e.g. manually or via * start action) and the kernel supports sequence numbers, we check if the * negotiated TS match the triggering packet and set the sequence number * to remove the temporary state in the kernel */ seq = child_sa->get_acquire_seq(child_sa); if (!seq && !(charon->kernel->get_features(charon->kernel) & KERNEL_ACQUIRE_SEQ)) { return; } this->mutex->lock(this->mutex); enumerator = this->acquires->create_enumerator(this->acquires); while (enumerator->enumerate(enumerator, &acquire)) { if (!seq) { /* if not triggered by an acquire, compare the TS from the packet * that triggered the current acquire, set the seq if they match */ if (!child_sa_ts_match(child_sa, acquire->data->src, acquire->data->dst)) { continue; } child_sa->set_acquire_seq(child_sa, acquire->data->seq); } else if (!acquire->ike_sa || seq != acquire->data->seq) { continue; } if (acquire->new_seq) { child_sa->set_acquire_seq(child_sa, acquire->new_seq); acquire->data->seq = acquire->new_seq; acquire->new_seq = 0; } break; } enumerator->destroy(enumerator); this->mutex->unlock(this->mutex); } /** * Complete the acquire, if successful or failed. */ static void complete(private_trap_manager_t *this, ike_sa_t *ike_sa, child_sa_t *child_sa) { enumerator_t *enumerator; acquire_t *acquire; uint32_t seq = 0; /* ignore trap policies and CHILD_SAs not triggered by an acquire */ if (!ike_sa || (child_sa && !(seq = child_sa->get_acquire_seq(child_sa)))) { return; } this->mutex->lock(this->mutex); enumerator = this->acquires->create_enumerator(this->acquires); while (enumerator->enumerate(enumerator, &acquire)) { /* just look at the sequence number when handling a CHILD_SA event, * otherwise, compare the IKE_SA */ if (!acquire->ike_sa || (seq && seq != acquire->data->seq) || (!seq && acquire->ike_sa != ike_sa)) { continue; } this->acquires->remove_at(this->acquires, enumerator); destroy_acquire(acquire); } enumerator->destroy(enumerator); this->mutex->unlock(this->mutex); } METHOD(listener_t, ike_state_change, bool, trap_listener_t *listener, ike_sa_t *ike_sa, ike_sa_state_t state) { switch (state) { case IKE_DESTROYING: complete(listener->traps, ike_sa, NULL); return TRUE; default: return TRUE; } } METHOD(listener_t, child_state_change, bool, trap_listener_t *listener, ike_sa_t *ike_sa, child_sa_t *child_sa, child_sa_state_t state) { switch (state) { case CHILD_INSTALLING: update_acquire_seq(listener->traps, ike_sa, child_sa); return TRUE; case CHILD_INSTALLED: case CHILD_DESTROYING: complete(listener->traps, ike_sa, child_sa); return TRUE; default: return TRUE; } } METHOD(listener_t, ike_reestablish_pre, bool, trap_listener_t *listener, ike_sa_t *old, ike_sa_t *new) { private_trap_manager_t *this = listener->traps; enumerator_t *enumerator; acquire_t *acquire; if (old->has_condition(old, COND_REDIRECTED)) { /* if we get redirected during IKE_AUTH, we just migrate to the new SA. * we'd have to disable listening for child state changes otherwise (due * to task migration). and if the initiation failed, the initial SA * couldn't be used anyway, so we can also just track the destruction of * of the new one in that case */ this->mutex->lock(this->mutex); enumerator = this->acquires->create_enumerator(this->acquires); while (enumerator->enumerate(enumerator, &acquire)) { if (acquire->ike_sa == old) { acquire->ike_sa = new; } } enumerator->destroy(enumerator); this->mutex->unlock(this->mutex); } return TRUE; } METHOD(trap_manager_t, flush, void, private_trap_manager_t *this) { this->lock->write_lock(this->lock); while (this->installing) { this->condvar->wait(this->condvar, this->lock); } this->traps->destroy_function(this->traps, (void*)destroy_entry); this->traps = linked_list_create(); this->installing = INSTALL_DISABLED; this->lock->unlock(this->lock); } METHOD(trap_manager_t, destroy, void, private_trap_manager_t *this) { charon->bus->remove_listener(charon->bus, &this->listener.listener); this->traps->destroy_function(this->traps, (void*)destroy_entry); this->acquires->destroy_function(this->acquires, (void*)destroy_acquire); this->condvar->destroy(this->condvar); this->mutex->destroy(this->mutex); this->lock->destroy(this->lock); free(this); } /** * See header */ trap_manager_t *trap_manager_create(void) { private_trap_manager_t *this; INIT(this, .public = { .install = _install, .uninstall = _uninstall, .install_external = _install_external, .remove_external = _remove_external, .create_enumerator = _create_enumerator, .acquire = _acquire, .flush = _flush, .destroy = _destroy, }, .listener = { .traps = this, .listener = { .ike_state_change = _ike_state_change, .ike_reestablish_pre = _ike_reestablish_pre, .child_state_change = _child_state_change, }, }, .traps = linked_list_create(), .acquires = linked_list_create(), .mutex = mutex_create(MUTEX_TYPE_DEFAULT), .lock = rwlock_create(RWLOCK_TYPE_DEFAULT), .condvar = rwlock_condvar_create(), .ignore_acquire_ts = lib->settings->get_bool(lib->settings, "%s.ignore_acquire_ts", FALSE, lib->ns), ); charon->bus->add_listener(charon->bus, &this->listener.listener); return &this->public; }