]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
kernel-libipsec: Router reads packets from multiple TUN devices
authorTobias Brunner <tobias@strongswan.org>
Sat, 15 Jun 2013 14:21:08 +0000 (16:21 +0200)
committerTobias Brunner <tobias@strongswan.org>
Fri, 21 Jun 2013 15:03:21 +0000 (17:03 +0200)
These devices are collected via kernel_listener_t interface.

src/libcharon/plugins/kernel_libipsec/kernel_libipsec_ipsec.c
src/libcharon/plugins/kernel_libipsec/kernel_libipsec_plugin.c
src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.c
src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.h

index efa519a83c8dfbf9286711bef7b2750e3e29ce44..f605501ba75524c7bdbb3a1604c49520962b1a5f 100644 (file)
@@ -13,6 +13,7 @@
  */
 
 #include "kernel_libipsec_ipsec.h"
+#include "kernel_libipsec_router.h"
 
 #include <library.h>
 #include <ipsec.h>
@@ -35,11 +36,6 @@ struct private_kernel_libipsec_ipsec_t {
         */
        ipsec_event_listener_t ipsec_listener;
 
-       /**
-        * TUN device
-        */
-       tun_device_t *tun;
-
        /**
         * Mutex to lock access to various lists
         */
@@ -389,7 +385,7 @@ static bool install_route(private_kernel_libipsec_ipsec_t *this,
        }
 
        INIT(route,
-               .if_name = strdup(this->tun->get_name(this->tun)),
+               .if_name = router->get_tun_name(router, is_virtual ? src_ip : NULL),
                .src_ip = src_ip,
                .dst_net = chunk_clone(policy->dst.net->get_address(policy->dst.net)),
                .prefixlen = policy->dst.mask,
@@ -634,7 +630,6 @@ kernel_libipsec_ipsec_t *kernel_libipsec_ipsec_create()
                .ipsec_listener = {
                        .expire = expire,
                },
-               .tun = lib->get(lib, "kernel-libipsec-tun"),
                .mutex = mutex_create(MUTEX_TYPE_DEFAULT),
                .policies = linked_list_create(),
                .excludes = linked_list_create(),
index 003ea29c0cdb416c3fb7dfc789413fdb67505f56..d5f3bc248b2d1b92023cac8f6c17a7b40ee96dbd 100644 (file)
@@ -64,7 +64,7 @@ static bool create_router(private_kernel_libipsec_plugin_t *this,
        }
        else
        {
-               this->router->destroy(this->router);
+               DESTROY_IF(this->router);
        }
        return TRUE;
 }
index 9a829f9650a4d7769f219c4e799b86074a2128bd..6ce1d4eb0b95b8d3f26385e8a0753d06614a447a 100644 (file)
  * for more details.
  */
 
+#include <unistd.h>
+#include <fcntl.h>
+
 #include "kernel_libipsec_router.h"
 
 #include <daemon.h>
+#include <hydra.h>
 #include <ipsec.h>
+#include <collections/hashtable.h>
 #include <networking/tun_device.h>
+#include <threading/rwlock.h>
+#include <threading/thread.h>
 #include <processing/jobs/callback_job.h>
 
 typedef struct private_kernel_libipsec_router_t private_kernel_libipsec_router_t;
 
+/**
+ * Entry in the TUN device map
+ */
+typedef struct {
+       /** virtual IP (points to internal data of tun) */
+       host_t *addr;
+       /** underlying TUN file descriptor (cached from tun) */
+       int fd;
+       /** TUN device */
+       tun_device_t *tun;
+} tun_entry_t;
+
+/**
+ * Single instance of the router
+ */
+kernel_libipsec_router_t *router;
+
 /**
  * Private data
  */
@@ -33,11 +57,43 @@ struct private_kernel_libipsec_router_t {
        kernel_libipsec_router_t public;
 
        /**
-        * TUN device
+        * Default TUN device if kernel interface does not require separate TUN
+        * devices per VIP or for tunnels without VIP.
         */
-       tun_device_t *tun;
+       tun_entry_t tun;
+
+       /**
+        * Hashtable that maps virtual IPs to TUN devices (tun_entry_t).
+        */
+       hashtable_t *tuns;
+
+       /**
+        * Lock for TUN device map
+        */
+       rwlock_t *lock;
+
+       /**
+        * Pipe to signal handle_plain() about changes regarding TUN devices
+        */
+       int notify[2];
 };
 
+/**
+ * Hash function for TUN device map
+ */
+static u_int tun_entry_hash(tun_entry_t *entry)
+{
+       return chunk_hash(entry->addr->get_address(entry->addr));
+}
+
+/**
+ * Comparison function for TUN device map
+ */
+static bool tun_entry_equals(tun_entry_t *a, tun_entry_t *b)
+{
+       return a->addr->ip_equals(a->addr, b->addr);
+}
+
 /**
  * Outbound callback
  */
@@ -61,18 +117,56 @@ static void receiver_esp_cb(void *data, packet_t *packet)
 static void deliver_plain(private_kernel_libipsec_router_t *this,
                                                  ip_packet_t *packet)
 {
-       this->tun->write_packet(this->tun, packet->get_encoding(packet));
+       tun_device_t *tun;
+       tun_entry_t *entry, lookup = {
+               .addr = packet->get_destination(packet),
+       };
+
+       this->lock->read_lock(this->lock);
+       entry = this->tuns->get(this->tuns, &lookup);
+       tun = entry ? entry->tun : this->tun.tun;
+       tun->write_packet(tun, packet->get_encoding(packet));
+       this->lock->unlock(this->lock);
        packet->destroy(packet);
 }
 
 /**
- * Job handling outbound plaintext packets
+ * Create an FD set covering all TUN devices and the read end of the notify pipe
  */
-static job_requeue_t handle_plain(private_kernel_libipsec_router_t *this)
+static int collect_fds(private_kernel_libipsec_router_t *this, fd_set *fds)
+{
+       enumerator_t *enumerator;
+       tun_entry_t *entry;
+       int maxfd;
+
+       FD_ZERO(fds);
+       FD_SET(this->notify[0], fds);
+       maxfd = this->notify[0];
+
+       FD_SET(this->tun.fd, fds);
+       maxfd = max(maxfd, this->tun.fd);
+
+       this->lock->read_lock(this->lock);
+       enumerator = this->tuns->create_enumerator(this->tuns);
+       while (enumerator->enumerate(enumerator, NULL, &entry))
+       {
+               FD_SET(entry->fd, fds);
+               maxfd = max(maxfd, entry->fd);
+       }
+       enumerator->destroy(enumerator);
+       this->lock->unlock(this->lock);
+
+       return maxfd + 1;
+}
+
+/**
+ * Read and process outbound plaintext packet for the given TUN device
+ */
+static void process_plain(tun_device_t *tun)
 {
        chunk_t raw;
 
-       if (this->tun->read_packet(this->tun, &raw))
+       if (tun->read_packet(tun, &raw))
        {
                ip_packet_t *packet;
 
@@ -86,9 +180,113 @@ static job_requeue_t handle_plain(private_kernel_libipsec_router_t *this)
                        DBG1(DBG_KNL, "invalid IP packet read from TUN device");
                }
        }
+}
+
+/**
+ * Handle waiting data for any TUN device
+ */
+static void handle_tuns(private_kernel_libipsec_router_t *this, fd_set *fds)
+{
+       enumerator_t *enumerator;
+       tun_entry_t *entry;
+
+       if (FD_ISSET(this->tun.fd, fds))
+       {
+               process_plain(this->tun.tun);
+       }
+
+       this->lock->read_lock(this->lock);
+       enumerator = this->tuns->create_enumerator(this->tuns);
+       while (enumerator->enumerate(enumerator, NULL, &entry))
+       {
+               if (FD_ISSET(entry->fd, fds))
+               {
+                       process_plain(entry->tun);
+               }
+       }
+       enumerator->destroy(enumerator);
+       this->lock->unlock(this->lock);
+}
+
+/**
+ * Job handling outbound plaintext packets
+ */
+static job_requeue_t handle_plain(private_kernel_libipsec_router_t *this)
+{
+       bool oldstate;
+       fd_set fds;
+       int maxfd;
+
+       maxfd = collect_fds(this, &fds);
+
+       oldstate = thread_cancelability(TRUE);
+       if (select(maxfd, &fds, NULL, NULL, NULL) <= 0)
+       {
+               thread_cancelability(oldstate);
+               return JOB_REQUEUE_FAIR;
+       }
+       thread_cancelability(oldstate);
+
+       if (FD_ISSET(this->notify[0], &fds))
+       {       /* list of TUN devices changed, read notification data, rebuild FDs */
+               char buf[1];
+               while (read(this->notify[0], &buf, sizeof(buf)) == sizeof(buf));
+               return JOB_REQUEUE_DIRECT;
+       }
+
+       handle_tuns(this, &fds);
        return JOB_REQUEUE_DIRECT;
 }
 
+METHOD(kernel_listener_t, tun, bool,
+       private_kernel_libipsec_router_t *this, tun_device_t *tun, bool created)
+{
+       tun_entry_t *entry, lookup;
+       char buf[] = {0x01};
+
+       this->lock->write_lock(this->lock);
+       if (created)
+       {
+               INIT(entry,
+                       .addr = tun->get_address(tun, NULL),
+                       .fd = tun->get_fd(tun),
+                       .tun = tun,
+               );
+               this->tuns->put(this->tuns, entry, entry);
+       }
+       else
+       {
+               lookup.addr = tun->get_address(tun, NULL);
+               entry = this->tuns->remove(this->tuns, &lookup);
+               free(entry);
+       }
+       /* notify handler thread to recreate FD set */
+       ignore_result(write(this->notify[1], buf, sizeof(buf)));
+       this->lock->unlock(this->lock);
+       return TRUE;
+}
+
+METHOD(kernel_libipsec_router_t, get_tun_name, char*,
+       private_kernel_libipsec_router_t *this, host_t *vip)
+{
+       tun_entry_t *entry, lookup = {
+               .addr = vip,
+       };
+       tun_device_t *tun;
+       char *name;
+
+       if (!vip)
+       {
+               return strdup(this->tun.tun->get_name(this->tun.tun));
+       }
+       this->lock->read_lock(this->lock);
+       entry = this->tuns->get(this->tuns, &lookup);
+       tun = entry ? entry->tun : this->tun.tun;
+       name = strdup(tun->get_name(tun));
+       this->lock->unlock(this->lock);
+       return name;
+}
+
 METHOD(kernel_libipsec_router_t, destroy, void,
        private_kernel_libipsec_router_t *this)
 {
@@ -98,23 +296,61 @@ METHOD(kernel_libipsec_router_t, destroy, void,
                                                                                 (ipsec_outbound_cb_t)send_esp);
        ipsec->processor->unregister_inbound(ipsec->processor,
                                                                                 (ipsec_inbound_cb_t)deliver_plain);
+       hydra->kernel_interface->remove_listener(hydra->kernel_interface,
+                                                                                        &this->public.listener);
+       this->lock->destroy(this->lock);
+       this->tuns->destroy(this->tuns);
+       close(this->notify[0]);
+       close(this->notify[1]);
+       router = NULL;
        free(this);
 }
 
+/**
+ * Set O_NONBLOCK on the given socket.
+ */
+static bool set_nonblock(int socket)
+{
+       int flags = fcntl(socket, F_GETFL);
+       return flags != -1 && fcntl(socket, F_SETFL, flags | O_NONBLOCK) != -1;
+}
+
 /*
  * See header file
  */
-kernel_libipsec_router_t *kernel_libipsec_router_create(tun_device_t *tun)
+kernel_libipsec_router_t *kernel_libipsec_router_create()
 {
        private_kernel_libipsec_router_t *this;
 
        INIT(this,
                .public = {
+                       .listener = {
+                               .tun = _tun,
+                       },
+                       .get_tun_name = _get_tun_name,
                        .destroy = _destroy,
                },
-               .tun = lib->get(lib, "kernel-libipsec-tun"),
+               .tun = {
+                       .tun = lib->get(lib, "kernel-libipsec-tun"),
+               }
        );
 
+       if (pipe(this->notify) != 0 ||
+               !set_nonblock(this->notify[0]) || !set_nonblock(this->notify[1]))
+       {
+               DBG1(DBG_KNL, "creating notify pipe for kernel-libipsec router failed");
+               free(this);
+               return NULL;
+       }
+
+       this->tun.fd = this->tun.tun->get_fd(this->tun.tun);
+
+       this->tuns = hashtable_create((hashtable_hash_t)tun_entry_hash,
+                                                                 (hashtable_equals_t)tun_entry_equals, 4);
+       this->lock = rwlock_create(RWLOCK_TYPE_DEFAULT);
+
+       hydra->kernel_interface->add_listener(hydra->kernel_interface,
+                                                                                 &this->public.listener);
        ipsec->processor->register_outbound(ipsec->processor, send_esp, NULL);
        ipsec->processor->register_inbound(ipsec->processor,
                                                                        (ipsec_inbound_cb_t)deliver_plain, this);
@@ -124,5 +360,6 @@ kernel_libipsec_router_t *kernel_libipsec_router_create(tun_device_t *tun)
                        (job_t*)callback_job_create((callback_job_cb_t)handle_plain, this,
                                                                        NULL, (callback_job_cancel_t)return_false));
 
+       router = &this->public;
        return &this->public;
 }
index a7ef27c0aa94ee8a6167523dfef4fe7575f338ef..7b2f3c6c5c8f607ccc7c5157d03e5642cb08fce1 100644 (file)
@@ -21,6 +21,8 @@
 #ifndef KERNEL_LIBIPSEC_ROUTER_H_
 #define KERNEL_LIBIPSEC_ROUTER_H_
 
+#include <kernel/kernel_listener.h>
+
 typedef struct kernel_libipsec_router_t kernel_libipsec_router_t;
 
 /**
@@ -29,12 +31,30 @@ typedef struct kernel_libipsec_router_t kernel_libipsec_router_t;
  */
 struct kernel_libipsec_router_t {
 
+       /**
+        * Implements kernel_listener_t interface
+        */
+       kernel_listener_t listener;
+
+       /**
+        * Get the name of the TUN device to be used with the given virtual IP.
+        *
+        * @param vip   virtual IP
+        * @return              allocated name
+        */
+       char *(*get_tun_name)(kernel_libipsec_router_t *this, host_t *vip);
+
        /**
         * Destroy the given instance
         */
        void (*destroy)(kernel_libipsec_router_t *this);
 };
 
+/**
+ * Single instance of this class, if created
+ */
+extern kernel_libipsec_router_t *router;
+
 /**
  * Create a kernel_libipsec_router_t instance.
  *