diff options
Diffstat (limited to 'src/libcharon/network/receiver.c')
-rw-r--r-- | src/libcharon/network/receiver.c | 221 |
1 files changed, 151 insertions, 70 deletions
diff --git a/src/libcharon/network/receiver.c b/src/libcharon/network/receiver.c index d8cebe192..cfb1408ef 100644 --- a/src/libcharon/network/receiver.c +++ b/src/libcharon/network/receiver.c @@ -30,6 +30,8 @@ /** lifetime of a cookie, in seconds */ #define COOKIE_LIFETIME 10 +/** time we wait before disabling cookies */ +#define COOKIE_CALMDOWN_DELAY 10 /** how many times to reuse the secret */ #define COOKIE_REUSE 10000 /** default value for private_receiver_t.cookie_threshold */ @@ -96,11 +98,26 @@ struct private_receiver_t { u_int32_t cookie_threshold; /** + * timestamp of last cookie requested + */ + time_t last_cookie; + + /** * how many half open IKE_SAs per peer before blocking */ u_int32_t block_threshold; /** + * Drop IKE_SA_INIT requests if processor job load exceeds this limit + */ + u_int init_limit_job_load; + + /** + * Drop IKE_SA_INIT requests if half open IKE_SA count exceeds this limit + */ + u_int init_limit_half_open; + + /** * Delay for receiving incoming packets, to simulate larger RTT */ int receive_delay; @@ -215,55 +232,146 @@ static bool cookie_verify(private_receiver_t *this, message_t *message, } /** - * check if cookies are required, and if so, a valid cookie is included + * Check if a valid cookie found */ -static bool cookie_required(private_receiver_t *this, message_t *message) +static bool check_cookie(private_receiver_t *this, message_t *message) { - bool failed = FALSE; - - if (charon->ike_sa_manager->get_half_open_count(charon->ike_sa_manager, - NULL) >= this->cookie_threshold) + packet_t *packet; + chunk_t data; + + /* check for a cookie. We don't use our parser here and do it + * quick and dirty for performance reasons. + * we assume the cookie is the first payload (which is a MUST), and + * the cookie's SPI length is zero. */ + packet = message->get_packet(message); + data = packet->get_data(packet); + if (data.len < + IKE_HEADER_LENGTH + NOTIFY_PAYLOAD_HEADER_LENGTH + + sizeof(u_int32_t) + this->hasher->get_hash_size(this->hasher) || + *(data.ptr + 16) != NOTIFY || + *(u_int16_t*)(data.ptr + IKE_HEADER_LENGTH + 6) != htons(COOKIE)) { - /* check for a cookie. We don't use our parser here and do it - * quick and dirty for performance reasons. - * we assume the cookie is the first payload (which is a MUST), and - * the cookie's SPI length is zero. */ - packet_t *packet = message->get_packet(message); - chunk_t data = packet->get_data(packet); - if (data.len < - IKE_HEADER_LENGTH + NOTIFY_PAYLOAD_HEADER_LENGTH + - sizeof(u_int32_t) + this->hasher->get_hash_size(this->hasher) || - *(data.ptr + 16) != NOTIFY || - *(u_int16_t*)(data.ptr + IKE_HEADER_LENGTH + 6) != htons(COOKIE)) - { - /* no cookie found */ - failed = TRUE; - } - else - { - data.ptr += IKE_HEADER_LENGTH + NOTIFY_PAYLOAD_HEADER_LENGTH; - data.len = sizeof(u_int32_t) + this->hasher->get_hash_size(this->hasher); - if (!cookie_verify(this, message, data)) - { - DBG2(DBG_NET, "found cookie, but content invalid"); - failed = TRUE; - } - } + /* no cookie found */ + packet->destroy(packet); + return FALSE; + } + data.ptr += IKE_HEADER_LENGTH + NOTIFY_PAYLOAD_HEADER_LENGTH; + data.len = sizeof(u_int32_t) + this->hasher->get_hash_size(this->hasher); + if (!cookie_verify(this, message, data)) + { + DBG2(DBG_NET, "found cookie, but content invalid"); packet->destroy(packet); + return FALSE; + } + return TRUE; +} + +/** + * Check if we currently require cookies + */ +static bool cookie_required(private_receiver_t *this, + u_int half_open, u_int32_t now) +{ + if (this->cookie_threshold && half_open >= this->cookie_threshold) + { + this->last_cookie = now; + return TRUE; + } + if (now < this->last_cookie + COOKIE_CALMDOWN_DELAY) + { + /* We don't disable cookies unless we haven't seen IKE_SA_INITs + * for COOKIE_CALMDOWN_DELAY seconds. This avoids jittering between + * cookie on / cookie off states, which is problematic. Consider the + * following: A legitimiate initiator sends a IKE_SA_INIT while we + * are under a DoS attack. If we toggle our cookie behavior, + * multiple retransmits of this IKE_SA_INIT might get answered with + * and without cookies. The initiator goes on and retries with + * a cookie, but it can't know if the completing IKE_SA_INIT response + * is to its IKE_SA_INIT request with or without cookies. This is + * problematic, as the cookie is part of AUTH payload data. + */ + this->last_cookie = now; + return TRUE; } - return failed; + return FALSE; } /** - * check if peer has to many half open IKE_SAs + * Check if we should drop IKE_SA_INIT because of cookie/overload checking */ -static bool peer_too_aggressive(private_receiver_t *this, message_t *message) +static bool drop_ike_sa_init(private_receiver_t *this, message_t *message) { - if (charon->ike_sa_manager->get_half_open_count(charon->ike_sa_manager, - message->get_source(message)) >= this->block_threshold) + u_int half_open; + u_int32_t now; + + now = time_monotonic(NULL); + half_open = charon->ike_sa_manager->get_half_open_count( + charon->ike_sa_manager, NULL); + + /* check for cookies */ + if (cookie_required(this, half_open, now) && !check_cookie(this, message)) + { + chunk_t cookie; + + cookie = cookie_build(this, message, now - this->secret_offset, + chunk_from_thing(this->secret)); + DBG2(DBG_NET, "received packet from: %#H to %#H", + message->get_source(message), + message->get_destination(message)); + DBG2(DBG_NET, "sending COOKIE notify to %H", + message->get_source(message)); + send_notify(message, COOKIE, cookie); + chunk_free(&cookie); + if (++this->secret_used > COOKIE_REUSE) + { + /* create new cookie */ + DBG1(DBG_NET, "generating new cookie secret after %d uses", + this->secret_used); + memcpy(this->secret_old, this->secret, SECRET_LENGTH); + this->rng->get_bytes(this->rng, SECRET_LENGTH, this->secret); + this->secret_switch = now; + this->secret_used = 0; + } + return TRUE; + } + + /* check if peer has too many IKE_SAs half open */ + if (this->block_threshold && + charon->ike_sa_manager->get_half_open_count(charon->ike_sa_manager, + message->get_source(message)) >= this->block_threshold) + { + DBG1(DBG_NET, "ignoring IKE_SA setup from %H, " + "peer too aggressive", message->get_source(message)); + return TRUE; + } + + /* check if global half open IKE_SA limit reached */ + if (this->init_limit_half_open && + half_open >= this->init_limit_half_open) { + DBG1(DBG_NET, "ignoring IKE_SA setup from %H, half open IKE_SA " + "count of %d exceeds limit of %d", message->get_source(message), + half_open, this->init_limit_half_open); return TRUE; } + + /* check if job load acceptable */ + if (this->init_limit_job_load) + { + u_int jobs = 0, i; + + for (i = 0; i < JOB_PRIO_MAX; i++) + { + jobs += lib->processor->get_job_load(lib->processor, i); + } + if (jobs > this->init_limit_job_load) + { + DBG1(DBG_NET, "ignoring IKE_SA setup from %H, job load of %d " + "exceeds limit of %d", message->get_source(message), + jobs, this->init_limit_job_load); + return TRUE; + } + } return FALSE; } @@ -314,39 +422,8 @@ static job_requeue_t receive_packets(private_receiver_t *this) if (message->get_request(message) && message->get_exchange_type(message) == IKE_SA_INIT) { - /* check for cookies */ - if (this->cookie_threshold && cookie_required(this, message)) - { - u_int32_t now = time_monotonic(NULL); - chunk_t cookie = cookie_build(this, message, now - this->secret_offset, - chunk_from_thing(this->secret)); - - DBG2(DBG_NET, "received packet from: %#H to %#H", - message->get_source(message), - message->get_destination(message)); - DBG2(DBG_NET, "sending COOKIE notify to %H", - message->get_source(message)); - send_notify(message, COOKIE, cookie); - chunk_free(&cookie); - if (++this->secret_used > COOKIE_REUSE) - { - /* create new cookie */ - DBG1(DBG_NET, "generating new cookie secret after %d uses", - this->secret_used); - memcpy(this->secret_old, this->secret, SECRET_LENGTH); - this->rng->get_bytes(this->rng, SECRET_LENGTH, this->secret); - this->secret_switch = now; - this->secret_used = 0; - } - message->destroy(message); - return JOB_REQUEUE_DIRECT; - } - - /* check if peer has not too many IKE_SAs half open */ - if (this->block_threshold && peer_too_aggressive(this, message)) + if (drop_ike_sa_init(this, message)) { - DBG1(DBG_NET, "ignoring IKE_SA setup from %H, " - "peer too aggressive", message->get_source(message)); message->destroy(message); return JOB_REQUEUE_DIRECT; } @@ -408,6 +485,10 @@ receiver_t *receiver_create() this->block_threshold = lib->settings->get_int(lib->settings, "charon.block_threshold", BLOCK_THRESHOLD_DEFAULT); } + this->init_limit_job_load = lib->settings->get_int(lib->settings, + "charon.init_limit_job_load", 0); + this->init_limit_half_open = lib->settings->get_int(lib->settings, + "charon.init_limit_half_open", 0); this->receive_delay = lib->settings->get_int(lib->settings, "charon.receive_delay", 0); this->receive_delay_type = lib->settings->get_int(lib->settings, @@ -435,8 +516,8 @@ receiver_t *receiver_create() this->rng->get_bytes(this->rng, SECRET_LENGTH, this->secret); memcpy(this->secret_old, this->secret, SECRET_LENGTH); - this->job = callback_job_create((callback_job_cb_t)receive_packets, - this, NULL, NULL); + this->job = callback_job_create_with_prio((callback_job_cb_t)receive_packets, + this, NULL, NULL, JOB_PRIO_CRITICAL); lib->processor->queue_job(lib->processor, (job_t*)this->job); return &this->public; |