365 lines
8.3 KiB
C
365 lines
8.3 KiB
C
/*
|
|
* Copyright (C) 2013 Tobias Brunner
|
|
* HSR Hochschule fuer Technik Rapperswil
|
|
*
|
|
* 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 <http://www.fsf.org/copyleft/gpl.txt>.
|
|
*
|
|
* 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 <unistd.h>
|
|
#include <fcntl.h>
|
|
|
|
#include "kernel_libipsec_router.h"
|
|
|
|
#include <daemon.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
|
|
*/
|
|
struct private_kernel_libipsec_router_t {
|
|
|
|
/**
|
|
* Public interface
|
|
*/
|
|
kernel_libipsec_router_t public;
|
|
|
|
/**
|
|
* Default TUN device if kernel interface does not require separate TUN
|
|
* devices per VIP or for tunnels without VIP.
|
|
*/
|
|
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
|
|
*/
|
|
static void send_esp(void *data, esp_packet_t *packet)
|
|
{
|
|
charon->sender->send_no_marker(charon->sender, (packet_t*)packet);
|
|
}
|
|
|
|
/**
|
|
* Receiver callback
|
|
*/
|
|
static void receiver_esp_cb(void *data, packet_t *packet)
|
|
{
|
|
ipsec->processor->queue_inbound(ipsec->processor,
|
|
esp_packet_create_from_packet(packet));
|
|
}
|
|
|
|
/**
|
|
* Inbound callback
|
|
*/
|
|
static void deliver_plain(private_kernel_libipsec_router_t *this,
|
|
ip_packet_t *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);
|
|
}
|
|
|
|
/**
|
|
* Read and process outbound plaintext packet for the given TUN device
|
|
*/
|
|
static void process_plain(tun_device_t *tun)
|
|
{
|
|
chunk_t raw;
|
|
|
|
if (tun->read_packet(tun, &raw))
|
|
{
|
|
ip_packet_t *packet;
|
|
|
|
packet = ip_packet_create(raw);
|
|
if (packet)
|
|
{
|
|
ipsec->processor->queue_outbound(ipsec->processor, packet);
|
|
}
|
|
else
|
|
{
|
|
DBG1(DBG_KNL, "invalid IP packet read from TUN device");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find flagged revents in a pollfd set by fd
|
|
*/
|
|
static int find_revents(struct pollfd *pfd, int count, int fd)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
if (pfd[i].fd == fd)
|
|
{
|
|
return pfd[i].revents;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Job handling outbound plaintext packets
|
|
*/
|
|
static job_requeue_t handle_plain(private_kernel_libipsec_router_t *this)
|
|
{
|
|
enumerator_t *enumerator;
|
|
tun_entry_t *entry;
|
|
bool oldstate;
|
|
int count = 0;
|
|
char buf[1];
|
|
struct pollfd *pfd;
|
|
|
|
this->lock->read_lock(this->lock);
|
|
|
|
pfd = alloca(sizeof(*pfd) * (this->tuns->get_count(this->tuns) + 2));
|
|
pfd[count].fd = this->notify[0];
|
|
pfd[count].events = POLLIN;
|
|
count++;
|
|
pfd[count].fd = this->tun.fd;
|
|
pfd[count].events = POLLIN;
|
|
count++;
|
|
|
|
enumerator = this->tuns->create_enumerator(this->tuns);
|
|
while (enumerator->enumerate(enumerator, NULL, &entry))
|
|
{
|
|
pfd[count].fd = entry->fd;
|
|
pfd[count].events = POLLIN;
|
|
count++;
|
|
}
|
|
enumerator->destroy(enumerator);
|
|
this->lock->unlock(this->lock);
|
|
|
|
oldstate = thread_cancelability(TRUE);
|
|
if (poll(pfd, count, -1) <= 0)
|
|
{
|
|
thread_cancelability(oldstate);
|
|
return JOB_REQUEUE_FAIR;
|
|
}
|
|
thread_cancelability(oldstate);
|
|
|
|
if (pfd[0].revents & POLLIN)
|
|
{
|
|
/* list of TUN devices changed, read notification data, rebuild FDs */
|
|
while (read(this->notify[0], &buf, sizeof(buf)) == sizeof(buf))
|
|
{
|
|
/* nop */
|
|
}
|
|
return JOB_REQUEUE_DIRECT;
|
|
}
|
|
|
|
if (pfd[1].revents & POLLIN)
|
|
{
|
|
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 (find_revents(pfd, count, entry->fd) & POLLIN)
|
|
{
|
|
process_plain(entry->tun);
|
|
}
|
|
}
|
|
enumerator->destroy(enumerator);
|
|
this->lock->unlock(this->lock);
|
|
|
|
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)
|
|
{
|
|
charon->receiver->del_esp_cb(charon->receiver,
|
|
(receiver_esp_cb_t)receiver_esp_cb);
|
|
ipsec->processor->unregister_outbound(ipsec->processor,
|
|
(ipsec_outbound_cb_t)send_esp);
|
|
ipsec->processor->unregister_inbound(ipsec->processor,
|
|
(ipsec_inbound_cb_t)deliver_plain);
|
|
charon->kernel->remove_listener(charon->kernel, &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()
|
|
{
|
|
private_kernel_libipsec_router_t *this;
|
|
|
|
INIT(this,
|
|
.public = {
|
|
.listener = {
|
|
.tun = _tun,
|
|
},
|
|
.get_tun_name = _get_tun_name,
|
|
.destroy = _destroy,
|
|
},
|
|
.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);
|
|
|
|
charon->kernel->add_listener(charon->kernel, &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);
|
|
charon->receiver->add_esp_cb(charon->receiver,
|
|
(receiver_esp_cb_t)receiver_esp_cb, NULL);
|
|
lib->processor->queue_job(lib->processor,
|
|
(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;
|
|
}
|