Mercurial > hg > nginx-quic
view src/http/v2/ngx_http_v2_table.c @ 8551:c35b255d80dc quic
HTTP/3: close connection on keepalive_requests * 2.
After receiving GOAWAY, client is not supposed to create new streams. However,
until client reads this frame, we allow it to create new streams, which are
gracefully rejected. To prevent client from abusing this algorithm, a new
limit is introduced. Upon reaching keepalive_requests * 2, server now closes
the entire QUIC connection claiming excessive load.
author | Roman Arutyunyan <arut@nginx.com> |
---|---|
date | Thu, 29 Jul 2021 16:01:37 +0300 |
parents | 87e9e4aabf1b |
children |
line wrap: on
line source
/* * Copyright (C) Nginx, Inc. * Copyright (C) Valentin V. Bartenev */ #include <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h> #define NGX_HTTP_V2_TABLE_SIZE 4096 static ngx_int_t ngx_http_v2_table_account(ngx_http_v2_connection_t *h2c, size_t size); static ngx_http_v2_header_t ngx_http_v2_static_table[] = { { ngx_string(":authority"), ngx_string("") }, { ngx_string(":method"), ngx_string("GET") }, { ngx_string(":method"), ngx_string("POST") }, { ngx_string(":path"), ngx_string("/") }, { ngx_string(":path"), ngx_string("/index.html") }, { ngx_string(":scheme"), ngx_string("http") }, { ngx_string(":scheme"), ngx_string("https") }, { ngx_string(":status"), ngx_string("200") }, { ngx_string(":status"), ngx_string("204") }, { ngx_string(":status"), ngx_string("206") }, { ngx_string(":status"), ngx_string("304") }, { ngx_string(":status"), ngx_string("400") }, { ngx_string(":status"), ngx_string("404") }, { ngx_string(":status"), ngx_string("500") }, { ngx_string("accept-charset"), ngx_string("") }, { ngx_string("accept-encoding"), ngx_string("gzip, deflate") }, { ngx_string("accept-language"), ngx_string("") }, { ngx_string("accept-ranges"), ngx_string("") }, { ngx_string("accept"), ngx_string("") }, { ngx_string("access-control-allow-origin"), ngx_string("") }, { ngx_string("age"), ngx_string("") }, { ngx_string("allow"), ngx_string("") }, { ngx_string("authorization"), ngx_string("") }, { ngx_string("cache-control"), ngx_string("") }, { ngx_string("content-disposition"), ngx_string("") }, { ngx_string("content-encoding"), ngx_string("") }, { ngx_string("content-language"), ngx_string("") }, { ngx_string("content-length"), ngx_string("") }, { ngx_string("content-location"), ngx_string("") }, { ngx_string("content-range"), ngx_string("") }, { ngx_string("content-type"), ngx_string("") }, { ngx_string("cookie"), ngx_string("") }, { ngx_string("date"), ngx_string("") }, { ngx_string("etag"), ngx_string("") }, { ngx_string("expect"), ngx_string("") }, { ngx_string("expires"), ngx_string("") }, { ngx_string("from"), ngx_string("") }, { ngx_string("host"), ngx_string("") }, { ngx_string("if-match"), ngx_string("") }, { ngx_string("if-modified-since"), ngx_string("") }, { ngx_string("if-none-match"), ngx_string("") }, { ngx_string("if-range"), ngx_string("") }, { ngx_string("if-unmodified-since"), ngx_string("") }, { ngx_string("last-modified"), ngx_string("") }, { ngx_string("link"), ngx_string("") }, { ngx_string("location"), ngx_string("") }, { ngx_string("max-forwards"), ngx_string("") }, { ngx_string("proxy-authenticate"), ngx_string("") }, { ngx_string("proxy-authorization"), ngx_string("") }, { ngx_string("range"), ngx_string("") }, { ngx_string("referer"), ngx_string("") }, { ngx_string("refresh"), ngx_string("") }, { ngx_string("retry-after"), ngx_string("") }, { ngx_string("server"), ngx_string("") }, { ngx_string("set-cookie"), ngx_string("") }, { ngx_string("strict-transport-security"), ngx_string("") }, { ngx_string("transfer-encoding"), ngx_string("") }, { ngx_string("user-agent"), ngx_string("") }, { ngx_string("vary"), ngx_string("") }, { ngx_string("via"), ngx_string("") }, { ngx_string("www-authenticate"), ngx_string("") }, }; #define NGX_HTTP_V2_STATIC_TABLE_ENTRIES \ (sizeof(ngx_http_v2_static_table) \ / sizeof(ngx_http_v2_header_t)) ngx_str_t * ngx_http_v2_get_static_name(ngx_uint_t index) { return &ngx_http_v2_static_table[index - 1].name; } ngx_str_t * ngx_http_v2_get_static_value(ngx_uint_t index) { return &ngx_http_v2_static_table[index - 1].value; } ngx_int_t ngx_http_v2_get_indexed_header(ngx_http_v2_connection_t *h2c, ngx_uint_t index, ngx_uint_t name_only) { u_char *p; size_t rest; ngx_http_v2_header_t *entry; if (index == 0) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent invalid hpack table index 0"); return NGX_ERROR; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 get indexed %s: %ui", name_only ? "name" : "header", index); index--; if (index < NGX_HTTP_V2_STATIC_TABLE_ENTRIES) { h2c->state.header = ngx_http_v2_static_table[index]; return NGX_OK; } index -= NGX_HTTP_V2_STATIC_TABLE_ENTRIES; if (index < h2c->hpack.added - h2c->hpack.deleted) { index = (h2c->hpack.added - index - 1) % h2c->hpack.allocated; entry = h2c->hpack.entries[index]; p = ngx_pnalloc(h2c->state.pool, entry->name.len + 1); if (p == NULL) { return NGX_ERROR; } h2c->state.header.name.len = entry->name.len; h2c->state.header.name.data = p; rest = h2c->hpack.storage + NGX_HTTP_V2_TABLE_SIZE - entry->name.data; if (entry->name.len > rest) { p = ngx_cpymem(p, entry->name.data, rest); p = ngx_cpymem(p, h2c->hpack.storage, entry->name.len - rest); } else { p = ngx_cpymem(p, entry->name.data, entry->name.len); } *p = '\0'; if (name_only) { return NGX_OK; } p = ngx_pnalloc(h2c->state.pool, entry->value.len + 1); if (p == NULL) { return NGX_ERROR; } h2c->state.header.value.len = entry->value.len; h2c->state.header.value.data = p; rest = h2c->hpack.storage + NGX_HTTP_V2_TABLE_SIZE - entry->value.data; if (entry->value.len > rest) { p = ngx_cpymem(p, entry->value.data, rest); p = ngx_cpymem(p, h2c->hpack.storage, entry->value.len - rest); } else { p = ngx_cpymem(p, entry->value.data, entry->value.len); } *p = '\0'; return NGX_OK; } ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent out of bound hpack table index: %ui", index); return NGX_ERROR; } ngx_int_t ngx_http_v2_add_header(ngx_http_v2_connection_t *h2c, ngx_http_v2_header_t *header) { size_t avail; ngx_uint_t index; ngx_http_v2_header_t *entry, **entries; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 table add: \"%V: %V\"", &header->name, &header->value); if (h2c->hpack.entries == NULL) { h2c->hpack.allocated = 64; h2c->hpack.size = NGX_HTTP_V2_TABLE_SIZE; h2c->hpack.free = NGX_HTTP_V2_TABLE_SIZE; h2c->hpack.entries = ngx_palloc(h2c->connection->pool, sizeof(ngx_http_v2_header_t *) * h2c->hpack.allocated); if (h2c->hpack.entries == NULL) { return NGX_ERROR; } h2c->hpack.storage = ngx_palloc(h2c->connection->pool, h2c->hpack.free); if (h2c->hpack.storage == NULL) { return NGX_ERROR; } h2c->hpack.pos = h2c->hpack.storage; } if (ngx_http_v2_table_account(h2c, header->name.len + header->value.len) != NGX_OK) { return NGX_OK; } if (h2c->hpack.reused == h2c->hpack.deleted) { entry = ngx_palloc(h2c->connection->pool, sizeof(ngx_http_v2_header_t)); if (entry == NULL) { return NGX_ERROR; } } else { entry = h2c->hpack.entries[h2c->hpack.reused++ % h2c->hpack.allocated]; } avail = h2c->hpack.storage + NGX_HTTP_V2_TABLE_SIZE - h2c->hpack.pos; entry->name.len = header->name.len; entry->name.data = h2c->hpack.pos; if (avail >= header->name.len) { h2c->hpack.pos = ngx_cpymem(h2c->hpack.pos, header->name.data, header->name.len); } else { ngx_memcpy(h2c->hpack.pos, header->name.data, avail); h2c->hpack.pos = ngx_cpymem(h2c->hpack.storage, header->name.data + avail, header->name.len - avail); avail = NGX_HTTP_V2_TABLE_SIZE; } avail -= header->name.len; entry->value.len = header->value.len; entry->value.data = h2c->hpack.pos; if (avail >= header->value.len) { h2c->hpack.pos = ngx_cpymem(h2c->hpack.pos, header->value.data, header->value.len); } else { ngx_memcpy(h2c->hpack.pos, header->value.data, avail); h2c->hpack.pos = ngx_cpymem(h2c->hpack.storage, header->value.data + avail, header->value.len - avail); } if (h2c->hpack.allocated == h2c->hpack.added - h2c->hpack.deleted) { entries = ngx_palloc(h2c->connection->pool, sizeof(ngx_http_v2_header_t *) * (h2c->hpack.allocated + 64)); if (entries == NULL) { return NGX_ERROR; } index = h2c->hpack.deleted % h2c->hpack.allocated; ngx_memcpy(entries, &h2c->hpack.entries[index], (h2c->hpack.allocated - index) * sizeof(ngx_http_v2_header_t *)); ngx_memcpy(&entries[h2c->hpack.allocated - index], h2c->hpack.entries, index * sizeof(ngx_http_v2_header_t *)); (void) ngx_pfree(h2c->connection->pool, h2c->hpack.entries); h2c->hpack.entries = entries; h2c->hpack.added = h2c->hpack.allocated; h2c->hpack.deleted = 0; h2c->hpack.reused = 0; h2c->hpack.allocated += 64; } h2c->hpack.entries[h2c->hpack.added++ % h2c->hpack.allocated] = entry; return NGX_OK; } static ngx_int_t ngx_http_v2_table_account(ngx_http_v2_connection_t *h2c, size_t size) { ngx_http_v2_header_t *entry; size += 32; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 table account: %uz free:%uz", size, h2c->hpack.free); if (size <= h2c->hpack.free) { h2c->hpack.free -= size; return NGX_OK; } if (size > h2c->hpack.size) { h2c->hpack.deleted = h2c->hpack.added; h2c->hpack.free = h2c->hpack.size; return NGX_DECLINED; } do { entry = h2c->hpack.entries[h2c->hpack.deleted++ % h2c->hpack.allocated]; h2c->hpack.free += 32 + entry->name.len + entry->value.len; } while (size > h2c->hpack.free); h2c->hpack.free -= size; return NGX_OK; } ngx_int_t ngx_http_v2_table_size(ngx_http_v2_connection_t *h2c, size_t size) { ssize_t needed; ngx_http_v2_header_t *entry; if (size > NGX_HTTP_V2_TABLE_SIZE) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent invalid table size update: %uz", size); return NGX_ERROR; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 new hpack table size: %uz was:%uz", size, h2c->hpack.size); needed = h2c->hpack.size - size; while (needed > (ssize_t) h2c->hpack.free) { entry = h2c->hpack.entries[h2c->hpack.deleted++ % h2c->hpack.allocated]; h2c->hpack.free += 32 + entry->name.len + entry->value.len; } h2c->hpack.size = size; h2c->hpack.free -= needed; return NGX_OK; }