]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
kernel-wfp: Create userland state for SAs/policies to install in kernel
authorMartin Willi <martin@revosec.ch>
Fri, 1 Nov 2013 09:54:38 +0000 (10:54 +0100)
committerMartin Willi <martin@revosec.ch>
Wed, 4 Jun 2014 14:32:06 +0000 (16:32 +0200)
src/libcharon/plugins/kernel_wfp/kernel_wfp_ipsec.c

index ac37cb66ebf1d0d98e8913884b79248b72585df2..35bf8cc8ab0158238f3b6b1d2aea374d7e0176db 100644 (file)
@@ -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;