uint32_t mid;
/**
- * packet(s) for retransmission
+ * Helper to defragment the request
+ */
+ message_t *defrag;
+
+ /**
+ * Hash of the current message, or its first fragment
+ */
+ uint8_t hash[HASH_SIZE_SHA1];
+
+ /**
+ * Packet(s) for retransmissions (mid-1)
*/
array_t *packets;
/**
- * Helper to defragment the request
+ * Hash of the previously received message, or its first fragment
*/
- message_t *defrag;
+ uint8_t prev_hash[HASH_SIZE_SHA1];
} responding;
return initiate ? this->initiating.mid : this->responding.mid;
}
+/**
+ * Hash the given message with SHA-1
+ */
+static bool hash_message(message_t *msg, uint8_t hash[HASH_SIZE_SHA1])
+{
+ hasher_t *hasher;
+
+ hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA1);
+ if (!hasher ||
+ !hasher->get_hash(hasher, msg->get_packet_data(msg), hash))
+ {
+ DESTROY_IF(hasher);
+ return FALSE;
+ }
+ hasher->destroy(hasher);
+ return TRUE;
+}
+
/**
* Handle the given IKE fragment, if it is one.
*
if (msg != *defrag)
{
cache_received_message(this, msg, NULL);
+ hash_message(msg, this->responding.hash);
}
return SUCCESS;
}
if (status == NEED_MORE || status == SUCCESS)
{
cache_received_message(this, msg, fragment);
+ /* to detect retransmissions we only hash the first fragment */
+ if (fragment->get_fragment_number(fragment) == 1)
+ {
+ hash_message(msg, this->responding.hash);
+ }
}
if (status == SUCCESS)
{
return status;
}
-/**
- * Check if a message with message ID 0 looks like it is used to synchronize
- * the message IDs.
- */
-static bool looks_like_mid_sync(private_task_manager_t *this, message_t *msg,
- bool strict)
-{
- enumerator_t *enumerator;
- notify_payload_t *notify;
- payload_t *payload;
- bool found = FALSE, other = FALSE;
-
- if (msg->get_exchange_type(msg) == INFORMATIONAL)
- {
- enumerator = msg->create_payload_enumerator(msg);
- while (enumerator->enumerate(enumerator, &payload))
- {
- if (payload->get_type(payload) == PLV2_NOTIFY)
- {
- notify = (notify_payload_t*)payload;
- switch (notify->get_notify_type(notify))
- {
- case IKEV2_MESSAGE_ID_SYNC:
- case IPSEC_REPLAY_COUNTER_SYNC:
- found = TRUE;
- continue;
- default:
- break;
- }
- }
- if (strict)
- {
- other = TRUE;
- break;
- }
- }
- enumerator->destroy(enumerator);
- }
- return found && !other;
-}
-
/**
* Check whether we should reject the given request message
*/
}
return reject;
}
+
/**
* Check if a message with message ID 0 looks like it is used to synchronize
- * the message IDs and we are prepared to process it.
+ * the message IDs.
*
- * Note: This is not called if the responder never sent a message before (i.e.
- * we expect MID 0).
+ * Call this after checking the message with is_potential_mid_sync() first.
*/
static bool is_mid_sync(private_task_manager_t *this, message_t *msg)
{
- if (this->ike_sa->get_state(this->ike_sa) == IKE_ESTABLISHED &&
- this->ike_sa->supports_extension(this->ike_sa,
- EXT_IKE_MESSAGE_ID_SYNC))
+ enumerator_t *enumerator;
+ notify_payload_t *notify;
+ payload_t *payload;
+ bool found = FALSE, other = FALSE;
+
+ enumerator = msg->create_payload_enumerator(msg);
+ while (enumerator->enumerate(enumerator, &payload))
{
- return looks_like_mid_sync(this, msg, TRUE);
+ if (payload->get_type(payload) == PLV2_NOTIFY)
+ {
+ notify = (notify_payload_t*)payload;
+ switch (notify->get_notify_type(notify))
+ {
+ case IKEV2_MESSAGE_ID_SYNC:
+ case IPSEC_REPLAY_COUNTER_SYNC:
+ found = TRUE;
+ continue;
+ default:
+ break;
+ }
+ }
+ other = TRUE;
+ break;
}
- return FALSE;
+ enumerator->destroy(enumerator);
+ return found && !other;
+}
+
+/**
+ * Check if a message with message ID 0 looks like it could potentially be used
+ * to synchronize the message IDs and if we are prepared to process it.
+ *
+ * This may be called before the message body is parsed.
+ */
+static bool is_potential_mid_sync(private_task_manager_t *this, message_t *msg)
+{
+ return msg->get_exchange_type(msg) == INFORMATIONAL &&
+ this->ike_sa->get_state(this->ike_sa) == IKE_ESTABLISHED &&
+ this->ike_sa->supports_extension(this->ike_sa,
+ EXT_IKE_MESSAGE_ID_SYNC);
+}
+
+/**
+ * Check if the given message is a retransmitted request
+ */
+static status_t is_retransmit(private_task_manager_t *this, message_t *msg)
+{
+ uint8_t hash[HASH_SIZE_SHA1];
+ uint32_t mid;
+
+ mid = msg->get_message_id(msg);
+
+ if (mid == this->responding.mid)
+ {
+ return NEED_MORE;
+ }
+
+ if (mid == this->responding.mid - 1 &&
+ array_count(this->responding.packets))
+ {
+ if (!hash_message(msg, hash))
+ {
+ DBG1(DBG_IKE, "ignoring message, failed to hash message");
+ return FAILED;
+ }
+ if (memeq_const(hash, this->responding.prev_hash, sizeof(hash)))
+ {
+ return ALREADY_DONE;
+ }
+ }
+ /* this includes fragments with fragment number > 1 (we only hash the first
+ * fragment), for which RFC 7383, section 2.6.1 explicitly does not allow
+ * retransmitting responses. we don't parse/verify these messages to check,
+ * we just ignore them. also included are MID sync messages with MID 0 */
+ return INVALID_ARG;
}
METHOD(task_manager_t, process_message, status_t,
{
host_t *me, *other;
status_t status;
- uint32_t mid;
+ uint32_t mid, *expected_mid = NULL;
bool schedule_delete_job = FALSE;
+ me = msg->get_destination(msg);
+ other = msg->get_source(msg);
+ mid = msg->get_message_id(msg);
+
charon->bus->message(charon->bus, msg, TRUE, FALSE);
- status = parse_message(this, msg);
- if (status != SUCCESS)
+
+ if (msg->get_request(msg))
{
- return status;
+ bool potential_mid_sync = FALSE;
+
+ switch (is_retransmit(this, msg))
+ {
+ case ALREADY_DONE:
+ DBG1(DBG_IKE, "received retransmit of request with ID %d, "
+ "retransmitting response", mid);
+ this->ike_sa->set_statistic(this->ike_sa, STAT_INBOUND,
+ time_monotonic(NULL));
+ charon->bus->alert(charon->bus, ALERT_RETRANSMIT_RECEIVE, msg);
+ send_packets(this, this->responding.packets,
+ msg->get_destination(msg), msg->get_source(msg));
+ return SUCCESS;
+ case INVALID_ARG:
+ if (mid == 0 && is_potential_mid_sync(this, msg))
+ {
+ potential_mid_sync = TRUE;
+ }
+ else
+ {
+ expected_mid = &this->responding.mid;
+ break;
+ }
+ /* check if it's actually an MID sync message */
+ case NEED_MORE:
+ status = parse_message(this, msg);
+ if (potential_mid_sync && status == SUCCESS &&
+ !is_mid_sync(this, msg))
+ {
+ expected_mid = &this->responding.mid;
+ }
+ break;
+ case FAILED:
+ default:
+ return FAILED;
+ }
+ }
+ else
+ {
+ if (mid == this->initiating.mid)
+ {
+ status = parse_message(this, msg);
+ }
+ else
+ {
+ expected_mid = &this->initiating.mid;
+ }
}
- me = msg->get_destination(msg);
- other = msg->get_source(msg);
+ if (expected_mid)
+ {
+ DBG1(DBG_IKE, "received message ID %d, expected %d, ignored",
+ mid, *expected_mid);
+ return SUCCESS;
+ }
+ else if (status != SUCCESS)
+ {
+ return status;
+ }
/* if this IKE_SA is virgin, we check for a config */
- if (this->ike_sa->get_ike_cfg(this->ike_sa) == NULL)
+ if (!this->ike_sa->get_ike_cfg(this->ike_sa))
{
ike_cfg_t *ike_cfg;
ike_cfg = charon->backends->get_ike_cfg(charon->backends,
me, other, IKEV2);
- if (ike_cfg == NULL)
+ if (!ike_cfg)
{
/* no config found for these hosts, destroy */
DBG1(DBG_IKE, "no IKE config found for %H...%H, sending %N",
schedule_delete_job = TRUE;
}
- mid = msg->get_message_id(msg);
if (msg->get_request(msg))
{
- if (mid == this->responding.mid || (mid == 0 && is_mid_sync(this, msg)))
+ /* special handling for requests happens in is_retransmit()
+ * reject initial messages if not received in specific states,
+ * after rekeying we only expect a DELETE in an INFORMATIONAL */
+ if (reject_request(this, msg))
{
- if (reject_request(this, msg))
- {
- return FAILED;
- }
- if (!this->ike_sa->supports_extension(this->ike_sa, EXT_MOBIKE))
- { /* with MOBIKE, we do no implicit updates */
- this->ike_sa->update_hosts(this->ike_sa, me, other, mid == 1);
- }
- status = handle_fragment(this, &this->responding.defrag, msg);
- if (status != SUCCESS)
- {
- if (status == NEED_MORE)
- {
- this->ike_sa->set_statistic(this->ike_sa, STAT_INBOUND,
- time_monotonic(NULL));
- }
- return status;
- }
- charon->bus->message(charon->bus, msg, TRUE, TRUE);
- if (msg->get_exchange_type(msg) == EXCHANGE_TYPE_UNDEFINED)
- { /* ignore messages altered to EXCHANGE_TYPE_UNDEFINED */
- return SUCCESS;
- }
- switch (process_request(this, msg))
- {
- case SUCCESS:
- this->ike_sa->set_statistic(this->ike_sa, STAT_INBOUND,
- time_monotonic(NULL));
- this->responding.mid++;
- break;
- case NEED_MORE:
- break;
- default:
- flush(this);
- return DESTROY_ME;
- }
+ return FAILED;
}
- else if ((mid == this->responding.mid - 1) &&
- array_count(this->responding.packets) &&
- !(mid == 0 && looks_like_mid_sync(this, msg, FALSE)))
+ if (!this->ike_sa->supports_extension(this->ike_sa, EXT_MOBIKE))
+ { /* with MOBIKE, we do no implicit updates */
+ this->ike_sa->update_hosts(this->ike_sa, me, other, mid == 1);
+ }
+ status = handle_fragment(this, &this->responding.defrag, msg);
+ if (status != SUCCESS)
{
- status = handle_fragment(this, &this->responding.defrag, msg);
- if (status != SUCCESS)
+ if (status == NEED_MORE)
{
- if (status == NEED_MORE)
- {
- this->ike_sa->set_statistic(this->ike_sa, STAT_INBOUND,
- time_monotonic(NULL));
- }
- return status;
+ this->ike_sa->set_statistic(this->ike_sa, STAT_INBOUND,
+ time_monotonic(NULL));
}
- DBG1(DBG_IKE, "received retransmit of request with ID %d, "
- "retransmitting response", mid);
- this->ike_sa->set_statistic(this->ike_sa, STAT_INBOUND,
- time_monotonic(NULL));
- charon->bus->alert(charon->bus, ALERT_RETRANSMIT_RECEIVE, msg);
- send_packets(this, this->responding.packets,
- msg->get_destination(msg), msg->get_source(msg));
+ return status;
}
- else
+ charon->bus->message(charon->bus, msg, TRUE, TRUE);
+ if (msg->get_exchange_type(msg) == EXCHANGE_TYPE_UNDEFINED)
+ { /* ignore messages altered to EXCHANGE_TYPE_UNDEFINED */
+ return SUCCESS;
+ }
+ switch (process_request(this, msg))
{
- DBG1(DBG_IKE, "received message ID %d, expected %d, ignored",
- mid, this->responding.mid);
+ case SUCCESS:
+ this->ike_sa->set_statistic(this->ike_sa, STAT_INBOUND,
+ time_monotonic(NULL));
+ this->responding.mid++;
+ memcpy(this->responding.prev_hash, this->responding.hash,
+ sizeof(this->responding.prev_hash));
+ break;
+ case NEED_MORE:
+ break;
+ default:
+ flush(this);
+ return DESTROY_ME;
}
}
else
{
- if (mid == this->initiating.mid)
- {
- if (this->ike_sa->get_state(this->ike_sa) == IKE_CREATED ||
- this->ike_sa->get_state(this->ike_sa) == IKE_CONNECTING ||
- msg->get_exchange_type(msg) != IKE_SA_INIT)
- { /* only do updates based on verified messages (or initial ones) */
- if (!this->ike_sa->supports_extension(this->ike_sa, EXT_MOBIKE))
- { /* with MOBIKE, we do no implicit updates. we force an
- * update of the local address on IKE_SA_INIT, but never
- * for the remote address */
- this->ike_sa->update_hosts(this->ike_sa, me, NULL, mid == 0);
- this->ike_sa->update_hosts(this->ike_sa, NULL, other, FALSE);
- }
- }
- status = handle_fragment(this, &this->initiating.defrag, msg);
- if (status != SUCCESS)
- {
- if (status == NEED_MORE)
- {
- this->ike_sa->set_statistic(this->ike_sa, STAT_INBOUND,
- time_monotonic(NULL));
- }
- return status;
- }
- charon->bus->message(charon->bus, msg, TRUE, TRUE);
- if (msg->get_exchange_type(msg) == EXCHANGE_TYPE_UNDEFINED)
- { /* ignore messages altered to EXCHANGE_TYPE_UNDEFINED */
- return SUCCESS;
+ if (this->ike_sa->get_state(this->ike_sa) == IKE_CREATED ||
+ this->ike_sa->get_state(this->ike_sa) == IKE_CONNECTING ||
+ msg->get_exchange_type(msg) != IKE_SA_INIT)
+ { /* only do updates based on verified messages (or initial ones) */
+ if (!this->ike_sa->supports_extension(this->ike_sa, EXT_MOBIKE))
+ { /* with MOBIKE, we do no implicit updates. we force an
+ * update of the local address on IKE_SA_INIT, but never
+ * for the remote address */
+ this->ike_sa->update_hosts(this->ike_sa, me, NULL, mid == 0);
+ this->ike_sa->update_hosts(this->ike_sa, NULL, other, FALSE);
}
- if (process_response(this, msg) != SUCCESS)
+ }
+ status = handle_fragment(this, &this->initiating.defrag, msg);
+ if (status != SUCCESS)
+ {
+ if (status == NEED_MORE)
{
- flush(this);
- return DESTROY_ME;
+ this->ike_sa->set_statistic(this->ike_sa, STAT_INBOUND,
+ time_monotonic(NULL));
}
- this->ike_sa->set_statistic(this->ike_sa, STAT_INBOUND,
- time_monotonic(NULL));
+ return status;
}
- else
- {
- DBG1(DBG_IKE, "received message ID %d, expected %d, ignored",
- mid, this->initiating.mid);
+ charon->bus->message(charon->bus, msg, TRUE, TRUE);
+ if (msg->get_exchange_type(msg) == EXCHANGE_TYPE_UNDEFINED)
+ { /* ignore messages altered to EXCHANGE_TYPE_UNDEFINED */
return SUCCESS;
}
+ if (process_response(this, msg) != SUCCESS)
+ {
+ flush(this);
+ return DESTROY_ME;
+ }
+ this->ike_sa->set_statistic(this->ike_sa, STAT_INBOUND,
+ time_monotonic(NULL));
}
if (schedule_delete_job)