From af0e284b967d0ecff1abcdce6558ed4635e3e757 Mon Sep 17 00:00:00 2001 From: Maxim Dounin Date: Wed, 18 Sep 2019 20:28:12 +0300 Subject: [PATCH] HTTP/2: traffic-based flood detection. With this patch, all traffic over an HTTP/2 connection is counted in the h2c->total_bytes field, and payload traffic is counted in the h2c->payload_bytes field. As long as total traffic is many times larger than payload traffic, we consider this to be a flood. --- src/http/v2/ngx_http_v2.c | 21 +++++++++++++++++++-- src/http/v2/ngx_http_v2.h | 3 +++ src/http/v2/ngx_http_v2_filter_module.c | 13 ++++++++++++- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c index e3c7bbc34..640dd4ae7 100644 --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -419,6 +419,14 @@ ngx_http_v2_read_handler(ngx_event_t *rev) } while (p != end); + h2c->total_bytes += n; + + if (h2c->total_bytes / 8 > h2c->payload_bytes + 1048576) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "http2 flood detected"); + ngx_http_v2_finalize_connection(h2c, NGX_HTTP_V2_NO_ERROR); + return; + } + } while (rev->ready); if (ngx_handle_read_event(rev, 0) != NGX_OK) { @@ -963,6 +971,8 @@ ngx_http_v2_state_read_data(ngx_http_v2_connection_t *h2c, u_char *pos, stream->in_closed = h2c->state.flags & NGX_HTTP_V2_END_STREAM_FLAG; } + h2c->payload_bytes += size; + if (r->request_body) { rc = ngx_http_v2_process_request_body(r, pos, size, stream->in_closed); @@ -2909,9 +2919,9 @@ ngx_http_v2_get_frame(ngx_http_v2_connection_t *h2c, size_t length, "requested control frame is too large: %uz", length); return NULL; } +#endif frame->length = length; -#endif buf->last = ngx_http_v2_write_len_and_type(buf->pos, length, type); @@ -2938,6 +2948,8 @@ ngx_http_v2_frame_handler(ngx_http_v2_connection_t *h2c, frame->next = h2c->free_frames; h2c->free_frames = frame; + h2c->total_bytes += NGX_HTTP_V2_FRAME_HEADER_SIZE + frame->length; + return NGX_OK; } @@ -3723,7 +3735,8 @@ ngx_http_v2_construct_cookie_header(ngx_http_request_t *r) static void ngx_http_v2_run_request(ngx_http_request_t *r) { - ngx_connection_t *fc; + ngx_connection_t *fc; + ngx_http_v2_connection_t *h2c; fc = r->connection; @@ -3755,6 +3768,10 @@ ngx_http_v2_run_request(ngx_http_request_t *r) r->headers_in.chunked = 1; } + h2c = r->stream->connection; + + h2c->payload_bytes += r->request_length; + ngx_http_process_request(r); failed: diff --git a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h index 69d55d1cb..59ddf54e2 100644 --- a/src/http/v2/ngx_http_v2.h +++ b/src/http/v2/ngx_http_v2.h @@ -119,6 +119,9 @@ struct ngx_http_v2_connection_s { ngx_connection_t *connection; ngx_http_connection_t *http_connection; + off_t total_bytes; + off_t payload_bytes; + ngx_uint_t processing; ngx_uint_t frames; ngx_uint_t idle; diff --git a/src/http/v2/ngx_http_v2_filter_module.c b/src/http/v2/ngx_http_v2_filter_module.c index 96f55426d..a6e5e7d4f 100644 --- a/src/http/v2/ngx_http_v2_filter_module.c +++ b/src/http/v2/ngx_http_v2_filter_module.c @@ -1877,6 +1877,8 @@ ngx_http_v2_headers_frame_handler(ngx_http_v2_connection_t *h2c, stream->request->header_size += NGX_HTTP_V2_FRAME_HEADER_SIZE + frame->length; + h2c->payload_bytes += frame->length; + ngx_http_v2_handle_frame(stream, frame); ngx_http_v2_handle_stream(h2c, stream); @@ -1931,6 +1933,8 @@ ngx_http_v2_push_frame_handler(ngx_http_v2_connection_t *h2c, stream->request->header_size += NGX_HTTP_V2_FRAME_HEADER_SIZE + frame->length; + h2c->payload_bytes += frame->length; + ngx_http_v2_handle_frame(stream, frame); ngx_http_v2_handle_stream(h2c, stream); @@ -2024,6 +2028,8 @@ ngx_http_v2_data_frame_handler(ngx_http_v2_connection_t *h2c, stream->request->header_size += NGX_HTTP_V2_FRAME_HEADER_SIZE; + h2c->payload_bytes += frame->length; + ngx_http_v2_handle_frame(stream, frame); ngx_http_v2_handle_stream(h2c, stream); @@ -2036,12 +2042,17 @@ static ngx_inline void ngx_http_v2_handle_frame(ngx_http_v2_stream_t *stream, ngx_http_v2_out_frame_t *frame) { - ngx_http_request_t *r; + ngx_http_request_t *r; + ngx_http_v2_connection_t *h2c; r = stream->request; r->connection->sent += NGX_HTTP_V2_FRAME_HEADER_SIZE + frame->length; + h2c = stream->connection; + + h2c->total_bytes += NGX_HTTP_V2_FRAME_HEADER_SIZE + frame->length; + if (frame->fin) { stream->out_closed = 1; }