Return fatal error if flooding is detected to close session immediately

This change adds new return error code from nghttp2_session_mem_recv
and nghttp2_session_recv functions, namely NGHTTP2_ERR_FLOODED.  It is
fatal error, and is returned when flooding was detected.
This commit is contained in:
Tatsuhiro Tsujikawa 2015-09-30 22:16:03 +09:00
parent 0cb8c82125
commit d22ced77c0
6 changed files with 35 additions and 34 deletions

View File

@ -399,7 +399,16 @@ typedef enum {
* Invalid client magic (see :macro:`NGHTTP2_CLIENT_MAGIC`) was * Invalid client magic (see :macro:`NGHTTP2_CLIENT_MAGIC`) was
* received and further processing is not possible. * received and further processing is not possible.
*/ */
NGHTTP2_ERR_BAD_CLIENT_MAGIC = -903 NGHTTP2_ERR_BAD_CLIENT_MAGIC = -903,
/**
* Possible flooding by peer was detected in this HTTP/2 session.
* Flooding is measured by how many PING and SETTINGS frames with
* ACK flag set are queued for transmission. These frames are
* response for the peer initiated frames, and peer can cause memory
* exhaustion on server side to send these frames forever and does
* not read network.
*/
NGHTTP2_ERR_FLOODED = -904
} nghttp2_error; } nghttp2_error;
/** /**
@ -2366,6 +2375,9 @@ NGHTTP2_EXTERN ssize_t nghttp2_session_mem_send(nghttp2_session *session,
* when |session| was configured as server and * when |session| was configured as server and
* `nghttp2_option_set_no_recv_client_magic()` is not used with * `nghttp2_option_set_no_recv_client_magic()` is not used with
* nonzero value. * nonzero value.
* :enum:`NGHTTP2_ERR_FLOODED`
* Flooding was detected in this HTTP/2 session, and it must be
* closed. This is most likely caused by misbehaviour of peer.
*/ */
NGHTTP2_EXTERN int nghttp2_session_recv(nghttp2_session *session); NGHTTP2_EXTERN int nghttp2_session_recv(nghttp2_session *session);
@ -2402,6 +2414,9 @@ NGHTTP2_EXTERN int nghttp2_session_recv(nghttp2_session *session);
* when |session| was configured as server and * when |session| was configured as server and
* `nghttp2_option_set_no_recv_client_magic()` is not used with * `nghttp2_option_set_no_recv_client_magic()` is not used with
* nonzero value. * nonzero value.
* :enum:`NGHTTP2_ERR_FLOODED`
* Flooding was detected in this HTTP/2 session, and it must be
* closed. This is most likely caused by misbehaviour of peer.
*/ */
NGHTTP2_EXTERN ssize_t nghttp2_session_mem_recv(nghttp2_session *session, NGHTTP2_EXTERN ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
const uint8_t *in, const uint8_t *in,

View File

@ -294,6 +294,9 @@ const char *nghttp2_strerror(int error_code) {
return "The user callback function failed"; return "The user callback function failed";
case NGHTTP2_ERR_BAD_CLIENT_MAGIC: case NGHTTP2_ERR_BAD_CLIENT_MAGIC:
return "Received bad client magic byte string"; return "Received bad client magic byte string";
case NGHTTP2_ERR_FLOODED:
return "Flooding was detected in this HTTP/2 session, and it must be "
"closed";
default: default:
return "Unknown error code"; return "Unknown error code";
} }

View File

@ -52,8 +52,7 @@ typedef enum {
* Invalid HTTP header field was received but it can be treated as * Invalid HTTP header field was received but it can be treated as
* if it was not received because of compatibility reasons. * if it was not received because of compatibility reasons.
*/ */
NGHTTP2_ERR_IGN_HTTP_HEADER = -105, NGHTTP2_ERR_IGN_HTTP_HEADER = -105
NGHTTP2_ERR_FLOODING_DETECTED = -106
} nghttp2_internal_error; } nghttp2_internal_error;
#endif /* NGHTTP2_INT_H */ #endif /* NGHTTP2_INT_H */

View File

@ -141,15 +141,6 @@ static int session_detect_idle_stream(nghttp2_session *session,
return 0; return 0;
} }
static void session_on_flooding_detected(nghttp2_session *session) {
/* If we found flooding, we might not send GOAWAY since peer might
not read at all. So we just set these flags to pretend that
GOAWAY is sent, so that nghttp2_session_want_read() and
nghttp2_session_want_write() return 0. */
session->goaway_flags |= NGHTTP2_GOAWAY_TERM_ON_SEND |
NGHTTP2_GOAWAY_TERM_SENT | NGHTTP2_GOAWAY_SENT;
}
static int session_terminate_session(nghttp2_session *session, static int session_terminate_session(nghttp2_session *session,
int32_t last_stream_id, int32_t last_stream_id,
uint32_t error_code, const char *reason) { uint32_t error_code, const char *reason) {
@ -5930,8 +5921,7 @@ int nghttp2_session_add_ping(nghttp2_session *session, uint8_t flags,
if ((flags & NGHTTP2_FLAG_ACK) && if ((flags & NGHTTP2_FLAG_ACK) &&
session->obq_flood_counter_ >= NGHTTP2_MAX_OBQ_FLOOD_ITEM) { session->obq_flood_counter_ >= NGHTTP2_MAX_OBQ_FLOOD_ITEM) {
session_on_flooding_detected(session); return NGHTTP2_ERR_FLOODED;
return NGHTTP2_ERR_FLOODING_DETECTED;
} }
item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item)); item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
@ -6081,8 +6071,7 @@ int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags,
} }
if (session->obq_flood_counter_ >= NGHTTP2_MAX_OBQ_FLOOD_ITEM) { if (session->obq_flood_counter_ >= NGHTTP2_MAX_OBQ_FLOOD_ITEM) {
session_on_flooding_detected(session); return NGHTTP2_ERR_FLOODED;
return NGHTTP2_ERR_FLOODING_DETECTED;
} }
} }

View File

@ -367,7 +367,7 @@ int nghttp2_session_add_rst_stream(nghttp2_session *session, int32_t stream_id,
* *
* NGHTTP2_ERR_NOMEM * NGHTTP2_ERR_NOMEM
* Out of memory. * Out of memory.
* NGHTTP2_ERR_FLOODING_DETECTED * NGHTTP2_ERR_FLOODED
* There are too many items in outbound queue; this only happens * There are too many items in outbound queue; this only happens
* if NGHTTP2_FLAG_ACK is set in |flags| * if NGHTTP2_FLAG_ACK is set in |flags|
*/ */
@ -417,7 +417,7 @@ int nghttp2_session_add_window_update(nghttp2_session *session, uint8_t flags,
* *
* NGHTTP2_ERR_NOMEM * NGHTTP2_ERR_NOMEM
* Out of memory. * Out of memory.
* NGHTTP2_ERR_FLOODING_DETECTED * NGHTTP2_ERR_FLOODED
* There are too many items in outbound queue; this only happens * There are too many items in outbound queue; this only happens
* if NGHTTP2_FLAG_ACK is set in |flags| * if NGHTTP2_FLAG_ACK is set in |flags|
*/ */
@ -643,6 +643,9 @@ int nghttp2_session_on_rst_stream_received(nghttp2_session *session,
* Out of memory * Out of memory
* NGHTTP2_ERR_CALLBACK_FAILURE * NGHTTP2_ERR_CALLBACK_FAILURE
* The read_callback failed * The read_callback failed
* NGHTTP2_ERR_FLOODED
* There are too many items in outbound queue, and this is most
* likely caused by misbehaviour of peer.
*/ */
int nghttp2_session_on_settings_received(nghttp2_session *session, int nghttp2_session_on_settings_received(nghttp2_session *session,
nghttp2_frame *frame, int noack); nghttp2_frame *frame, int noack);
@ -676,6 +679,9 @@ int nghttp2_session_on_push_promise_received(nghttp2_session *session,
* Out of memory. * Out of memory.
* NGHTTP2_ERR_CALLBACK_FAILURE * NGHTTP2_ERR_CALLBACK_FAILURE
* The callback function failed. * The callback function failed.
* NGHTTP2_ERR_FLOODED
* There are too many items in outbound queue, and this is most
* likely caused by misbehaviour of peer.
*/ */
int nghttp2_session_on_ping_received(nghttp2_session *session, int nghttp2_session_on_ping_received(nghttp2_session *session,
nghttp2_frame *frame); nghttp2_frame *frame);

View File

@ -8097,6 +8097,7 @@ void test_nghttp2_session_flooding(void) {
nghttp2_buf *buf; nghttp2_buf *buf;
nghttp2_frame frame; nghttp2_frame frame;
nghttp2_mem *mem; nghttp2_mem *mem;
size_t i;
mem = nghttp2_mem_default(); mem = nghttp2_mem_default();
@ -8113,21 +8114,15 @@ void test_nghttp2_session_flooding(void) {
buf = &bufs.head->buf; buf = &bufs.head->buf;
for (int i = 0; i < NGHTTP2_MAX_OBQ_FLOOD_ITEM; ++i) { for (i = 0; i < NGHTTP2_MAX_OBQ_FLOOD_ITEM; ++i) {
CU_ASSERT( CU_ASSERT(
(ssize_t)nghttp2_buf_len(buf) == (ssize_t)nghttp2_buf_len(buf) ==
nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf))); nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)));
} }
CU_ASSERT(1 == nghttp2_session_want_read(session)); CU_ASSERT(NGHTTP2_ERR_FLOODED ==
CU_ASSERT(1 == nghttp2_session_want_write(session));
CU_ASSERT((ssize_t)nghttp2_buf_len(buf) ==
nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf))); nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)));
CU_ASSERT(0 == nghttp2_session_want_read(session));
CU_ASSERT(0 == nghttp2_session_want_write(session));
nghttp2_session_del(session); nghttp2_session_del(session);
/* SETTINGS ACK */ /* SETTINGS ACK */
@ -8141,21 +8136,15 @@ void test_nghttp2_session_flooding(void) {
buf = &bufs.head->buf; buf = &bufs.head->buf;
for (int i = 0; i < NGHTTP2_MAX_OBQ_FLOOD_ITEM; ++i) { for (i = 0; i < NGHTTP2_MAX_OBQ_FLOOD_ITEM; ++i) {
CU_ASSERT( CU_ASSERT(
(ssize_t)nghttp2_buf_len(buf) == (ssize_t)nghttp2_buf_len(buf) ==
nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf))); nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)));
} }
CU_ASSERT(1 == nghttp2_session_want_read(session)); CU_ASSERT(NGHTTP2_ERR_FLOODED ==
CU_ASSERT(1 == nghttp2_session_want_write(session));
CU_ASSERT((ssize_t)nghttp2_buf_len(buf) ==
nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf))); nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)));
CU_ASSERT(0 == nghttp2_session_want_read(session));
CU_ASSERT(0 == nghttp2_session_want_write(session));
nghttp2_session_del(session); nghttp2_session_del(session);
nghttp2_bufs_free(&bufs); nghttp2_bufs_free(&bufs);
} }