/* * 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 . * * 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 #include #include #include #include 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; }