diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c index 7c05ff1e7..13dcd0e0f 100644 --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -194,6 +194,9 @@ static void ngx_http_v2_node_children_update(ngx_http_v2_node_t *node); static void ngx_http_v2_pool_cleanup(void *data); +static void ngx_http_v2_update_stream_rate(ngx_http_v2_connection_t *h2c); + + static ngx_http_v2_handler_pt ngx_http_v2_frame_states[] = { ngx_http_v2_state_data, /* NGX_HTTP_V2_DATA_FRAME */ @@ -1190,6 +1193,23 @@ ngx_http_v2_state_read_data(ngx_http_v2_connection_t *h2c, u_char *pos, return ngx_http_v2_state_complete(h2c, pos, end); } +static void +ngx_http_v2_update_stream_rate(ngx_http_v2_connection_t *h2c) +{ + ngx_http_v2_srv_conf_t *h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, + ngx_http_v2_module); + + /* dividing time by configured interval gives us the fixed window id */ + if (h2c->last_stream_record_time / h2scf->stream_rate_interval_ms + != ngx_current_msec / h2scf->stream_rate_interval_ms) + { + h2c->stream_rate = 0; + h2c->last_stream_record_time = ngx_current_msec; + } + + h2c->stream_rate++; +} + static u_char * ngx_http_v2_state_headers(ngx_http_v2_connection_t *h2c, u_char *pos, @@ -1321,6 +1341,27 @@ ngx_http_v2_state_headers(ngx_http_v2_connection_t *h2c, u_char *pos, goto rst_stream; } + /* + * track the number of streams per connection + * if stream rate exceeds the limit, send a GOAWAY frame + */ + ngx_http_v2_update_stream_rate(h2c); + + if (h2scf->max_stream_rate != 0 + && h2c->stream_rate >= (double) h2scf->max_stream_rate) { + ngx_log_error(NGX_LOG_ERR, h2c->connection->log, 0, + "frameshift detected, conn: %d, mitigation enabled: %d, stream rate: %ui", + h2c->connection->fd, h2scf->enable_frameshift_mitigation, h2c->stream_rate); + + if (h2scf->enable_frameshift_mitigation) { + /* + * finalize connection sends goaway and closes the connection + * when there are no more active streams + */ + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_ENHANCE_YOUR_CALM); + } + } + if (!h2c->settings_ack && !(h2c->state.flags & NGX_HTTP_V2_END_STREAM_FLAG) && h2scf->preread_size < NGX_HTTP_V2_DEFAULT_WINDOW) diff --git a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h index cb9014ccf..cf5510165 100644 --- a/src/http/v2/ngx_http_v2_module.h +++ b/src/http/v2/ngx_http_v2_module.h @@ -26,6 +26,9 @@ typedef struct { ngx_uint_t concurrent_pushes; size_t preread_size; ngx_uint_t streams_index_mask; + ngx_uint_t max_stream_rate; + ngx_msec_t stream_rate_interval_ms; + ngx_flag_t enable_frameshift_mitigation; } ngx_http_v2_srv_conf_t; diff --git a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h index cb9014ccf..cf5510165 100644 --- a/src/http/v2/ngx_http_v2.h +++ b/src/http/v2/ngx_http_v2.h @@ -126,6 +126,9 @@ struct ngx_http_v2_connection_s { ngx_uint_t idle; ngx_uint_t priority_limit; + ngx_uint_t stream_rate; + ngx_msec_t last_stream_record_time; + ngx_uint_t pushing; ngx_uint_t concurrent_pushes; diff --git a/src/http/v2/ngx_http_v2_module.c b/src/http/v2/ngx_http_v2_module.c index 62af9a543..90266404c 100644 --- a/src/http/v2/ngx_http_v2_module.c +++ b/src/http/v2/ngx_http_v2_module.c @@ -103,6 +103,27 @@ static ngx_command_t ngx_http_v2_commands[] = { offsetof(ngx_http_v2_srv_conf_t, concurrent_pushes), NULL }, + { ngx_string("http2_max_stream_rate"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v2_srv_conf_t, max_stream_rate), + NULL }, + + { ngx_string("http2_stream_rate_interval_ms"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_msec_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v2_srv_conf_t, stream_rate_interval_ms), + NULL }, + + { ngx_string("http2_frameshift_mitigation"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v2_srv_conf_t, enable_frameshift_mitigation), + NULL }, + { ngx_string("http2_max_requests"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_http_v2_obsolete, @@ -323,6 +344,12 @@ ngx_http_v2_create_srv_conf(ngx_conf_t *cf) h2scf->streams_index_mask = NGX_CONF_UNSET_UINT; + h2scf->max_stream_rate = NGX_CONF_UNSET_UINT; + + h2scf->stream_rate_interval_ms = NGX_CONF_UNSET_MSEC; + + h2scf->enable_frameshift_mitigation = NGX_CONF_UNSET; + return h2scf; } @@ -345,6 +372,15 @@ ngx_http_v2_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_uint_value(conf->streams_index_mask, prev->streams_index_mask, 32 - 1); + ngx_conf_merge_uint_value(conf->max_stream_rate, + prev->max_stream_rate, 0); + + ngx_conf_merge_uint_value(conf->stream_rate_interval_ms, + prev->stream_rate_interval_ms, 100.0); + + ngx_conf_merge_value(conf->enable_frameshift_mitigation, + prev->enable_frameshift_mitigation, 0); + return NGX_CONF_OK; }