diff --git a/src/libcharon/encoding/message.c b/src/libcharon/encoding/message.c index 31f8c14aa0..2fdbeb6076 100644 --- a/src/libcharon/encoding/message.c +++ b/src/libcharon/encoding/message.c @@ -23,6 +23,7 @@ #include "message.h" #include +#include #include #include #include @@ -66,6 +67,11 @@ */ #define MAX_NAT_D_PAYLOADS 10 +/** + * Maximum packet size for fragmented packets (same as in sockets) + */ +#define MAX_PACKET 10000 + /** * A payload rule defines the rules for a payload * in a specific message rule. It defines if and how @@ -804,6 +810,29 @@ static message_rule_t message_rules[] = { #endif /* USE_IKEV1 */ }; +/** + * Data for fragment reassembly. + */ +typedef struct { + + /** + * For IKEv1 the number of the last fragment (in case we receive them out + * of order), since the first one starts with 1 this defines the number of + * fragments we expect. + */ + u_int8_t last; + + /** + * Length of all currently received fragments. + */ + size_t len; + + /** + * Maximum length of a fragmented packet. + */ + size_t max_packet; + +} fragment_data_t; typedef struct private_message_t private_message_t; @@ -879,6 +908,7 @@ struct private_message_t { /** * Array of generated fragments (if any), as packet_t*. + * If defragmenting (frag != NULL) this contains fragment_t* */ array_t *fragments; @@ -896,8 +926,41 @@ struct private_message_t { * The message rule for this message instance */ message_rule_t *rule; + + /** + * Data used to reassemble a fragmented message + */ + fragment_data_t *frag; }; +/** + * A single fragment within a fragmented message + */ +typedef struct { + + /** fragment number */ + u_int8_t num; + + /** fragment data */ + chunk_t data; + +} fragment_t; + +static void fragment_destroy(fragment_t *this) +{ + chunk_free(&this->data); + free(this); +} + +static void reset_defrag(private_message_t *this, u_int16_t id) +{ + array_destroy_function(this->fragments, (void*)fragment_destroy, NULL); + this->fragments = NULL; + this->message_id = id; + this->frag->last = 0; + this->frag->len = 0; +} + /** * Get the message rule that applies to this message */ @@ -1877,7 +1940,7 @@ METHOD(message_t, parse_header, status_t, this->first_payload = ike_header->payload_interface.get_next_type( &ike_header->payload_interface); if (this->first_payload == PLV1_FRAGMENT && this->is_encrypted) - { /* racoon sets the encryted bit when sending a fragment, but these + { /* racoon sets the encrypted bit when sending a fragment, but these * messages are really not encrypted */ this->is_encrypted = FALSE; } @@ -2307,14 +2370,121 @@ METHOD(message_t, parse_body, status_t, return SUCCESS; } +METHOD(message_t, add_fragment, status_t, + private_message_t *this, message_t *message) +{ + fragment_payload_t *payload; + fragment_t *fragment; + bio_writer_t *writer; + host_t *src, *dst; + chunk_t data; + u_int8_t num; + int i, insert_at = -1; + + if (!this->frag) + { + return INVALID_STATE; + } + payload = (fragment_payload_t*)message->get_payload(message, PLV1_FRAGMENT); + if (!payload) + { + return INVALID_ARG; + } + if (!this->fragments || this->message_id != payload->get_id(payload)) + { + reset_defrag(this, payload->get_id(payload)); + /* we don't know the total number of fragments */ + this->fragments = array_create(0, 0); + } + + num = payload->get_number(payload); + if (!this->frag->last && payload->is_last(payload)) + { + this->frag->last = num; + } + + for (i = 0; i < array_count(this->fragments); i++) + { + array_get(this->fragments, i, &fragment); + if (fragment->num == num) + { + /* ignore a duplicate fragment */ + DBG1(DBG_ENC, "received duplicate fragment #%hhu", num); + return NEED_MORE; + } + if (fragment->num > num) + { + insert_at = i; + break; + } + } + data = payload->get_data(payload); + this->frag->len += data.len; + if (this->frag->len > this->frag->max_packet) + { + DBG1(DBG_ENC, "fragmented IKE message is too large"); + reset_defrag(this, 0); + return FAILED; + } + INIT(fragment, + .num = num, + .data = chunk_clone(data), + ); + array_insert(this->fragments, insert_at, fragment); + + if (this->frag->last < array_count(this->fragments)) + { + /* there are some fragments missing */ + DBG1(DBG_ENC, "received fragment #%hhu, waiting for complete IKE " + "message", num); + return NEED_MORE; + } + + writer = bio_writer_create(this->frag->len); + DBG1(DBG_ENC, "received fragment #%hhu, reassembling fragmented IKE " + "message", num); + + for (i = 0; i < array_count(this->fragments); i++) + { + array_get(this->fragments, i, &fragment); + writer->write_data(writer, fragment->data); + } + src = message->get_source(message); + dst = message->get_destination(message); + this->packet->set_source(this->packet, src->clone(src)); + this->packet->set_destination(this->packet, dst->clone(dst)); + this->packet->set_data(this->packet, writer->extract_buf(writer)); + writer->destroy(writer); + this->parser->destroy(this->parser); + this->parser = parser_create(this->packet->get_data(this->packet)); + reset_defrag(this, 0); + free(this->frag); + this->frag = NULL; + + if (parse_header(this) != SUCCESS) + { + DBG1(DBG_IKE, "failed to parse header of reassembled IKE message"); + return FAILED; + } + return SUCCESS; +} + METHOD(message_t, destroy, void, private_message_t *this) { DESTROY_IF(this->ike_sa_id); this->payloads->destroy_offset(this->payloads, offsetof(payload_t, destroy)); - array_destroy_offset(this->fragments, offsetof(packet_t, destroy)); this->packet->destroy(this->packet); this->parser->destroy(this->parser); + if (this->frag) + { + reset_defrag(this, 0); + free(this->frag); + } + else + { + array_destroy_offset(this->fragments, offsetof(packet_t, destroy)); + } free(this); } @@ -2352,6 +2522,7 @@ message_t *message_create_from_packet(packet_t *packet) .is_encoded = _is_encoded, .is_fragmented = _is_fragmented, .fragment = _fragment, + .add_fragment = _add_fragment, .set_source = _set_source, .get_source = _get_source, .set_destination = _set_destination, @@ -2390,3 +2561,24 @@ message_t *message_create(int major, int minor) return this; } + +/* + * Described in header. + */ +message_t *message_create_defrag(message_t *fragment) +{ + private_message_t *this; + + if (!fragment->get_payload(fragment, PLV1_FRAGMENT)) + { + return NULL; + } + this = (private_message_t*)message_create( + fragment->get_major_version(fragment), + fragment->get_minor_version(fragment)); + INIT(this->frag, + .max_packet = lib->settings->get_int(lib->settings, + "%s.max_packet", MAX_PACKET, lib->ns), + ); + return &this->public; +} diff --git a/src/libcharon/encoding/message.h b/src/libcharon/encoding/message.h index e8db51ce5f..69a8e93b1f 100644 --- a/src/libcharon/encoding/message.h +++ b/src/libcharon/encoding/message.h @@ -296,6 +296,26 @@ struct message_t { */ bool (*is_fragmented)(message_t *this); + /** + * Add a fragment to the message if it was created with + * message_create_defrag(). + * + * Once the message is completed it should be processed like any other + * inbound message. + * + * @note Only supported for IKEv1 at the moment. + * + * @param fragment fragment to add + * @return + * - SUCCESS if message was reassembled + * - NEED_MORE if not all fragments have yet been received + * - FAILED if reassembling failed + * - INVALID_ARG if fragment is invalid for some reason + * - INVALID_STATE if message was not created using + * message_create_defrag() + */ + status_t (*add_fragment)(message_t *this, message_t *fragment); + /** * Gets the source host informations. * @@ -419,4 +439,14 @@ message_t *message_create_from_packet(packet_t *packet); */ message_t *message_create(int major, int minor); +/** + * Creates a message_t object that is used to reassemble fragmented messages. + * + * Use add_fragment() to add fragments. + * + * @param fragment initial fragment (is not added) + * @return message_t object, NULL if fragment is not actually one + */ +message_t *message_create_defrag(message_t *fragment); + #endif /** MESSAGE_H_ @}*/ diff --git a/src/libcharon/sa/ikev1/task_manager_v1.c b/src/libcharon/sa/ikev1/task_manager_v1.c index 498d6c47ab..0f8e8bc6dd 100644 --- a/src/libcharon/sa/ikev1/task_manager_v1.c +++ b/src/libcharon/sa/ikev1/task_manager_v1.c @@ -38,8 +38,6 @@ #include #include -#include -#include #include /** @@ -51,11 +49,6 @@ */ #define MAX_OLD_HASHES 2 -/** - * Maximum packet size for fragmented packets (same as in sockets) - */ -#define MAX_PACKET 10000 - /** * First sequence number of responding packets. * @@ -177,38 +170,9 @@ struct private_task_manager_t { } initiating; /** - * Data used to reassemble a fragmented message + * Message we are currently defragmenting, if any (only one at a time) */ - struct { - - /** - * Fragment ID (currently only one is supported at a time) - */ - u_int16_t id; - - /** - * The number of the last fragment (in case we receive the fragments out - * of order), since the first starts with 1 this defines the number of - * fragments we expect - */ - u_int8_t last; - - /** - * List of fragments (fragment_t*) - */ - linked_list_t *list; - - /** - * Length of all currently received fragments - */ - size_t len; - - /** - * Maximum length of a fragmented packet - */ - size_t max_packet; - - } frag; + message_t *defrag; /** * List of queued tasks not yet in action @@ -256,34 +220,6 @@ struct private_task_manager_t { u_int32_t dpd_recv; }; -/** - * A single fragment within a fragmented message - */ -typedef struct { - - /** fragment number */ - u_int8_t num; - - /** fragment data */ - chunk_t data; - -} fragment_t; - -static void fragment_destroy(fragment_t *this) -{ - chunk_free(&this->data); - free(this); -} - -static void clear_fragments(private_task_manager_t *this, u_int16_t id) -{ - DESTROY_FUNCTION_IF(this->frag.list, (void*)fragment_destroy); - this->frag.list = NULL; - this->frag.last = 0; - this->frag.len = 0; - this->frag.id = id; -} - /** * Reset retransmission packet list */ @@ -1182,107 +1118,23 @@ static status_t process_response(private_task_manager_t *this, static status_t handle_fragment(private_task_manager_t *this, message_t *msg) { - fragment_payload_t *payload; - enumerator_t *enumerator; - fragment_t *fragment; - status_t status = SUCCESS; - chunk_t data; - u_int8_t num; + status_t status; - payload = (fragment_payload_t*)msg->get_payload(msg, PLV1_FRAGMENT); - if (!payload) + if (!this->defrag) { - return FAILED; - } - - if (!this->frag.list || this->frag.id != payload->get_id(payload)) - { - clear_fragments(this, payload->get_id(payload)); - this->frag.list = linked_list_create(); - } - - num = payload->get_number(payload); - if (!this->frag.last && payload->is_last(payload)) - { - this->frag.last = num; - } - - enumerator = this->frag.list->create_enumerator(this->frag.list); - while (enumerator->enumerate(enumerator, &fragment)) - { - if (fragment->num == num) - { /* ignore a duplicate fragment */ - DBG1(DBG_IKE, "received duplicate fragment #%hhu", num); - enumerator->destroy(enumerator); - return NEED_MORE; - } - if (fragment->num > num) + this->defrag = message_create_defrag(msg); + if (!this->defrag) { - break; + return FAILED; } } - - data = payload->get_data(payload); - this->frag.len += data.len; - if (this->frag.len > this->frag.max_packet) + status = this->defrag->add_fragment(this->defrag, msg); + if (status == SUCCESS) { - DBG1(DBG_IKE, "fragmented IKE message is too large"); - enumerator->destroy(enumerator); - clear_fragments(this, 0); - return FAILED; - } - - INIT(fragment, - .num = num, - .data = chunk_clone(data), - ); - - this->frag.list->insert_before(this->frag.list, enumerator, fragment); - enumerator->destroy(enumerator); - - if (this->frag.list->get_count(this->frag.list) == this->frag.last) - { - message_t *message; - packet_t *pkt; - host_t *src, *dst; - bio_writer_t *writer; - - writer = bio_writer_create(this->frag.len); - DBG1(DBG_IKE, "received fragment #%hhu, reassembling fragmented IKE " - "message", num); - enumerator = this->frag.list->create_enumerator(this->frag.list); - while (enumerator->enumerate(enumerator, &fragment)) - { - writer->write_data(writer, fragment->data); - } - enumerator->destroy(enumerator); - - src = msg->get_source(msg); - dst = msg->get_destination(msg); - pkt = packet_create_from_data(src->clone(src), dst->clone(dst), - writer->extract_buf(writer)); - writer->destroy(writer); - - message = message_create_from_packet(pkt); - if (message->parse_header(message) != SUCCESS) - { - DBG1(DBG_IKE, "failed to parse header of reassembled IKE message"); - message->destroy(message); - status = FAILED; - } - else - { - lib->processor->queue_job(lib->processor, - (job_t*)process_message_job_create(message)); - status = NEED_MORE; - - } - clear_fragments(this, 0); - } - else - { /* there are some fragments missing */ - DBG1(DBG_IKE, "received fragment #%hhu, waiting for complete IKE " - "message", num); + lib->processor->queue_job(lib->processor, + (job_t*)process_message_job_create(this->defrag)); + this->defrag = NULL; + /* do not process the last fragment */ status = NEED_MORE; } return status; @@ -1912,7 +1764,8 @@ METHOD(task_manager_t, reset, void, this->initiating.seqnr = 0; this->initiating.retransmitted = 0; this->initiating.type = EXCHANGE_TYPE_UNDEFINED; - clear_fragments(this, 0); + DESTROY_IF(this->defrag); + this->defrag = NULL; if (initiate != UINT_MAX) { this->dpd_send = initiate; @@ -1963,7 +1816,7 @@ METHOD(task_manager_t, destroy, void, this->active_tasks->destroy(this->active_tasks); this->queued_tasks->destroy(this->queued_tasks); this->passive_tasks->destroy(this->passive_tasks); - clear_fragments(this, 0); + DESTROY_IF(this->defrag); DESTROY_IF(this->queued); clear_packets(this->responding.packets); @@ -2014,10 +1867,6 @@ task_manager_v1_t *task_manager_v1_create(ike_sa_t *ike_sa) .responding = { .seqnr = RESPONDING_SEQ, }, - .frag = { - .max_packet = lib->settings->get_int(lib->settings, - "%s.max_packet", MAX_PACKET, lib->ns), - }, .ike_sa = ike_sa, .rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK), .queued_tasks = linked_list_create(),