331 lines
8.3 KiB
C
331 lines
8.3 KiB
C
/*
|
|
* Copyright (C) 2012 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 "ipsec.h"
|
|
#include "ipsec_processor.h"
|
|
|
|
#include <utils/debug.h>
|
|
#include <library.h>
|
|
#include <threading/rwlock.h>
|
|
#include <collections/blocking_queue.h>
|
|
#include <processing/jobs/callback_job.h>
|
|
|
|
typedef struct private_ipsec_processor_t private_ipsec_processor_t;
|
|
|
|
/**
|
|
* Private additions to ipsec_processor_t.
|
|
*/
|
|
struct private_ipsec_processor_t {
|
|
|
|
/**
|
|
* Public members
|
|
*/
|
|
ipsec_processor_t public;
|
|
|
|
/**
|
|
* Queue for inbound packets (esp_packet_t*)
|
|
*/
|
|
blocking_queue_t *inbound_queue;
|
|
|
|
/**
|
|
* Queue for outbound packets (ip_packet_t*)
|
|
*/
|
|
blocking_queue_t *outbound_queue;
|
|
|
|
/**
|
|
* Registered inbound callback
|
|
*/
|
|
struct {
|
|
ipsec_inbound_cb_t cb;
|
|
void *data;
|
|
} inbound;
|
|
|
|
/**
|
|
* Registered outbound callback
|
|
*/
|
|
struct {
|
|
ipsec_outbound_cb_t cb;
|
|
void *data;
|
|
} outbound;
|
|
|
|
/**
|
|
* Lock used to synchronize access to the callbacks
|
|
*/
|
|
rwlock_t *lock;
|
|
};
|
|
|
|
/**
|
|
* Deliver an inbound IP packet to the registered listener
|
|
*/
|
|
static void deliver_inbound(private_ipsec_processor_t *this,
|
|
esp_packet_t *packet)
|
|
{
|
|
this->lock->read_lock(this->lock);
|
|
if (this->inbound.cb)
|
|
{
|
|
this->inbound.cb(this->inbound.data, packet->extract_payload(packet));
|
|
}
|
|
else
|
|
{
|
|
DBG2(DBG_ESP, "no inbound callback registered, dropping packet");
|
|
}
|
|
packet->destroy(packet);
|
|
this->lock->unlock(this->lock);
|
|
}
|
|
|
|
/**
|
|
* Processes inbound packets
|
|
*/
|
|
static job_requeue_t process_inbound(private_ipsec_processor_t *this)
|
|
{
|
|
esp_packet_t *packet;
|
|
ip_packet_t *ip_packet;
|
|
ipsec_sa_t *sa;
|
|
uint8_t next_header;
|
|
uint32_t spi, reqid;
|
|
|
|
packet = (esp_packet_t*)this->inbound_queue->dequeue(this->inbound_queue);
|
|
|
|
if (!packet->parse_header(packet, &spi))
|
|
{
|
|
packet->destroy(packet);
|
|
return JOB_REQUEUE_DIRECT;
|
|
}
|
|
|
|
sa = ipsec->sas->checkout_by_spi(ipsec->sas, spi,
|
|
packet->get_destination(packet));
|
|
if (!sa)
|
|
{
|
|
DBG2(DBG_ESP, "inbound ESP packet does not belong to an installed SA");
|
|
packet->destroy(packet);
|
|
return JOB_REQUEUE_DIRECT;
|
|
}
|
|
|
|
if (!sa->is_inbound(sa))
|
|
{
|
|
DBG1(DBG_ESP, "error: IPsec SA is not inbound");
|
|
packet->destroy(packet);
|
|
ipsec->sas->checkin(ipsec->sas, sa);
|
|
return JOB_REQUEUE_DIRECT;
|
|
}
|
|
|
|
if (packet->decrypt(packet, sa->get_esp_context(sa)) != SUCCESS)
|
|
{
|
|
ipsec->sas->checkin(ipsec->sas, sa);
|
|
packet->destroy(packet);
|
|
return JOB_REQUEUE_DIRECT;
|
|
}
|
|
ip_packet = packet->get_payload(packet);
|
|
sa->update_usestats(sa, ip_packet->get_encoding(ip_packet).len);
|
|
reqid = sa->get_reqid(sa);
|
|
ipsec->sas->checkin(ipsec->sas, sa);
|
|
|
|
next_header = packet->get_next_header(packet);
|
|
switch (next_header)
|
|
{
|
|
case IPPROTO_IPIP:
|
|
case IPPROTO_IPV6:
|
|
{
|
|
ipsec_policy_t *policy;
|
|
|
|
policy = ipsec->policies->find_by_packet(ipsec->policies,
|
|
ip_packet, TRUE, reqid);
|
|
if (policy)
|
|
{
|
|
deliver_inbound(this, packet);
|
|
policy->destroy(policy);
|
|
break;
|
|
}
|
|
DBG1(DBG_ESP, "discarding inbound IP packet %#H == %#H [%hhu] due "
|
|
"to policy", ip_packet->get_source(ip_packet),
|
|
ip_packet->get_destination(ip_packet),
|
|
ip_packet->get_next_header(ip_packet));
|
|
/* no matching policy found, fall-through */
|
|
}
|
|
case IPPROTO_NONE:
|
|
/* discard dummy packets */
|
|
/* fall-through */
|
|
default:
|
|
packet->destroy(packet);
|
|
break;
|
|
}
|
|
return JOB_REQUEUE_DIRECT;
|
|
}
|
|
|
|
/**
|
|
* Send an ESP packet using the registered outbound callback
|
|
*/
|
|
static void send_outbound(private_ipsec_processor_t *this,
|
|
esp_packet_t *packet)
|
|
{
|
|
this->lock->read_lock(this->lock);
|
|
if (this->outbound.cb)
|
|
{
|
|
this->outbound.cb(this->outbound.data, packet);
|
|
}
|
|
else
|
|
{
|
|
DBG2(DBG_ESP, "no outbound callback registered, dropping packet");
|
|
packet->destroy(packet);
|
|
}
|
|
this->lock->unlock(this->lock);
|
|
}
|
|
|
|
/**
|
|
* Processes outbound packets
|
|
*/
|
|
static job_requeue_t process_outbound(private_ipsec_processor_t *this)
|
|
{
|
|
ipsec_policy_t *policy;
|
|
esp_packet_t *esp_packet;
|
|
ip_packet_t *packet;
|
|
ipsec_sa_t *sa;
|
|
host_t *src, *dst;
|
|
|
|
packet = (ip_packet_t*)this->outbound_queue->dequeue(this->outbound_queue);
|
|
|
|
policy = ipsec->policies->find_by_packet(ipsec->policies, packet, FALSE, 0);
|
|
if (!policy)
|
|
{
|
|
DBG2(DBG_ESP, "no matching outbound IPsec policy for %#H == %#H [%hhu]",
|
|
packet->get_source(packet), packet->get_destination(packet),
|
|
packet->get_next_header(packet));
|
|
packet->destroy(packet);
|
|
return JOB_REQUEUE_DIRECT;
|
|
}
|
|
|
|
sa = ipsec->sas->checkout_by_reqid(ipsec->sas, policy->get_reqid(policy),
|
|
FALSE);
|
|
if (!sa)
|
|
{ /* TODO-IPSEC: send an acquire to upper layer */
|
|
DBG1(DBG_ESP, "could not find an outbound IPsec SA for reqid {%u}, "
|
|
"dropping packet", policy->get_reqid(policy));
|
|
packet->destroy(packet);
|
|
policy->destroy(policy);
|
|
return JOB_REQUEUE_DIRECT;
|
|
}
|
|
src = sa->get_source(sa);
|
|
dst = sa->get_destination(sa);
|
|
esp_packet = esp_packet_create_from_payload(src->clone(src),
|
|
dst->clone(dst), packet);
|
|
if (esp_packet->encrypt(esp_packet, sa->get_esp_context(sa),
|
|
sa->get_spi(sa)) != SUCCESS)
|
|
{
|
|
ipsec->sas->checkin(ipsec->sas, sa);
|
|
esp_packet->destroy(esp_packet);
|
|
policy->destroy(policy);
|
|
return JOB_REQUEUE_DIRECT;
|
|
}
|
|
sa->update_usestats(sa, packet->get_encoding(packet).len);
|
|
ipsec->sas->checkin(ipsec->sas, sa);
|
|
policy->destroy(policy);
|
|
send_outbound(this, esp_packet);
|
|
return JOB_REQUEUE_DIRECT;
|
|
}
|
|
|
|
METHOD(ipsec_processor_t, queue_inbound, void,
|
|
private_ipsec_processor_t *this, esp_packet_t *packet)
|
|
{
|
|
this->inbound_queue->enqueue(this->inbound_queue, packet);
|
|
}
|
|
|
|
METHOD(ipsec_processor_t, queue_outbound, void,
|
|
private_ipsec_processor_t *this, ip_packet_t *packet)
|
|
{
|
|
this->outbound_queue->enqueue(this->outbound_queue, packet);
|
|
}
|
|
|
|
METHOD(ipsec_processor_t, register_inbound, void,
|
|
private_ipsec_processor_t *this, ipsec_inbound_cb_t cb, void *data)
|
|
{
|
|
this->lock->write_lock(this->lock);
|
|
this->inbound.cb = cb;
|
|
this->inbound.data = data;
|
|
this->lock->unlock(this->lock);
|
|
}
|
|
|
|
METHOD(ipsec_processor_t, unregister_inbound, void,
|
|
private_ipsec_processor_t *this, ipsec_inbound_cb_t cb)
|
|
{
|
|
this->lock->write_lock(this->lock);
|
|
if (this->inbound.cb == cb)
|
|
{
|
|
this->inbound.cb = NULL;
|
|
}
|
|
this->lock->unlock(this->lock);
|
|
}
|
|
|
|
METHOD(ipsec_processor_t, register_outbound, void,
|
|
private_ipsec_processor_t *this, ipsec_outbound_cb_t cb, void *data)
|
|
{
|
|
this->lock->write_lock(this->lock);
|
|
this->outbound.cb = cb;
|
|
this->outbound.data = data;
|
|
this->lock->unlock(this->lock);
|
|
}
|
|
|
|
METHOD(ipsec_processor_t, unregister_outbound, void,
|
|
private_ipsec_processor_t *this, ipsec_outbound_cb_t cb)
|
|
{
|
|
this->lock->write_lock(this->lock);
|
|
if (this->outbound.cb == cb)
|
|
{
|
|
this->outbound.cb = NULL;
|
|
}
|
|
this->lock->unlock(this->lock);
|
|
}
|
|
|
|
METHOD(ipsec_processor_t, destroy, void,
|
|
private_ipsec_processor_t *this)
|
|
{
|
|
this->inbound_queue->destroy_offset(this->inbound_queue,
|
|
offsetof(esp_packet_t, destroy));
|
|
this->outbound_queue->destroy_offset(this->outbound_queue,
|
|
offsetof(ip_packet_t, destroy));
|
|
this->lock->destroy(this->lock);
|
|
free(this);
|
|
}
|
|
|
|
/**
|
|
* Described in header.
|
|
*/
|
|
ipsec_processor_t *ipsec_processor_create()
|
|
{
|
|
private_ipsec_processor_t *this;
|
|
|
|
INIT(this,
|
|
.public = {
|
|
.queue_inbound = _queue_inbound,
|
|
.queue_outbound = _queue_outbound,
|
|
.register_inbound = _register_inbound,
|
|
.unregister_inbound = _unregister_inbound,
|
|
.register_outbound = _register_outbound,
|
|
.unregister_outbound = _unregister_outbound,
|
|
.destroy = _destroy,
|
|
},
|
|
.inbound_queue = blocking_queue_create(),
|
|
.outbound_queue = blocking_queue_create(),
|
|
.lock = rwlock_create(RWLOCK_TYPE_DEFAULT),
|
|
);
|
|
|
|
lib->processor->queue_job(lib->processor,
|
|
(job_t*)callback_job_create((callback_job_cb_t)process_inbound, this,
|
|
NULL, (callback_job_cancel_t)return_false));
|
|
lib->processor->queue_job(lib->processor,
|
|
(job_t*)callback_job_create((callback_job_cb_t)process_outbound, this,
|
|
NULL, (callback_job_cancel_t)return_false));
|
|
return &this->public;
|
|
}
|