kernel-libipsec: Router reads packets from multiple TUN devices

These devices are collected via kernel_listener_t interface.
This commit is contained in:
Tobias Brunner
2013-06-21 17:03:21 +02:00
parent 7045defbff
commit dcaf8d570c
4 changed files with 268 additions and 16 deletions
@@ -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(),
@@ -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;
}
@@ -13,15 +13,39 @@
* 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;
}
@@ -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.
*