# HG changeset patch # User Roman Arutyunyan # Date 1633602162 -10800 # Node ID 72b304f6207cb6483974d02543dda6b9959a79a4 # Parent a09bcc304eef60b114b0c26ba074eb1043954308 HTTP/3: traffic-based flood detection. With this patch, all traffic over HTTP/3 bidi and uni streams is counted in the h3c->total_bytes field, and payload traffic is counted in the h3c->payload_bytes field. As long as total traffic is many times larger than payload traffic, we consider this to be a flood. Request header traffic is counted as if all fields are literal. Response header traffic is counted as is. diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c --- a/src/http/v3/ngx_http_v3.c +++ b/src/http/v3/ngx_http_v3.c @@ -86,3 +86,22 @@ ngx_http_v3_cleanup_session(void *data) ngx_del_timer(&h3c->keepalive); } } + + +ngx_int_t +ngx_http_v3_check_flood(ngx_connection_t *c) +{ + ngx_http_v3_session_t *h3c; + + h3c = ngx_http_v3_get_session(c); + + if (h3c->total_bytes / 8 > h3c->payload_bytes + 1048576) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "http3 flood detected"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, + "HTTP/3 flood detected"); + return NGX_ERROR; + } + + return NGX_OK; +} diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -128,6 +128,9 @@ struct ngx_http_v3_session_s { uint64_t max_push_id; uint64_t goaway_push_id; + off_t total_bytes; + off_t payload_bytes; + ngx_uint_t goaway; /* unsigned goaway:1; */ ngx_connection_t *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM]; @@ -136,6 +139,7 @@ struct ngx_http_v3_session_s { void ngx_http_v3_init(ngx_connection_t *c); ngx_int_t ngx_http_v3_init_session(ngx_connection_t *c); +ngx_int_t ngx_http_v3_check_flood(ngx_connection_t *c); ngx_int_t ngx_http_v3_read_request_body(ngx_http_request_t *r); ngx_int_t ngx_http_v3_read_unbuffered_request_body(ngx_http_request_t *r); diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c --- a/src/http/v3/ngx_http_v3_filter_module.c +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -101,6 +101,7 @@ ngx_http_v3_header_filter(ngx_http_reque ngx_list_part_t *part; ngx_table_elt_t *header; ngx_connection_t *c; + ngx_http_v3_session_t *h3c; ngx_http_v3_filter_ctx_t *ctx; ngx_http_core_loc_conf_t *clcf; ngx_http_core_srv_conf_t *cscf; @@ -120,6 +121,8 @@ ngx_http_v3_header_filter(ngx_http_reque return NGX_OK; } + h3c = ngx_http_v3_get_session(r->connection); + if (r->method == NGX_HTTP_HEAD) { r->header_only = 1; } @@ -531,6 +534,8 @@ ngx_http_v3_header_filter(ngx_http_reque n = b->last - b->pos; + h3c->payload_bytes += n; + len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_HEADERS) + ngx_http_v3_encode_varlen_int(NULL, n); @@ -571,6 +576,9 @@ ngx_http_v3_header_filter(ngx_http_reque b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, r->headers_out.content_length_n); + h3c->payload_bytes += r->headers_out.content_length_n; + h3c->total_bytes += r->headers_out.content_length_n; + cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NGX_ERROR; @@ -590,6 +598,10 @@ ngx_http_v3_header_filter(ngx_http_reque ngx_http_set_ctx(r, ctx, ngx_http_v3_filter_module); } + for (cl = out; cl; cl = cl->next) { + h3c->total_bytes += cl->buf->last - cl->buf->pos; + } + return ngx_http_write_filter(r, out); } @@ -1096,9 +1108,12 @@ static ngx_chain_t * ngx_http_v3_create_push_promise(ngx_http_request_t *r, ngx_str_t *path, uint64_t push_id) { - size_t n, len; - ngx_buf_t *b; - ngx_chain_t *hl, *cl; + size_t n, len; + ngx_buf_t *b; + ngx_chain_t *hl, *cl; + ngx_http_v3_session_t *h3c; + + h3c = ngx_http_v3_get_session(r->connection); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http3 create push promise id:%uL", push_id); @@ -1233,6 +1248,8 @@ ngx_http_v3_create_push_promise(ngx_http n = b->last - b->pos; + h3c->payload_bytes += n; + len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_PUSH_PROMISE) + ngx_http_v3_encode_varlen_int(NULL, n); @@ -1265,6 +1282,7 @@ ngx_http_v3_body_filter(ngx_http_request ngx_int_t rc; ngx_buf_t *b; ngx_chain_t *out, *cl, *tl, **ll; + ngx_http_v3_session_t *h3c; ngx_http_v3_filter_ctx_t *ctx; if (in == NULL) { @@ -1276,6 +1294,8 @@ ngx_http_v3_body_filter(ngx_http_request return ngx_http_next_body_filter(r, in); } + h3c = ngx_http_v3_get_session(r->connection); + out = NULL; ll = &out; @@ -1340,6 +1360,8 @@ ngx_http_v3_body_filter(ngx_http_request tl->next = out; out = tl; + + h3c->payload_bytes += size; } if (cl->buf->last_buf) { @@ -1356,6 +1378,10 @@ ngx_http_v3_body_filter(ngx_http_request *ll = NULL; } + for (cl = out; cl; cl = cl->next) { + h3c->total_bytes += cl->buf->last - cl->buf->pos; + } + rc = ngx_http_next_body_filter(r, out); ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out, @@ -1369,13 +1395,16 @@ static ngx_chain_t * ngx_http_v3_create_trailers(ngx_http_request_t *r, ngx_http_v3_filter_ctx_t *ctx) { - size_t len, n; - u_char *p; - ngx_buf_t *b; - ngx_uint_t i; - ngx_chain_t *cl, *hl; - ngx_list_part_t *part; - ngx_table_elt_t *header; + size_t len, n; + u_char *p; + ngx_buf_t *b; + ngx_uint_t i; + ngx_chain_t *cl, *hl; + ngx_list_part_t *part; + ngx_table_elt_t *header; + ngx_http_v3_session_t *h3c; + + h3c = ngx_http_v3_get_session(r->connection); len = 0; @@ -1461,6 +1490,8 @@ ngx_http_v3_create_trailers(ngx_http_req n = b->last - b->pos; + h3c->payload_bytes += n; + hl = ngx_chain_get_free_buf(r->pool, &ctx->free); if (hl == NULL) { return NULL; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -218,6 +218,7 @@ ngx_http_v3_process_request(ngx_event_t ngx_int_t rc; ngx_connection_t *c; ngx_http_request_t *r; + ngx_http_v3_session_t *h3c; ngx_http_core_srv_conf_t *cscf; ngx_http_v3_parse_headers_t *st; @@ -233,6 +234,8 @@ ngx_http_v3_process_request(ngx_event_t return; } + h3c = ngx_http_v3_get_session(c); + st = &r->v3_parse->headers; b = r->header_in; @@ -298,6 +301,12 @@ ngx_http_v3_process_request(ngx_event_t } r->request_length += b->pos - p; + h3c->total_bytes += b->pos - p; + + if (ngx_http_v3_check_flood(c) != NGX_OK) { + ngx_http_close_request(r, NGX_HTTP_CLOSE); + break; + } if (rc == NGX_BUSY) { if (rev->error) { @@ -318,6 +327,10 @@ ngx_http_v3_process_request(ngx_event_t /* rc == NGX_OK || rc == NGX_DONE */ + h3c->payload_bytes += ngx_http_v3_encode_field_l(NULL, + &st->field_rep.field.name, + &st->field_rep.field.value); + if (ngx_http_v3_process_header(r, &st->field_rep.field.name, &st->field_rep.field.value) != NGX_OK) @@ -1080,6 +1093,7 @@ ngx_http_v3_request_body_filter(ngx_http ngx_buf_t *b; ngx_uint_t last; ngx_chain_t *cl, *out, *tl, **ll; + ngx_http_v3_session_t *h3c; ngx_http_request_body_t *rb; ngx_http_core_loc_conf_t *clcf; ngx_http_core_srv_conf_t *cscf; @@ -1088,6 +1102,8 @@ ngx_http_v3_request_body_filter(ngx_http rb = r->request_body; st = &r->v3_parse->body; + h3c = ngx_http_v3_get_session(r->connection); + if (rb->rest == -1) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, @@ -1135,6 +1151,11 @@ ngx_http_v3_request_body_filter(ngx_http rc = ngx_http_v3_parse_data(r->connection, st, cl->buf); r->request_length += cl->buf->pos - p; + h3c->total_bytes += cl->buf->pos - p; + + if (ngx_http_v3_check_flood(r->connection) != NGX_OK) { + return NGX_HTTP_CLOSE; + } if (rc == NGX_AGAIN) { continue; @@ -1178,6 +1199,8 @@ ngx_http_v3_request_body_filter(ngx_http { rb->received += st->length; r->request_length += st->length; + h3c->total_bytes += st->length; + h3c->payload_bytes += st->length; if (st->length < 8) { @@ -1222,12 +1245,16 @@ ngx_http_v3_request_body_filter(ngx_http cl->buf->pos += (size_t) st->length; rb->received += st->length; r->request_length += st->length; + h3c->total_bytes += st->length; + h3c->payload_bytes += st->length; st->length = 0; } else { st->length -= size; rb->received += size; r->request_length += size; + h3c->total_bytes += size; + h3c->payload_bytes += size; cl->buf->pos = cl->buf->last; } diff --git a/src/http/v3/ngx_http_v3_streams.c b/src/http/v3/ngx_http_v3_streams.c --- a/src/http/v3/ngx_http_v3_streams.c +++ b/src/http/v3/ngx_http_v3_streams.c @@ -171,6 +171,7 @@ ngx_http_v3_uni_read_handler(ngx_event_t ngx_buf_t b; ngx_int_t rc; ngx_connection_t *c; + ngx_http_v3_session_t *h3c; ngx_http_v3_uni_stream_t *us; c = rev->data; @@ -207,6 +208,14 @@ ngx_http_v3_uni_read_handler(ngx_event_t b.pos = buf; b.last = buf + n; + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (ngx_http_v3_check_flood(c) != NGX_OK) { + ngx_http_v3_close_uni_stream(c); + return; + } + rc = ngx_http_v3_parse_uni(c, &us->parse, &b); if (rc == NGX_DONE) { @@ -282,6 +291,9 @@ ngx_http_v3_create_push_stream(ngx_conne p = (u_char *) ngx_http_v3_encode_varlen_int(p, push_id); n = p - buf; + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + if (sc->send(sc, buf, n) != (ssize_t) n) { goto failed; } @@ -291,7 +303,6 @@ ngx_http_v3_create_push_stream(ngx_conne goto failed; } - h3c = ngx_http_v3_get_session(c); h3c->npushing++; cln->handler = ngx_http_v3_push_cleanup; @@ -383,6 +394,9 @@ ngx_http_v3_get_uni_stream(ngx_connectio n = (u_char *) ngx_http_v3_encode_varlen_int(buf, type) - buf; + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + if (sc->send(sc, buf, n) != (ssize_t) n) { goto failed; } @@ -403,6 +417,7 @@ ngx_http_v3_send_settings(ngx_connection u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 6]; size_t n; ngx_connection_t *cc; + ngx_http_v3_session_t *h3c; ngx_http_v3_srv_conf_t *h3scf; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send settings"); @@ -431,6 +446,9 @@ ngx_http_v3_send_settings(ngx_connection p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->max_blocked_streams); n = p - buf; + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + if (cc->send(cc, buf, n) != (ssize_t) n) { goto failed; } @@ -448,9 +466,10 @@ failed: ngx_int_t ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id) { - u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 3]; - size_t n; - ngx_connection_t *cc; + u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 3]; + size_t n; + ngx_connection_t *cc; + ngx_http_v3_session_t *h3c; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send goaway %uL", id); @@ -465,6 +484,9 @@ ngx_http_v3_send_goaway(ngx_connection_t p = (u_char *) ngx_http_v3_encode_varlen_int(p, id); n = p - buf; + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + if (cc->send(cc, buf, n) != (ssize_t) n) { goto failed; } @@ -482,9 +504,10 @@ failed: ngx_int_t ngx_http_v3_send_ack_section(ngx_connection_t *c, ngx_uint_t stream_id) { - u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; - size_t n; - ngx_connection_t *dc; + u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; + size_t n; + ngx_connection_t *dc; + ngx_http_v3_session_t *h3c; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 client ack section %ui", stream_id); @@ -497,6 +520,9 @@ ngx_http_v3_send_ack_section(ngx_connect buf[0] = 0x80; n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 7) - buf; + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + if (dc->send(dc, buf, n) != (ssize_t) n) { ngx_http_v3_close_uni_stream(dc); return NGX_ERROR; @@ -509,9 +535,10 @@ ngx_http_v3_send_ack_section(ngx_connect ngx_int_t ngx_http_v3_send_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) { - u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; - size_t n; - ngx_connection_t *dc; + u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; + size_t n; + ngx_connection_t *dc; + ngx_http_v3_session_t *h3c; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 client cancel stream %ui", stream_id); @@ -524,6 +551,9 @@ ngx_http_v3_send_cancel_stream(ngx_conne buf[0] = 0x40; n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 6) - buf; + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + if (dc->send(dc, buf, n) != (ssize_t) n) { ngx_http_v3_close_uni_stream(dc); return NGX_ERROR; @@ -536,9 +566,10 @@ ngx_http_v3_send_cancel_stream(ngx_conne ngx_int_t ngx_http_v3_send_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) { - u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; - size_t n; - ngx_connection_t *dc; + u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; + size_t n; + ngx_connection_t *dc; + ngx_http_v3_session_t *h3c; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 client increment insert count %ui", inc); @@ -551,6 +582,9 @@ ngx_http_v3_send_inc_insert_count(ngx_co buf[0] = 0; n = (u_char *) ngx_http_v3_encode_prefix_int(buf, inc, 6) - buf; + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + if (dc->send(dc, buf, n) != (ssize_t) n) { ngx_http_v3_close_uni_stream(dc); return NGX_ERROR;