Files
strongswan-ext/src/libcharon/plugins/kernel_libipsec/kernel_libipsec_router.c
T
Tobias Brunner e306fa5f73 kernel-libipsec: Add support to send/receive raw ESP packets
This is currently only supported on Linux and with the appropriate
permissions.

Since it's experimental, it's disabled by default.

The log messages for each sent and received ESP message are logged in NET
like the ones in the socket-default plugin for UDP-encapsulated messages.
2023-05-23 13:19:47 +02:00

372 lines
8.5 KiB
C

/*
* Copyright (C) 2013 Tobias Brunner
*
* Copyright (C) secunet Security Networks AG
*
* 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 "kernel_libipsec_esp_handler.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];
/**
* ESP handler to send raw ESP packets
*/
kernel_libipsec_esp_handler_t *esp_handler;
};
/**
* 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);
}
CALLBACK(send_esp, void,
private_kernel_libipsec_router_t *this, esp_packet_t *packet, bool encap)
{
if (encap)
{
charon->sender->send_no_marker(charon->sender, (packet_t*)packet);
}
else if (this->esp_handler)
{
this->esp_handler->send(this->esp_handler, packet);
}
else
{ /* shouldn't happen as UDP encap is forced without ESP handler */
packet->destroy(packet);
}
}
CALLBACK(receiver_esp_cb, void,
void *data, packet_t *packet)
{
ipsec->processor->queue_inbound(ipsec->processor,
esp_packet_create_from_packet(packet));
}
CALLBACK(deliver_plain, void,
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);
ipsec->processor->unregister_outbound(ipsec->processor, send_esp);
ipsec->processor->unregister_inbound(ipsec->processor, 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"),
},
.esp_handler = lib->get(lib, "kernel-libipsec-esp-handler"),
);
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, this);
ipsec->processor->register_inbound(ipsec->processor, deliver_plain, this);
charon->receiver->add_esp_cb(charon->receiver, 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;
}