aboutsummaryrefslogtreecommitdiffstats
path: root/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c
diff options
context:
space:
mode:
authorMartin Willi <martin@revosec.ch>2013-11-01 10:54:38 +0100
committerMartin Willi <martin@revosec.ch>2014-06-04 16:32:06 +0200
commit96ab7a8022341997cdf74459e2864c56be31af55 (patch)
treeafef7865f00aaa608f1e9f273952f498c42dff78 /src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c
parent8d91eee3fcc3c170ff97801ac758d430db8ae8b3 (diff)
downloadstrongswan-96ab7a8022341997cdf74459e2864c56be31af55.tar.bz2
strongswan-96ab7a8022341997cdf74459e2864c56be31af55.tar.xz
kernel-wfp: Create userland state for SAs/policies to install in kernel
Diffstat (limited to 'src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c')
-rw-r--r--src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c369
1 files changed, 364 insertions, 5 deletions
diff --git a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c
index ac37cb66e..35bf8cc8a 100644
--- a/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c
+++ b/src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c
@@ -16,6 +16,9 @@
#include "kernel_wfp_ipsec.h"
#include <daemon.h>
+#include <threading/mutex.h>
+#include <collections/array.h>
+#include <collections/hashtable.h>
typedef struct private_kernel_wfp_ipsec_t private_kernel_wfp_ipsec_t;
@@ -25,8 +28,176 @@ struct private_kernel_wfp_ipsec_t {
* Public interface
*/
kernel_wfp_ipsec_t public;
+
+ /**
+ * Next SPI to allocate
+ */
+ refcount_t nextspi;
+
+ /**
+ * SAD/SPD entries, as reqid => entry_t
+ */
+ hashtable_t *entries;
+
+ /**
+ * SAD entry lookup, as sa_entry_t => entry_t
+ */
+ hashtable_t *sas;
+
+ /**
+ * Mutex for accessing entries
+ */
+ mutex_t *mutex;
};
+/**
+ * Security association entry
+ */
+typedef struct {
+ /** SPI for this SA */
+ u_int32_t spi;
+ /** destination host address for this SPI */
+ host_t *dst;
+ /** inbound or outbound SA? */
+ bool inbound;
+ struct {
+ /** algorithm */
+ u_int16_t alg;
+ /** key */
+ chunk_t key;
+ } integ, encr;
+} sa_entry_t;
+
+/**
+ * Destroy an SA entry
+ */
+static void sa_entry_destroy(sa_entry_t *sa)
+{
+ chunk_clear(&sa->integ.key);
+ chunk_clear(&sa->encr.key);
+ free(sa);
+}
+
+/**
+ * Hash function for sas lookup table
+ */
+static u_int hash_sa(sa_entry_t *key)
+{
+ return chunk_hash_inc(chunk_from_thing(key->spi),
+ chunk_hash(key->dst->get_address(key->dst)));
+}
+
+/**
+ * equals function for sas lookup table
+ */
+static bool equals_sa(sa_entry_t *a, sa_entry_t *b)
+{
+ return a->spi == b->spi && a->dst->ip_equals(a->dst, b->dst);
+}
+
+/**
+ * Security policy entry
+ */
+typedef struct {
+ /** policy source addresses */
+ traffic_selector_t *src;
+ /** policy destinaiton addresses */
+ traffic_selector_t *dst;
+ /** direction of policy, in|out */
+ policy_dir_t direction;
+} sp_entry_t;
+
+/**
+ * Destroy an SP entry
+ */
+static void sp_entry_destroy(sp_entry_t *sp)
+{
+ sp->src->destroy(sp->src);
+ sp->dst->destroy(sp->dst);
+ free(sp);
+}
+
+/**
+ * Collection of SA/SP database entries for a reqid
+ */
+typedef struct {
+ /** reqid of entry */
+ u_int32_t reqid;
+ /** outer address on local host */
+ host_t *local;
+ /** outer address on remote host */
+ host_t *remote;
+ /** associated security associations, as sa_entry_t* */
+ array_t *sas;
+ /** associated policies, as sp_entry_t* */
+ array_t *sps;
+ /** IPsec protocol, ESP|AH */
+ u_int8_t protocol;
+ /** IPsec mode, tunnel|transport */
+ ipsec_mode_t mode;
+ /** UDP encapsulation */
+ bool encap;
+} entry_t;
+
+/**
+ * Create a SA/SP entry set
+ */
+static entry_t *entry_create(u_int32_t reqid, host_t *local, host_t *remote,
+ u_int8_t protocol, ipsec_mode_t mode, bool encap)
+{
+ entry_t *entry;
+
+ INIT(entry,
+ .reqid = reqid,
+ .sas = array_create(0, 0),
+ .sps = array_create(0, 0),
+ .local = local->clone(local),
+ .remote = remote->clone(remote),
+ .protocol = protocol,
+ .mode = mode,
+ .encap = encap,
+ );
+ return entry;
+}
+
+/**
+ * Destroy a SA/SP entry set
+ */
+static void entry_destroy(entry_t *entry)
+{
+ array_destroy(entry->sas);
+ array_destroy(entry->sps);
+ entry->local->destroy(entry->local);
+ entry->remote->destroy(entry->remote);
+ free(entry);
+}
+
+/**
+ * Get an entry, create if not exists. May fail if non-matching entry found
+ */
+static entry_t *get_or_create_entry(private_kernel_wfp_ipsec_t *this,
+ u_int32_t reqid, host_t *local, host_t *remote,
+ u_int8_t protocol, ipsec_mode_t mode, bool encap)
+{
+ entry_t *entry;
+
+ entry = this->entries->get(this->entries, (void*)(uintptr_t)reqid);
+ if (!entry)
+ {
+ entry = entry_create(reqid, local, remote, protocol, mode, encap);
+ this->entries->put(this->entries, (void*)(uintptr_t)reqid, entry);
+ return entry;
+ }
+ if (entry->protocol == protocol &&
+ entry->mode == mode &&
+ entry->local->ip_equals(entry->local, local) &&
+ entry->remote->ip_equals(entry->remote, remote))
+ {
+ return entry;
+ }
+ return NULL;
+}
+
METHOD(kernel_ipsec_t, get_features, kernel_feature_t,
private_kernel_wfp_ipsec_t *this)
{
@@ -37,7 +208,8 @@ METHOD(kernel_ipsec_t, get_spi, status_t,
private_kernel_wfp_ipsec_t *this, host_t *src, host_t *dst,
u_int8_t protocol, u_int32_t reqid, u_int32_t *spi)
{
- return NOT_SUPPORTED;
+ *spi = ref_get(&this->nextspi);
+ return SUCCESS;
}
METHOD(kernel_ipsec_t, get_cpi, status_t,
@@ -55,7 +227,52 @@ METHOD(kernel_ipsec_t, add_sa, status_t,
u_int16_t cpi, bool initiator, bool encap, bool esn, bool inbound,
traffic_selector_t *src_ts, traffic_selector_t *dst_ts)
{
- return NOT_SUPPORTED;
+ status_t status = SUCCESS;
+ host_t *local, *remote;
+ entry_t *entry;
+ sa_entry_t *sa;
+
+ if (inbound)
+ {
+ local = dst;
+ remote = src;
+ }
+ else
+ {
+ local = src;
+ remote = dst;
+ }
+
+ this->mutex->lock(this->mutex);
+ entry = get_or_create_entry(this, reqid, local, remote,
+ protocol, mode, encap);
+ if (entry)
+ {
+ INIT(sa,
+ .spi = spi,
+ .inbound = inbound,
+ .dst = inbound ? entry->local : entry->remote,
+ .encr = {
+ .alg = enc_alg,
+ .key = chunk_clone(enc_key),
+ },
+ .integ = {
+ .alg = int_alg,
+ .key = chunk_clone(int_key),
+ },
+ );
+ array_insert(entry->sas, -1, sa);
+ this->sas->put(this->sas, sa, entry);
+ }
+ else
+ {
+ DBG1(DBG_KNL, "adding SA failed, a different SA with reqid %u exists",
+ reqid);
+ status = FAILED;
+ }
+ this->mutex->unlock(this->mutex);
+
+ return status;
}
METHOD(kernel_ipsec_t, update_sa, status_t,
@@ -78,7 +295,61 @@ METHOD(kernel_ipsec_t, del_sa, status_t,
private_kernel_wfp_ipsec_t *this, host_t *src, host_t *dst,
u_int32_t spi, u_int8_t protocol, u_int16_t cpi, mark_t mark)
{
- return NOT_SUPPORTED;
+ status_t status = NOT_FOUND;
+ entry_t *entry;
+ host_t *local, *remote;
+ enumerator_t *enumerator;
+ sa_entry_t *sa, key = {
+ .dst = dst,
+ .spi = spi,
+ };
+
+ this->mutex->lock(this->mutex);
+
+ entry = this->sas->get(this->sas, &key);
+ if (entry)
+ {
+ enumerator = array_create_enumerator(entry->sas);
+ while (enumerator->enumerate(enumerator, &sa))
+ {
+ if (sa->inbound)
+ {
+ local = dst;
+ remote = src;
+ }
+ else
+ {
+ local = src;
+ remote = dst;
+ }
+ if (sa->spi == spi && entry->protocol == protocol &&
+ local->ip_equals(local, entry->local) &&
+ remote->ip_equals(remote, entry->remote))
+ {
+ array_remove_at(entry->sas, enumerator);
+ this->sas->remove(this->sas, sa);
+ /* TODO: uninstall SA from kernel */
+ sa_entry_destroy(sa);
+ status = SUCCESS;
+ break;
+ }
+ }
+ enumerator->destroy(enumerator);
+
+ if (!array_count(entry->sas) && !array_count(entry->sps))
+ {
+ entry = this->entries->remove(this->entries,
+ (void*)(uintptr_t)entry->reqid);
+ if (entry)
+ {
+ entry_destroy(entry);
+ }
+ }
+ }
+
+ this->mutex->unlock(this->mutex);
+
+ return status;
}
METHOD(kernel_ipsec_t, flush_sas, status_t,
@@ -93,7 +364,49 @@ METHOD(kernel_ipsec_t, add_policy, status_t,
policy_dir_t direction, policy_type_t type, ipsec_sa_cfg_t *sa, mark_t mark,
policy_priority_t priority)
{
- return NOT_SUPPORTED;
+ status_t status = SUCCESS;
+ host_t *local, *remote;
+ entry_t *entry;
+ sp_entry_t *sp;
+
+ if (direction == POLICY_FWD || priority != POLICY_PRIORITY_DEFAULT)
+ {
+ return SUCCESS;
+ }
+
+ if (direction == POLICY_IN)
+ {
+ local = dst;
+ remote = src;
+ }
+ else
+ {
+ local = src;
+ remote = dst;
+ }
+
+ this->mutex->lock(this->mutex);
+ entry = get_or_create_entry(this, sa->reqid, local, remote,
+ sa->esp.use ? IPPROTO_ESP : IPPROTO_AH,
+ sa->mode, FALSE);
+ if (entry)
+ {
+ INIT(sp,
+ .src = src_ts->clone(src_ts),
+ .dst = dst_ts->clone(dst_ts),
+ .direction = direction,
+ );
+ array_insert(entry->sps, -1, sp);
+ }
+ else
+ {
+ DBG1(DBG_KNL, "adding SP failed, a different SP with reqid %u exists",
+ sa->reqid);
+ status = FAILED;
+ }
+ this->mutex->unlock(this->mutex);
+
+ return status;
}
METHOD(kernel_ipsec_t, query_policy, status_t,
@@ -109,7 +422,46 @@ METHOD(kernel_ipsec_t, del_policy, status_t,
traffic_selector_t *dst_ts, policy_dir_t direction, u_int32_t reqid,
mark_t mark, policy_priority_t priority)
{
- return NOT_SUPPORTED;
+ status_t status = NOT_FOUND;
+ entry_t *entry;
+ sp_entry_t *sp;
+ enumerator_t *enumerator;
+
+ this->mutex->lock(this->mutex);
+
+ entry = this->entries->get(this->entries, (void*)(uintptr_t)reqid);
+ if (entry)
+ {
+ enumerator = array_create_enumerator(entry->sps);
+ while (enumerator->enumerate(enumerator, &sp))
+ {
+ if (sp->direction == direction &&
+ src_ts->equals(src_ts, sp->src) &&
+ dst_ts->equals(dst_ts, sp->dst))
+ {
+ array_remove_at(entry->sps, enumerator);
+ /* TODO: uninstall SP from kernel */
+ sp_entry_destroy(sp);
+ status = SUCCESS;
+ break;
+ }
+ }
+ enumerator->destroy(enumerator);
+
+ if (!array_count(entry->sas) && !array_count(entry->sps))
+ {
+ entry = this->entries->remove(this->entries,
+ (void*)(uintptr_t)reqid);
+ if (entry)
+ {
+ entry_destroy(entry);
+ }
+ }
+ }
+
+ this->mutex->unlock(this->mutex);
+
+ return status;
}
METHOD(kernel_ipsec_t, flush_policies, status_t,
@@ -133,6 +485,9 @@ METHOD(kernel_ipsec_t, enable_udp_decap, bool,
METHOD(kernel_ipsec_t, destroy, void,
private_kernel_wfp_ipsec_t *this)
{
+ this->entries->destroy(this->entries);
+ this->sas->destroy(this->sas);
+ this->mutex->destroy(this->mutex);
free(this);
}
@@ -163,6 +518,10 @@ kernel_wfp_ipsec_t *kernel_wfp_ipsec_create()
.destroy = _destroy,
},
},
+ .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
+ .entries = hashtable_create(hashtable_hash_ptr,
+ hashtable_equals_ptr, 4),
+ .sas = hashtable_create((void*)hash_sa, (void*)equals_sa, 4),
);
return &this->public;